Skip to content

Threat model ​

What agent-custody is built to withstand, what it is not, and who has to be trusted in each setup. Written for the security reviewer who has to decide whether the evidence this produces would hold in a dispute, and for us, since every design decision in the packages traces to a row here. Read verification.md for what each check proves and compliance.md for which control each artefact satisfies; this page is about attackers.

The claim being defended ​

A receipt is a signed statement that a named agent, acting for a named principal under a specific grant and policy, asked for a specific tool call at a specific time, and that the call was allowed, denied, withheld, executed, or failed, with the arguments and the result digested. The remote log commits to the receipt's existence before or shortly after the action, in a tree whose heads are signed by a key the agent's operator does not hold. The claim under attack is therefore: this receipt describes what actually happened, and it was not written, altered, or dropped after the fact.

Everything below asks who could make that claim false, and what stops them.

Parties ​

partyholdswants, if hostile
the agent (the model and its process)its own arguments, whatever credentials are in its environmentto act outside its grant, to hide an action, to make a denied action look allowed
the operator (the team running the agent and the gateway)the gateway key, the receipts directory, the local log, the policyto rewrite history after an incident, to produce receipts for actions that did not happen, to drop receipts for ones that did
the principal (whoever the agent acts for)their signing keyto disown a grant they signed, or to claim a grant they never gave
the upstream (the tool, API, or MCP server)its own key if it signs results, its own recordsto deny it served a result, or to claim it served a different one
the log operator (us, for the hosted log)the log's signing key, every tenant's leaf hashes, token hashesto rewrite a tenant's tree, to show different histories to different verifiers, to read receipt contents
the witness (a second operator)its own keyto countersign a rewritten history, or to refuse a true one
a network attackerthe wire between any two of the aboveto replay, drop, or alter appends and results
a verifier (auditor, counsel, regulator)public keys and receipts they were handedto be misled by any of the above

What the design assumes ​

  • Ed25519 signatures and SHA-256 are sound. Post-quantum signatures are issue #11, not present.
  • Private keys stay private to their holder. Key custody is the holder's problem; the packages read keys from files or a signer process and never transmit them.
  • The gateway runs where the agent cannot read its key or its receipts directory. A gateway in the agent's own container, with the key readable by the agent, is an SDK with extra steps and the receipts it produces are worth exactly that.
  • Clocks are roughly right. Timestamps in receipts are the producer's; the log's checkpoint times and the witness's countersignature times are the only times a verifier can hold anyone to.
  • A verifier obtains public keys from somewhere the attacker does not control: the log's key document over TLS, or a key file handed over out of band.

Attacks and what stops them ​

Each row names the attacker, the move, the defence, and whether the defence is a property of the evidence (holds against anyone later) or a property of the deployment (holds only if the deployment is as described).

By the agent ​

attackdefencekind
Call a tool the grant does not allowThe gateway evaluates the policy against the grant the principal signed; the agent never sees the policy or the key. A denied call never reaches the upstream and still produces a receipt.deployment: only calls through the gateway are covered
Feed the policy lies in the argumentscontext.args is claimed; policies that matter read context.facts, which the gateway fetched itself and marks observed. A policy that decides on args alone is the operator's choice and the receipt shows it.evidence: provenance is in the receipt
Bypass the gateway with a credential in its own environmentNothing in the packages prevents this. The gateway covers the tools behind it and no others.not covered; the deployment must keep credentials out of the agent
Skip an in-process hook or SDK wrapperSame: an SDK receipt is the agent's own report, and every field in it is claimed.not covered; use the gateway for anything consequential
Write memory the fleet will trustWrites not made through the gateway are claimed and quarantined until a gateway confirms them; reads leave them out unless asked for.deployment
Exhaust the log so its own calls are withheld rather than recordedA withheld call shows allow next to withheld and nothing happened upstream; there is no state in which an action occurs without a receipt when precommit names the tool.evidence

By the operator ​

attackdefencekind
Rewrite a receipt after the factThe receipt's envelope is signed by the gateway key, which the operator holds, so the signature alone does not stop this. What stops it is the log: the leaf hash of the original envelope is in a tree whose head the log signed, and a rewritten receipt has a different hash with no inclusion proof.evidence, with a remote log; deployment, with a local log the operator can rewrite
Drop a receiptThe log's tree only grows, and consistency proofs between any two heads show nothing was removed. A receipt the operator never logged never existed as evidence, which is the point of precommit for consequential calls: the authorization leaf goes in before the action.evidence for logged receipts; withheld calls for pre-committed tools
Forge a receipt for an action that never happenedNothing in the log stops the operator logging a fabricated receipt; the log commits to existence, not truth. What limits it: the upstream's signature over the result when the upstream signs, provider attestations where the provider delivers them, and the principal's signature on the grant. A forged receipt for a signing upstream fails its upstream checks.evidence, where the upstream signs; otherwise the operator's word
Replace the policy and claim a different one decidedThe receipt carries the sha256 of the policy text; a verifier with the policy file can check it, and a policy change is a different digest in every later receipt.evidence
Produce a second, cleaner historyWith a remote log, both histories would need heads signed by the log's key at the same size with different roots; the checkpoints host and the witness make that detectable. With a local log, a copy taken earlier by someone else is the only defence.evidence with a witnessed remote log
Erase what an agent believedforget erases the value and leaves a digest keyed by a forget key held outside the ledger, so the erasure is provable without the value. A forget with none leaves nothing and the custody pack says so.evidence

By the principal ​

attackdefencekind
Disown a grantThe grant is signed with the principal's key and embedded in every receipt issued under it; the verifier checks the signature against the principal's public key.evidence
Claim a grant they never gaveNobody else holds their key. A leaked principal key is the principal's problem and the reason grants carry expiry.assumption

By the upstream ​

attackdefencekind
Deny it served a resultWhen the upstream signs { receiptId, tool, contentDigest } the receipt carries its signature. When it does not sign, the receipt carries the gateway's digest of what the gateway saw, which is the operator's word against the upstream's.evidence, where the upstream signs
Serve a different result to the gateway than it recordsSame signature. Providers that deliver attestations (Stripe, GitHub webhooks) are checked against the provider's secret.evidence, where available

By the log operator ​

attackdefencekind
Read receipt contentsWith hashOnly, the log never receives them; only leaf hashes cross the wire. This is the default in the welcome sheet and the runbook, and it is the tenant's setting, not ours.deployment on the tenant's side
Rewrite a tenant's treeEvery head is signed and published as a checkpoint on a second host; a rewrite means two signed heads at one size with different roots, or a later head that does not extend an earlier one. Anyone holding an earlier head detects it with audit; the monitor does it every ten minutes; the witness countersigns only heads that extend the last it signed.evidence, given a witness or an earlier head held elsewhere
Show different histories to different verifiersSame: checkpoints are public and the witness sees one history. Without a witness, two verifiers who compare heads detect it, and nobody else does.evidence with a witness; otherwise detection needs comparison
Mint a token for a tenant and append noiseAppends are hashes with no content; the tenant's export and usage show leaves and live tokens they did not make, and the audit trail, which the tenant's export carries, records who minted what and from where.deployment
Sign with a key not in the key documentVerifiers pin by keyid from the published document; a head signed by an unpublished key fails. Retired keys stay published so old heads keep verifying, and a rotation is announced with its date.evidence
DisappearThe tenant's export carries every leaf hash, the signed head, the keys, and the checkpoints; verify --log and audit --log work against it with no server.evidence

By the witness ​

attackdefencekind
Countersign a rewritten historyIt cannot without also holding the log's key; a countersignature is over the log's own signed head. A witness that countersigns two heads at one size has published its own dishonesty.evidence
Refuse a true historyAn alarm with no consistency failure behind it is a false alarm; the log's consistency proof settles it in public. Verifiers who require a witness signature see a gap, which is the correct outcome for a disputed period.evidence
Collude with the log operatorTwo independent operators are the assumption. A witness on the log operator's machine proves nothing, and the deployment guide says so.assumption

On the network ​

attackdefencekind
Replay an appendAppends are idempotent in effect: the same leaf hash appended twice is two leaves, both true. Nothing an attacker replays creates a receipt the gateway did not sign.evidence
Alter an append in flightTLS between gateway and log; the leaf hash is over a signed envelope, so an altered hash simply fails inclusion for the real receipt.evidence
Drop the log's answerThe gateway retries, then errors or withholds; no receipt is handed out without an inclusion proof. Timeouts bound the wait.evidence
Steal a tenant tokenTokens are bearer secrets; a stolen one appends noise to that tenant's log until revoked. Rate limits bound the damage per second; the tenant's usage shows it.deployment

What is not defended ​

Stated plainly, because a security review that finds these itself will not believe the rest.

  • Actions outside the gateway. A credential the agent holds directly is used directly. The gateway is a choke point only for what goes through it.
  • Truth of SDK receipts. An in-process receipt is the process's own report. It is history, not evidence against that process.
  • Truth of what the upstream did when the upstream does not sign. The receipt proves what the gateway sent and what it got back, on the gateway's word.
  • The operator's key custody. A stolen gateway key signs receipts the verifier cannot tell from real ones; the log still bounds when they were created.
  • Availability. A log that is down withholds pre-committed calls. That is the designed behaviour and it is a denial of service on the agent. Run the log with the runbook's monitoring, and expect a tenant to ask for the SLA.
  • Post-quantum adversaries. Issue #11.
  • A dishonest log operator with a dishonest witness. Two colluding parties can present a consistent false history. The defence is choosing them independently.

How the hosted log is run against this model ​

The deployment in deploy/ and its runbook: the signer alone holds the key and is reachable only inside the compose network; the log process facing the internet holds no key; tenant tokens are hashed at rest and shown once; every admin action requires the admin token and is throttled; checkpoints are published on a second host; the monitor runs from machines that are not ours every ten minutes; backups are nightly, with an off-machine mirror the runbook installs once a destination is configured; the key rotates yearly and on suspicion, with retired keys published; the witness runs on another operator's machine, or the site says it does not yet.