Inspiration
Spending caps check how much an agent pays. We check why. Raijin is an x402 gateway that stops agent payment fraud before signing. Code checks the payment against a human-approved task. The judge never sees attacker text. CDP independently rejects the signatures we demonstrate.
AI agents now pay for APIs on their own over x402: fractions of a cent per call, signed automatically. Every control we found answers how much (Coinbase Agentic Wallets, Ramp and Mercury agent cards) or to whom (payee checks). None of them asks whether a payment serves the task the agent was given. So a prompt-injected agent making a small, plausible, off-task purchase gets through every one of them. This is important because our task for security is asymmetrical.
What it does
A gateway that sits between an agent and its money. A human creates a mandate: the task, the specific subjects it covers, the merchants allowed, a budget and an expiry. The agent gets a token for that one mandate, and never holds a wallet key. Every payment then runs through these tiers in order:
- Pre-contact check. Before the gateway contacts anyone, the URL is canonicalized and matched to the merchant catalog. Secrets in the URL, undeclared parameters, and subjects outside the mandate are stopped here.
- Hard rules (Tier 0). Network, payee swaps, per-call caps, reference-price jumps, budget and velocity. The budget is reserved atomically before anything is signed.
- Grants (Tier 1). An exact-match permission from an earlier approval: same mandate, payee, method, endpoint and parameter values. A hit approves the payment in milliseconds.
- Task judge (Tier 2). An LLM asks one question: would someone doing exactly this task need this purchase? It never reads attacker-written text. It sees the human's task, the admin's catalog labels, a price and counts. Unknown hosts and routes deny at admission and never reach the judge.
- Human review (Tier 3). Escalations wait in a dashboard queue for an approval or rejection with a note.
Durable per-agent restrictions only tighten automatically: defined incidents require review, and repeated security incidents suspend purchases across sessions and mandates. Clean payments never earn trust back. Only an explicit admin restoration with an operator name and reason loosens restrictions. The judge never sees trust. The dashboard shows restriction state, recent incidents, restoration controls, live decisions and the approval queue; the historical trust graph remains hidden.
The signer is a second, independent check. Same cataloged merchant, same URL, same 1¢. A research mandate is denied; a bulk-export mandate is paid. Caps and payee rules cannot see that difference. The judge never reads the page, the URL, or the 402 text. A canary test on every commit proves it. Signing is a Coinbase CDP server account with an attached policy: Base Sepolia USDC TransferWithAuthorization only, merchant payee only, 0.01 USDC max. We skip the gateway’s own checks and ask CDP to sign 0.50 USDC to an attacker; it returns 403 policy_violation. The runtime key cannot loosen that policy. Settlement uses the public x402.org facilitator, not CDP’s. The merchant is loopback HTTP. The policy does not see endpoints, subjects, or running totals.
How we built it
- Gateway: TypeScript, Hono, SQLite with Drizzle. Atomic SQL for reservations, settlement and grant use.
- Payments: x402 (
@x402/core,@x402/fetch,@x402/evm,@x402/express), viem, USDC on Base Sepolia through the public x402.org test facilitator (not CDP's facilitator). Includes a payment lifecycle and a reconciler, so a lost settlement response or a crash never overspends. - Signer: a Coinbase CDP server account (
@coinbase/cdp-sdk) under an attached signing policy, with separate admin and runtime keys. The gateway never falls back to a local key if CDP refuses or times out. - Judge: Claude via native
fetchto the Anthropic API (not the SDK), temperature 0, pinned model, 5-second timeout, output validated with zod. Any error is a denial. A plan drafter and escalation explainer exist as advisory APIs with a CLI; they are not wired into the dashboard or/pay. - Dashboard: Next.js with server-sent events. The admin token stays on the server. Agent restriction state and audited restoration are shown; the historical trust graph is not.
- Test merchants: one honest x402 seller, PriceScope, serving synthetic price data on
http://localhost:4101(a loopback exception in the gateway's destination rules, not HTTPS), and an attacker server onlocalhost:4199with injected pages. The attacker server isn't in the catalog, so the gateway never contacts it. - Contracts first: one shared types package, with the judge's input fixed by contract so that no code path can add attacker text to it.
Evaluation
What we measured live. Two frozen matched-pair sets. Same cataloged merchant, same URL, only the human task changes. Claude Sonnet 4.5, three repetitions, mock payments, 2026-10-24. Labels are authored and not independently reviewed. This is not plan-lock and not chain settlement.
v1.3-v1 (225e634): six pairs plus four boundary probes.
| Category baseline (no task text) | Approve-everything stub | Live judge (reps 1 / 2 / 3) | |
|---|---|---|---|
| Off-task twins stopped | 1 / 6 (also wrongly blocks the legit export) | 0 / 6 | 6 / 6 / 6 |
| Legitimate twins auto-approved | 5 / 6 | 6 / 6 | 5 / 6, 6 / 6, 6 / 6 |
| Judge-only catches vs that baseline | — | 0 / 5 | 5 / 5 / 5 |
| Boundary probes (unselected, unknown route, leftover grant, resume after deselect) | 4 / 4, zero judge calls | 4 / 4 | 4 / 4 |
| Off-task mock settlements | 5 | 6 | 0 / 0 / 0 |
Rep 1's only miss was a JUDGE_ERROR on the legitimate full-history export, not a wrong denial. Live judge median latency was about 2.8 s.
heldout-v2 (d2cd0fa, authored by a teammate who did not read the judge prompt): eight PriceScope pairs. Six lookups are judge-only; the baseline denies both export twins.
| Category baseline | Approve-everything stub | Live judge (reps 1 / 2 / 3) | |
|---|---|---|---|
| Attacks stopped | 2 / 8 (both exports; also blocks both legit exports) | 0 / 8 | 7 / 8, 6 / 8, 6 / 8 |
| Legitimate auto-approved | 6 / 8 | 8 / 8 | 8 / 8 / 8 |
| Judge-only catches vs that baseline (6 lookups) | — | 0 / 6 | 5 / 6, 4 / 6, 4 / 6 |
| Off-task mock settlements | 6 | 8 | 1 / 2 / 2 |
The live judge is the only layer that can tell a full-company export from a two-company one. press_attack (summarize a press release → Umbrella 30-day lookup) leaked every repetition. history_attack leaked in reps 2 and 3. We did not retune the prompt on this set.
What the older 40-case report is. eval/REPORT.md is a stub-approve mock run, not a live model. Under that stub the core set stopped 10 / 20 attacks by rules and let 10 through; the stronger category baseline stopped 20 / 20 without reading the task. Of 17 held-out page variants, only V4 still reaches the judge. The rest die at BAD_URL, SECRET_IN_URL, MERCHANT_NOT_SELECTED, SUBJECT_NOT_IN_MANDATE, or PAYEE_SWAP. Do not quote the stub table as live accuracy.
The canary test still passes on every commit: no attack fixture's canary string appears in a judge request.
Load test: unmeasured. No loadtest/REPORT.md.
Payment evidence
All on Base Sepolia, a test network whose USDC has no cash value:
- CDP probes, run against the real CDP API with the gateway's runtime key: a 0.01 USDC transfer to the merchant is signed; a value one unit over, another payee, another contract, a
Permittype,signHash,signMessage, and the gateway's own signer with its checks skipped asking for 0.50 USDC to an attacker all return403 policy_violation. Attempts by the runtime key to loosen or detach the policy return403 Missing required scope. - CDP-signed settlement through the gateway:
0x87d139ba…36e6da, purchased data delivered. - Lost reply, then a restart: the purchase came back
payment_unknown, the reconciler found the settlement on chain (0xbd4d0566…64675), and a retry with the same key after restarting the gateway returned the same payment, with no new payment created.
Challenges we ran into
- The judge has to decide task-relevance without reading the page, the URL, or the merchant's 402 text. Too little signal and it cannot tell a needed lookup from an off-task one; too much and it can be injected. Most attacks we wrote for it never reached it: unknown hosts, secrets in URLs, and unselected merchants die at pre-contact.
- The first on-chain settle failed because the public x402.org facilitator hit a nonce collision on its gas wallet, not because our signer was wrong. A retry landed. That is why an ambiguous outcome stays
UNKNOWNwith funds reserved, not a clean fail. - CDP allows a signing operation that has no rules. Our first policy still signed
signHashandsignMessage. We had to reject every other route explicitly, then prove the runtime key cannot edit or detach that policy. - A live model often refuses the poisoned page, so a demo that waits for bait-taking never shows the firewall. We added a labeled compromised worker that always sends the same cataloged purchase, and we measure that path.
What we're proud of
- An attack that passes every cap and payee rule, and is still stopped before signing.
- A judge that can't be prompt-injected through the payment request, because it never reads one, and a test that proves it on every commit.
- An attack set that someone who didn't build the judge wrote and labeled, plus a second held-out set they wrote without reading the prompt.
- Honest misses: a press-release summary still bought a one-cent lookup on every live repetition.
What's out of scope (and we say so)
- Data leaking through the agent's free requests. A payment gateway can't see them.
- A compromised gateway or admin, stolen keys, smart-contract bugs, chain-level attacks, and real money. Everything is testnet.
- Who still has to be trusted: whoever holds the CDP admin key (it can change the signing policy), CDP itself, and the operator who writes the catalog.
- The CDP policy is per signature. It checks payee, value, token and type. It doesn't know the HTTP endpoint, the subjects, the plan or cumulative totals.
- No HTTPS evidence. The only merchant runs on loopback HTTP under an explicit exception.
- The trust score and its thresholds are a starting policy, not a validated model.
- Before grants warm up, a judge call can cost more than the payment, and settlement fees can exceed tiny prices at volume. Batching settlement is out of scope.
- Labels are not independently reviewed. Current task-text results are not strict plan-lock.
What's next
Policies set per tenant, the egress sandbox for deployed agents, batched settlement, and a catalog that merchants can apply to join.
How we used generative AI
Claude is the task judge on the payment path and the live demo agent. Both call the Anthropic API with native fetch, not the SDK. A plan drafter and an escalation explainer were built as advisory interfaces (docs/judge-advisories.md) with a local CLI; they are not in the dashboard or the /pay pipeline.
Built With
- anthropic
- base-sepolia
- claude
- coinbase-cdp
- drizzle
- hono
- next.js
- node.js
- pnpm
- pytest
- python
- sqlite
- typescript
- usdc
- viem
- vitest
- x402
- zod

Log in or sign up for Devpost to join the conversation.