A prompt injection should not be able to drain a wallet.
Give an agent a signing key and you have given it everything that key can do. Prompt injection, a poisoned tool description, or a confused retrieval step then becomes a payment you did not intend — and the transaction that results looks perfectly valid, because it is.
The fix is not a better prompt. It is to make the unauthorized action unrepresentable at the point of signing.
What you add
- Middleware in front of the action — an HTTP payment endpoint or an agent tool call, the same decision engine behind both.
- The gate computes a fixed‑width binding over the exact request — scheme, network, asset, recipient, amount, resource — and evaluates it against the capability the agent actually holds.
- Three outcomes, never two: ALLOW, DENY (violation) and DENY (unavailable). An attack and an outage are different events and a policy engine that cannot tell them apart will eventually fail open on one of them.
- A pre‑sign guard that decodes the transaction about to be signed and refuses unless it pays exactly the bound recipient, exactly the bound asset, exactly the bound amount. Prevention, not an audit trail of the loss.
Sub-agents, without handing over the keys
An agent can delegate to another agent, and the delegation can only narrow — a smaller ceiling, a shorter window, a subset of counterparties, never more than the parent held. Delegation depth is bounded, and revoking a delegator’s key denies its sub‑agents while leaving the delegator’s own authority intact.
What “can only narrow” looks like
Three real tokens, minted by the reference implementation while this page was written. The chain is
human → agent → sub‑agent, and these are the decoded capability_scope claims
exactly as they appear in each token. Nothing below is illustrative.
go run ./cmd/agentdemo reproduces it offline, with no network and no keys of ours.
{"action":"payment","currency":"USD","max_amount":10000}
{"currency":"USD","max_amount":8000}
{"currency":"USD","max_amount":5000}
The ceiling falls at every hop, the depth counter falls with it, and the
human_anchor claim is byte‑identical in all three: the same accountable person,
carried unchanged to the last delegate. Widening is not rejected at spend time; it is refused at
mint, so a token granting more than its parent never exists. In the same demo, sub‑agent B
asking for 6,000 against its 5,000 ceiling is refused before any token is issued, and revoking agent A’s
delegation key kills B’s authority while leaving A’s own capability working.
Verification without a phone-home
Verification is offline: the verifier needs the token and a locally cached snapshot of the trust registry, and contacts no issuer, no registry service and no chain to reach a decision. Nothing about your users’ activity reaches us, because nothing reaches us at all.
And a receipt for every decision
Allow or deny, each decision emits a signed, hash‑chained receipt committed to an RFC 6962 Merkle log. When someone later asks why an agent did something, the answer is a verifiable record rather than a log line you have to be trusted about.
You run it. That is the point.
The control plane belongs with the regulated entity that already carries the obligation. We publish the software under Apache‑2.0; you deploy it, you hold the keys, you set the policy, and no transaction detail reaches us or anyone else. We do not operate a service in your money path and we take no fee per transaction.
Honest boundaries
If this is useful to you
Everything is public and reproducible — the reference implementation, the specification work, and the tests behind each claim above.
Reference implementation on GitHub →
Framework paper and IETF Internet‑Draft →
rudi@violetskysecurity.com