Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,27 @@ session and its auth deadline.
Credentials and approval codes are environment-only. They are never returned by
an MCP tool or written to Sibyl. See [docs/H3RETIK_RUNBOOK.md](docs/H3RETIK_RUNBOOK.md).

## Operation Red

Operation Red is the managed campaign path. A company defines an exact target
allowlist, module set, time window, request-rate ceiling, prohibited actions,
and maximum USDC budget. That canonical scope is hashed before approval.

Each module receives a separate H3RETIK worker receipt and worker identity. The
control plane accepts funding only after H3RETIK verifies the Base receipt and
returns the paid session and worker IDs. Bearer tokens are used only in memory
while dispatching and are never written to the campaign database or Sibyl.

The run order is operational modules, independent verification, then reporting.
H3RETIK execution attestations tied to the approved action become verified
telemetry; model-authored findings remain assertions until reproducible proof
promotes them. A campaign is marked reported only after its delivery provider
returns a message reference. AgentMail delivery uses the official inbox send
endpoint through `AgentMailReportSender`.

See [docs/OPERATION_RED.md](docs/OPERATION_RED.md) for the state machine,
integration API, credential boundary, and deterministic end-to-end test.

## H1DR4 integration

`attackgraph_discover_h1dr4_tools` reads the live H1DR4 MCP capability list and
Expand Down
87 changes: 87 additions & 0 deletions docs/OPERATION_RED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Operation Red control plane

Operation Red is the managed red-team campaign path. It coordinates paid,
disposable H3RETIK workers; it does not replace AttackGraph or Sibyl.

## Trust boundaries

1. The company supplies a target allowlist, modules, schedule, rate ceiling,
prohibited actions, and maximum budget.
2. `OperationRedCampaignStore.create` canonicalizes and hashes that scope.
3. A human approval identity approves that immutable hash.
4. `OperationRedOrchestrator.prepare_receipts` creates one H3RETIK worker
receipt per module. Each worker uses the `h1dr4-worker-pack` `redteam`
profile, BlockRun inference, and bounded Hermes toolsets.
5. `sync_funding` asks H3RETIK to verify each Base receipt. A caller-entered
amount, screenshot, or transaction claim is not accepted as funding.
6. At dispatch, the orchestrator re-syncs every receipt before changing the
campaign to `running`. It verifies the receipt ID, amount, wallet, Base/USDC
asset, worker ID, and session ID.
7. The paid token is passed directly to H3RETIK and discarded after the call.
Tokens are not stored in the Operation Red database or Sibyl.
8. Every module has a scoped AttackGraph action. Executor completion becomes a
verified Sibyl event only when its H3RETIK attestation correlates with that
action. Findings and loot parsed from the model response remain assertions.
9. The campaign enters `reported` only after the report sender returns a stable
provider message reference.

## State machine

```text
draft -> approved -> funded -> scheduled -> running -> verifying -> reported
| | | |
| | | +-- per-worker success/failure
| | +-- enforced UTC execution window
| +-- all assigned H3RETIK receipts verified paid
+-- receipts may be prepared, but no worker can run
```

Worker assignments retain operational metadata only: module, lane, receipt,
wallet, worker/session/job IDs, status, error, evidence digest, and telemetry
event ID. Report delivery retains its channel, recipient, status, evidence
digest, and provider reference.

## Python integration

```python
from h1dr4_attackgraph import OperationRedCampaignStore, OperationRedOrchestrator
from h1dr4_attackgraph.h3retik import H3retikClient

store = OperationRedCampaignStore(".attackgraph/operation-red.db")
orchestrator = OperationRedOrchestrator(
store=store,
h3retik=H3retikClient(),
attackgraph=attackgraph_service,
)

campaign = store.create(scope)
store.approve(campaign["campaign_id"], approved_by=passkey_credential_id)
quote = orchestrator.quote_campaign(campaign["campaign_id"])
prepared = orchestrator.prepare_receipts(campaign["campaign_id"], wallet=wallet)
# Fund each prepared assignment's receipt address on Base.
funded = orchestrator.sync_funding(campaign["campaign_id"])
store.schedule(campaign["campaign_id"])
result = orchestrator.dispatch(campaign["campaign_id"])
reported = orchestrator.deliver_report(
campaign["campaign_id"],
recipient="security@example.com",
sender=report_sender,
)
```

The forthcoming H1DR4 assignment page should call these operations from its
authenticated server. It must not expose `approve`, paid receipt auth, or
dispatch as unauthenticated browser or agent MCP tools.

## Verification

Run the deterministic integration test without paying or contacting a target:

```bash
uv run pytest tests/test_operation_red.py -q
```

The test proves receipt mismatch rejection, schedule enforcement, three worker
jobs, scoped actions, verified executor telemetry, asserted semantic findings,
loot ingestion, report delivery acknowledgement, and absence of bearer tokens
from the campaign database.
38 changes: 33 additions & 5 deletions docs/VALIDATION.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Validation record

Validated on 2026-09-03 from a non-FileProvider macOS workspace.
Latest local validation: 2026-09-08 from a non-FileProvider macOS workspace.

## Automated

```text
ruff: all checks passed
pytest: 33 passed
pytest: 58 passed
package: sdist and wheel build successfully
dashboard: lint and production build succeed
dashboard: lint and production build succeeded in the latest main CI
```

The suite covers:
Expand All @@ -32,6 +32,32 @@ The suite covers:
- Exact target, action, and bound-session correlation for verified evidence.
- Nested secret redaction across agent attributes and executor proof.
- Typed event, worker, session, job, room, and relationship projection into the dashboard.
- Operation Red scope and derived-plan integrity checks.
- Rejection of caller-asserted funding and exact H3RETIK paid-receipt validation.
- Per-module worker/session/job reconciliation without bearer-token persistence.
- Schedule-window enforcement before worker dispatch.
- Automatic verified executor telemetry plus asserted semantic findings and loot.
- Report delivery gating and the AgentMail send adapter.

## Operation Red deterministic end-to-end

The local suite executes a complete three-worker campaign with a deterministic
H3RETIK transport: `web`, `verification`, and `reporting`. It quotes and creates
the same worker receipt shape as the hosted MCP, syncs independently paid
Base/USDC receipts, binds separate H3RETIK session and worker IDs, runs each
worker, parses its completion envelope, and writes correlated execution,
finding, and artifact events into the real local Sibyl database.

The test proves the orchestration and trust boundaries without spending funds
or touching a target. It deliberately keeps model-authored findings asserted
while executor completion records become verified. The final state changes to
`reported` only after the report adapter returns a message reference. The test
also scans the campaign database bytes and confirms that the fake H3RETIK bearer
tokens were never persisted.

This does not claim a newly paid live three-worker campaign. The live hosted
runtime proof below and this orchestration proof are separate until a company
funds the full specialist campaign.

## MCP transport

Expand All @@ -56,8 +82,10 @@ h1dr4_osint_prepare
h1dr4_osint_agent
```

The live H3RETIK endpoint advertised 33 tools, including compute-window quote,
session-job create/start, job status, and job output. The initial read-only
The live H3RETIK endpoint advertised 38 tools. On 2026-09-08 a fresh read-only
worker quote confirmed all four worker tools, a `7.0 USDC` micro-worker quote,
manual `openai/gpt-5.6-sol`, `terminal,file`, and `blockrun-x402`. The initial
read-only
preflight quoted a 5-minute, 3-action Europe window at `0.19 USDC` without
accepting terms or spending funds. The separately authorized paid lifecycle is
recorded below.
Expand Down
8 changes: 7 additions & 1 deletion src/h1dr4_attackgraph/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
"""H1DR4 ATTACKGRAPH: durable, model-agnostic red-team context over MCP."""

from h1dr4_attackgraph.operation_red import OperationRedCampaignStore
from h1dr4_attackgraph.operation_red import (
AgentMailReportSender,
OperationRedCampaignStore,
OperationRedOrchestrator,
)
from h1dr4_attackgraph.products import MODULE_REGISTRY, OperationRedScope, ProductSurface

__version__ = "0.1.0"

__all__ = [
"MODULE_REGISTRY",
"AgentMailReportSender",
"OperationRedCampaignStore",
"OperationRedOrchestrator",
"OperationRedScope",
"ProductSurface",
"__version__",
Expand Down
159 changes: 158 additions & 1 deletion src/h1dr4_attackgraph/h3retik.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ def capabilities(self) -> dict[str, Any]:
"h3retik_create_workspace",
"h3retik_attach_session_to_workspace",
"h3retik_get_workspace",
"h3retik_quote_worker",
"h3retik_create_worker_receipt",
"h3retik_get_worker",
"h3retik_create_worker_job",
}
return {
"endpoint": self.rpc.endpoint,
Expand Down Expand Up @@ -111,6 +115,141 @@ def sync_receipt(self, receipt_id: str) -> Any:
{"receipt_id": receipt_id},
)

def quote_worker(
self,
*,
goal: str,
target: str,
package: str = "micro",
location: str = "auto",
inference_budget_usdc: float = 1.0,
model_mode: str = "manual",
model: str = "",
routing_profile: str = "premium",
hermes_toolsets: list[str] | None = None,
) -> Any:
arguments: dict[str, Any] = {
"plugin_id": "h1dr4-worker-pack",
"preset": "redteam",
"package": package,
"goal": goal,
"target": target,
"location": location,
"inference_budget_usdc": inference_budget_usdc,
"model_mode": model_mode,
"routing_profile": routing_profile,
"hermes_toolsets": hermes_toolsets or ["terminal", "file"],
}
if model:
arguments["model"] = model
return self.rpc.call_tool("h3retik_quote_worker", arguments)

def create_worker_receipt(
self,
*,
wallet: str,
goal: str,
target: str,
package: str = "micro",
location: str = "auto",
asset: str = "USDC",
inference_budget_usdc: float = 1.0,
model_mode: str = "manual",
model: str = "",
routing_profile: str = "premium",
hermes_toolsets: list[str] | None = None,
lane: str = "",
constraints: list[str] | None = None,
) -> Any:
arguments: dict[str, Any] = {
"wallet": wallet,
"plugin_id": "h1dr4-worker-pack",
"preset": "redteam",
"package": package,
"goal": goal,
"target": target,
"location": location,
"asset": asset,
"inference_budget_usdc": inference_budget_usdc,
"model_mode": model_mode,
"routing_profile": routing_profile,
"hermes_toolsets": hermes_toolsets or ["terminal", "file"],
"constraints": list(constraints or []),
}
if model:
arguments["model"] = model
if lane:
arguments["lane"] = lane
return self.rpc.call_tool("h3retik_create_worker_receipt", arguments)

def get_worker(self, *, wallet: str, token: str, worker_id: str) -> Any:
return self.rpc.call_tool(
"h3retik_get_worker",
{"wallet": wallet, "token": token, "worker_id": worker_id},
)

def execute_worker(
self,
*,
wallet: str,
token: str,
worker_id: str,
target: str,
workspace_id: str,
workspace_name: str = "",
session_label: str = "Operation Red worker",
session_lane: str = "",
max_minutes: int = 30,
job_id: str = "",
command: str = "",
poll_timeout: float | None = None,
) -> dict[str, Any]:
worker = self.get_worker(wallet=wallet, token=token, worker_id=worker_id)
session_id = str(self._find_value(worker, "session_id") or "")
if not session_id:
raise RuntimeError(f"H3RETIK worker did not return a session_id: {worker!r}")
if workspace_id:
self.attach_session(
wallet=wallet,
token=token,
workspace_id=workspace_id,
workspace_name=workspace_name,
session_id=session_id,
label=session_label,
lane=session_lane,
)
create_args: dict[str, Any] = {
"wallet": wallet,
"token": token,
"worker_id": worker_id,
"target": target,
"max_minutes": max_minutes,
}
if job_id:
create_args["job_id"] = job_id
if command:
create_args["cmd"] = command
created = self.rpc.call_tool("h3retik_create_worker_job", create_args)
created_job_id = str(self._find_value(created, "job_id") or "")
if not created_job_id:
raise RuntimeError(f"H3RETIK did not return a worker job_id: {created!r}")
if poll_timeout is None:
poll_timeout = self.default_poll_timeout({"max_minutes": max_minutes})
status, output = self._start_and_poll(
wallet=wallet,
token=token,
session_id=session_id,
job_id=created_job_id,
poll_timeout=poll_timeout,
)
return {
"worker_id": worker_id,
"session_id": session_id,
"job_id": created_job_id,
"status": status,
"output": output,
}

def execute_existing_session(
self,
*,
Expand Down Expand Up @@ -143,6 +282,24 @@ def execute_existing_session(
job_id = self._find_value(created, "job_id")
if not job_id:
raise RuntimeError(f"H3RETIK did not return a job_id: {created!r}")
last_status, output = self._start_and_poll(
wallet=wallet,
token=token,
session_id=session_id,
job_id=str(job_id),
poll_timeout=poll_timeout,
)
return {"job_id": job_id, "status": last_status, "output": output}

def _start_and_poll(
self,
*,
wallet: str,
token: str,
session_id: str,
job_id: str,
poll_timeout: float,
) -> tuple[Any, Any]:
started = self.rpc.call_tool(
"h3retik_start_job",
{"wallet": wallet, "token": token, "session_id": session_id, "job_id": job_id},
Expand All @@ -164,7 +321,7 @@ def execute_existing_session(
"h3retik_get_job_output",
{"wallet": wallet, "token": token, "job_id": job_id},
)
return {"job_id": job_id, "status": last_status, "output": output}
return last_status, output

@staticmethod
def default_poll_timeout(spec: dict[str, Any]) -> float:
Expand Down
Loading
Loading