
TollWarden
com.tollwardenPublished by tollwarden.comv1.6.0Updated Oct 24, 2026
Screens x402 payments before settlement for injected payee addresses and sellers who never deliver.
Overview
Screens x402 micropayments before settlement, returning allow, flag, or block verdicts for injected payee addresses and non-delivering sellers.
- What you can do
- TollWarden is an advisory, non-custodial payment firewall for x402 agents. It exposes scan endpoints for outgoing payments and incoming 402 offers, plus counterparty reputation lookups and reports, delivery-outcome recording, key lifecycle, and optional human-in-the-loop approvals. Checks cover PII and secret leakage in payment metadata, nonce replay, duplicate purchases, overpayment, prompt-injection-triggered payments, resource URL risk, and measured delivery history. Verdicts are returned with per-check reasons and Ed25519 attestations.
- When to use it
- Use it when an autonomous agent pays over x402 and you want a pre-settlement check against manipulated payee addresses, replayed authorizations, inflated prices, or sellers with poor delivery records. It is aimed at agents that read untrusted web or tool content before paying.
- Requirements
- Runs as a local stdio process on the desktop. The README shows npm and pip client packages and framework integrations, and the service is reached over HTTP. An API key is issued via the keys endpoint and sent in the X-API-Key header; the first 100 calls per key are free, after which calls are paid via x402. No environment variables or headers are declared in the manifest.
Installation
In SourceWeft
- Open TollWarden in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
TollWarden
A payment security firewall for x402 — screen every micropayment before it settles.
[x402] [network] [tests] [npm] [license]
Agents that pay over x402 get drained in predictable ways: secrets leak through payment metadata, captured authorizations get replayed, quoted prices get inflated, and poisoned web content tricks agents into paying addresses they never planned to pay. TollWarden is one POST before settlement that checks for all of it and returns allow / flag / block with machine-readable, per-check reasons — in ~0.6 ms.
TollWarden is advisory and non-custodial: it never touches private keys, wallets, or funds. It wraps around whatever facilitator and wallet your agent already uses. And it's a first-class x402 seller itself — its endpoints are paid via the official x402 middleware, settle through the Coinbase CDP facilitator, and carry Bazaar discovery metadata.
Use it in 30 seconds
New here? Protect your x402 agent in 5 minutes →
The SDK (sdk/, zero dependencies) also verifies every verdict's Ed25519 attestation against a pinned key, tracks your free-call quota, and can subscribe to plans autonomously. Wallet authors get standalone verifyAttestation() / computePaymentCommitment() — and the enforcement kit: new TollWardenEnforcer({ trustedKeyHex }).guardSigner(account) wraps any viem/ethers signer so it physically refuses to sign an x402 payment authorization without a fresh, payment-bound allow-verdict.
Framework integrations
Building on an agent framework? TollWarden ships drop-in packages that give your agent "scan before you pay" in about two lines — a toolset plus a provenance mechanism that auto-tags what the agent reads, so the prompt-injection-triggered-payment detector works without any prompt engineering:
Each exposes the same three tools (scan / check reputation / report) and a framework-native way to feed what the agent read into injection detection. LangChain uses a callback, CrewAI an after-tool-call hook and the Vercel AI SDK an onStepFinish handler, and all three tag it automatically. NeMo and AgentKit take it as an explicit content argument, and the AgentKit action also fills in the payer from the agent's own wallet. Every package also ships a guarded_payment / guardedPayment wrapper for enforcement by construction (the payment executor never runs on a block verdict), and the NeMo one accepts an async executor. See each package's README for the two-line setup, or its page under tollwarden.com/integrations.
What it catches
The AI agent payment security guide walks through the attacks these checks answer, one failure mode at a time, with each check's limits.
Core detectors
Zero-latency hardening tier — checks that hold even when the calling agent's narration is compromised:
Signed verdicts. Every response carries an Ed25519 attestation over scan_id|direction|verdict|risk_score|scanned_at|payment_commitment|expires_at (public key at /.well-known/tollwarden-verdict-key). The payment_commitment is sha256(network|pay_to|asset|amount|nonce), so a wallet can confirm an allow-verdict belongs to this payment and hasn't been replayed onto another, and reject it after expires_at. Wallet policies can require a fresh signed allow-verdict before signing — turning the firewall from advisory into enforceable, still without TollWarden touching funds.
Signed pin evidence. Scan attestations also carry a second signed record — evidence-v1|scan_id|payment_commitment|pin_domain|pin_age_seconds|pin_corroboration (same key, bound to the same scan and payment). It publishes how long the merchant pin behind this payee had held at scan time (0 = first sighting) and which out-of-band sources corroborated it — each named (today: cdp_bazaar, the CDP Bazaar merchant index listing the domain among the pinned address's resources), never a boolean and never a composite score, because a corroboration that succeeded through a genuinely independent channel and one the attacker already controls would be the same bit, and source strength is a per-merchant judgment that belongs to you. Folding "young pin, corroborated" into a single allow/flag would make a risk-tolerance call TollWarden has no business making: with separate signed fields, a wallet can accept a four-minute-old corroborated pin for a cent and refuse it for fifty dollars. The verdict message itself stays frozen at its 7 fields — deployed verifiers are unaffected, and both SDKs verify the evidence record and surface it as pin_evidence. One honest limit: verifiers tolerate an absent evidence record (older servers, override attestations), so absence proves nothing — a stripped record is indistinguishable from an old server, and only the signed empty fields assert "no pin applies". If your policy requires pin evidence, decide explicitly what an attestation without it means to you. Every signed field is a compatibility commitment: fields get added, never quietly changed or withdrawn.
Wallet-side enforcement. Both SDKs ship that policy turnkey as TollWardenEnforcer. enforcer.guardSigner(account) (TS: viem accounts, ethers v6 signers) / enforcer.guard_signer(account) (Python: eth-account, all call shapes) wraps the signer in a proxy that recomputes the payment commitment from the typed data being signed (EIP-3009 / ERC-2612) and refuses the signature unless a fresh, pinned-key-verified allow-verdict exists for exactly that commitment. Approvals are single-use and expire with the attestation; a compromised agent that scans payment A cannot sign payment B, and one that skips scanning cannot sign at all. Optional local policy bounds it further: a hard recipient allowlist (allowedRecipients) and per-payment / cumulative spend caps (maxAmountAtomic / maxTotalAtomic), checked against the typed data at signature time with no server involved — even a payment carrying a valid allow-verdict is refused if it pays an unlisted recipient or exceeds the caps, so a subverted advisory layer can only move bounded amounts to known parties. The agent can never extend the allowlist; the one escape hatch is opt-in (overrideAdmitsRecipient, on top of acceptOverrides): a human-approved override from step-up approvals admits exactly the payment it binds — never the recipient, and never past the spend caps. Fail-closed and fully local; the two implementations are cross-validated against the same production signer — see sdk/README.md and sdk-python/README.md. It composes with the default payment path. Pass the enforcer to wrapFetchWithTollWarden(…, { enforcer }) / wrap_transport_with_tollwarden(…, enforcer=…) and each scanned offer's allow verdict becomes the signing authority for the one nonce-bearing authorization the x402 client mints for it. The wrapper scans the offer with context.phase: "pre_sign", because it has no nonce yet. The enforcer accepts only allow verdicts by default, so declare where the decision came from before the request (notePlanning(), noteUserInstruction() or observe()), since an undeclared origin flags and is refused before anything is signed.
Tamper-evident audit log. Every scan decision is appended to a hash-chained log (AUDIT_LOG=on) that stores a SHA-256 of the payment plus non-sensitive transaction facts — never the plaintext PII/secrets it scans. GET /v1/audit/verify recomputes the chain and reports any tampering; GET /v1/audit/head returns the current head hash for external anchoring. See SECURITY-AUDIT.md.
API
¹ Per-scan price drops on a plan: Pro ($4.99/30d) → $0.005/scan with 6× velocity headroom and deep content analysis on every scan; Scale ($19.99/30d) → $0.002/scan at the hard-ceiling limits. Plans raise your own thresholds only — replay detection, merchant pinning, asset verification, and PII scanning are identical on every tier and can't be relaxed by paying more. See GET /v1/plans.
Send the key from POST /v1/keys in the X-API-Key header; the first FREE_CALLS (default 100) calls bypass payment. After that, unpaid calls get a standard x402 402 Payment Required — any x402 client (@x402/fetch, x402-requests, …) handles pay-and-retry automatically.
Key lifecycle. The account is the identity; the psk_ secret is just a credential pointing at it. If a key leaks, POST /v1/keys/rotate mints a replacement secret bound to the same account — usage history, remaining free calls, and any active plan carry over unchanged (rotation never resets the free tier, so it can't be farmed). The old secret keeps scanning through an optional grace window (grace_seconds, default 900, 0 = dies instantly, max 24 h) so a fleet can switch over without downtime, but during grace it can no longer rotate or revoke — a leaked old secret can't take over the account. POST /v1/keys/revoke (with {"confirm": true}) is the kill switch: key and account die permanently, and the tombstone persists so the dead key keeps failing with a named reason instead of a mystery 401. Rotating the owner key? Update ADMIN_KEY_SHA256 to the api_key_sha256 in the rotate response.
Example: scan an outgoing payment
context.origin is the key input for injection detection: payments prompted by just-read content are the primary prompt-injection exfiltration path and get elevated scrutiny.
payment must name a recipient (pay_to) or a value (amount, or amount_usd). A payment with neither has nothing to screen, so it gets a 400 and is not scanned, recorded, or settled (on a free-tier key it still uses a free call). Fields with the wrong type count as absent, and so do x402 offer names, so send payTo as pay_to and maxAmountRequired as amount.
Example response (replay blocked)
Dashboard
Usage dashboard — GET /dashboard. A single self-contained page where any key holder can see their own scan counts, verdict breakdown, free-tier quota, plan status, and (when approvals are configured) their own approval-decision telemetry. Paste your psk_ key and hit View; the key is sent only as an X-API-Key header to GET /v1/usage (never in a URL, so it can't leak via history, referrers, or server logs), and each key can only ever see its own account. Served with a locked-down CSP (default-src 'none', zero external resources) and rendered exclusively via textContent.
Human-in-the-loop step-up approvals
A flag verdict no longer has to be a dead end. Configure a webhook once and every flag pauses for a human decision:
On a flag, your webhook receives the payment facts (the full pay_to — never truncated) and a one-time decide link. The reviewer opens /approve, checks the address character by character, and clicks Approve or Deny. Approval mints a short-lived (≤5 min) Ed25519-signed override verdict carrying the distinct tag override:allow in the signed message itself — an override can never masquerade as an organic allow, and it is bound to exactly the flagged payment's commitment. Blocks are never approvable; decisions are idempotent, single-token, and recorded in the tamper-evident audit log.
The agent side is two lines with the SDK:
(Python: tollwarden.wait_for_approval(scan, payment=payment) / TollWardenEnforcer(..., accept_overrides=True).)
Why acceptOverrides is opt-in: an agent that holds its own API key could configure the approval webhook to point somewhere it can read, then "approve" its own flags — no human involved. Overrides are only trustworthy when the webhook receiver is out of the agent's reach (your ops channel, not the agent's environment). The enforcement kit therefore refuses override:allow unless the wallet owner explicitly opts in, exactly like allowFlagged.
Decision telemetry — watch yourself drift. In most deployments, human approval decays into a compliance formality approved reflexively — and that decay is invisible in any single observation: a reviewer approving in 8 seconds after three months of 40 looks identical, in one log line, to a reviewer who got calibrated. So TollWarden records the decision latency of every approval (both timestamps are server-stamped) and pairs each approved payment with its later delivery outcome. GET /v1/usage and the dashboard show your own aggregates — recent vs. baseline median latency, approval rate, and how approved payments actually delivered. Recent median shrinking while the approval rate sits near 100% is the legible signature of rubber-stamping; the outcome pairing is what separates that from "the operator got faster because the flags got predictable". Raw aggregates only, no composite score — and the data is visible only to the key that owns it: it never feeds a verdict and never appears in reputation lookups, trust evaluations, or public stats, because a public record of who rubber-stamps is a targeting list.
Disabling. Per key: POST /v1/approvals/config with {"webhook_url": null} — the response carries an advisory reminding you that flags return to advisory-only (nothing pauses, no overrides are minted, and an acceptOverrides wallet has no flag→payment path anymore); pending approvals stay decidable until they expire. Server-wide: APPROVALS=off refuses new configs and opens no new approvals, with the same advisory, while in-flight approvals remain decidable so a mid-flight disable strands nothing.
Delivery outcomes
Every scan check validates the payment — well-formed, un-manipulated. None of them can tell you the seller will actually deliver: a perfectly clean payment to a seller who never ships still fails the agent. The outcome loop closes that gap with measured history instead of accusations:
- x402 delivery is usually synchronous — the resource arrives in the paid response — so the SDK payment-path wrappers (
wrapFetchWithTollWarden/wrap_transport_with_tollwarden) observe it mechanically and report it automatically: paid 2xx →delivered, paid 5xx/second-402 →not_delivered, with status/bytes/latency evidence (opt out withreportOutcomes: false). Settling some other way?POST /v1/outcomesorclient.reportOutcome(scan, ...)directly. - Every outcome is bound to a scan TollWarden performed: the report must present the
scan_id+payment_commitmentpair, one outcome per scan, keyed scans only from the scanning account. Fabricating delivery history requires making real, scanned payments — an anti-Sybil property free-text reports can't have. - Aggregated delivery rates surface in
GET /v1/reputation/:addressand in adeliverycheck on every future scan of that counterparty: low rates and repeated no-ships flag (never block — measured history is still third-party signal, audit H-2 applies). - The denominator is public too: reputation lookups report
scans_seen(non-blocked scans TollWarden performed against the counterparty) andreport_coveragealongside the outcome counts — so selectively reported outcomes read as low coverage instead of passing as a complete record, and a counterparty with scans but zero reported outcomes shows that population instead of reading as "no history". Coverage is informational only (scan counts are client-driven) and never feeds a flag.
Honest limits: this measures that content arrived, not that it was good — quality judgments stay with POST /v1/reputation/report — and it covers digital, synchronous x402 delivery, not physical shipment.
Local development
For contributors and anyone auditing the detectors — runs entirely offline, payments disabled, no wallet needed:
Performance
Measured on the zero-dependency dev server (same handlers as production): 2,000 sequential scans in 1.20 s → 0.60 ms/scan round-trip, including HTTP, JSON parsing, the full check suite, and Ed25519 signing. Deployed latency is dominated by network RTT; on the paid path, the x402 verify/settle round-trip to the facilitator (inherent to x402, identical on any host) dominates everything else.
The hosted service
The production service at https://tollwarden.com is live on Base mainnet, indexed in the x402 Bazaar, registered on x402scan, monitored with a tamper-evident audit chain, and registered as an on-chain ERC-8004 agent (agentId 59586 in the IdentityRegistry on Base — the token's URI resolves back to this service's registration file). Verify it yourself:
Public stats, and who is in them
GET /v1/stats publishes aggregate service numbers (scan totals, verdict split, distinct
agents, self-measured 90-day uptime). The response reports third-party and first-party
usage separately. Keys the operator tags as first-party (our own agents, chiefly the
open-source ecosystem scout that buys real x402 resources twice a day) are counted under
first_party, and the homepage panel shows third_party only. The attribution comes from
per-key usage counters recorded at scan time, so it is authenticated by the key that made
each scan and covers scans made before a key was tagged.
Why publish the first-party number instead of quietly subtracting it? The scout is public and its run count is checkable, so a reader can reconcile the figure themselves. A disclosure sentence is a claim someone has to trust. A number that reconciles is evidence.
Tagging is owner-only (POST /v1/admin/keys/first-party, guarded the same way as
/v1/admin/stats, taking the key hash rather than the key). There is no client-facing
way to set the flag. A caller able to tag itself first-party could remove its own scans
from the public third-party denominator, which is the exact thing the split exists to
prevent.
MCP server
Listed in the official MCP registry as com.tollwarden/tollwarden — or run it directly with zero config:
Twelve tools over stdio: scan_outgoing_payment, scan_incoming_payment, check_counterparty_reputation, report_counterparty, report_payment_outcome (close the loop after settlement — builds measured delivery history), dispute_reputation (wallet-signed rebuttal shown alongside reports — never erases them), mint_api_key, rotate_api_key (leaked-key recovery — fresh secret, same account), check_approval_status (poll a human-in-the-loop approval), get_plans, subscribe_plan, and verify_verdict_attestation (full Ed25519 verification performed locally — pinned key, commitment recompute, expiry, plus the signed pin-evidence record: returns pin_evidence with pin age and named corroboration sources). Defaults to the production service; set TOLLWARDEN_URL to point elsewhere.
Detection defaults (hosted service)
Published for transparency — these are the thresholds your scans are judged against on the Starter tier (plans raise the velocity/spend headroom; nothing can relax the safety checks):
Local dev configuration for contributors is documented in .env.example.
Architecture
Design notes: verdicts aggregate worst-first (any block ⇒ block); risk_score is severity-based with compounding for multiple independent findings; the detection core has zero runtime dependencies, so the full suite runs with node --experimental-strip-types and no install.
Security & custody model
- Non-custodial, advisory-only. TollWarden sees payment metadata — it never signs, holds, or routes funds. The settle/refuse decision stays with your wallet/facilitator (optionally enforced via signed verdicts).
- Detected secrets are redacted in responses (first 4 + last 2 chars) and never persisted. Scan payloads are processed in memory; only nonce fingerprints, hashed purchase fingerprints (kept for the duplicate-purchase window), pins, velocity counters, reputation reports, and API-key usage are stored.
- Threat model — what holds against whom. Two attacker tiers matter, and they are not the same:
- A confused agent — a prompt-injected model still making tool calls through its normal harness — is what TollWarden is built for. Detection catches manipulated payments; velocity and spend caps, first-contact limits, and merchant pinning are computed server-side from observed scans, so they bind on every scanned payment no matter what the model believes; and
guardSignercloses the skip-scanning hole at the wallet, because the wrapped signer recomputes the commitment from the typed data actually being signed and refuses without a fresh allow-verdict — with optional local policy (recipient allowlist + spend caps) bounding even approved payments. (context.originis self-declared, so an agent that lies about provenance weakens the content tier — the caps, pinning, and signer enforcement don't depend on it.) - A compromised process — attacker code running where the wallet's private key is readable — defeats any in-process wrapper, including our enforcer: it can sign with the raw key and never touch TollWarden. TollWarden is non-custodial and never holds your key, which also means it cannot protect a key your own process exposes. For this tier the mitigation is key isolation — keep the signer in a separate process, KMS, or hardware, and run the enforcer at that boundary so nothing inside the agent process can produce a signature. (The auto-minted
psk_API key is a scan credential only: it can spend your TollWarden quota, never your funds.)
- A confused agent — a prompt-injected model still making tool calls through its normal harness — is what TollWarden is built for. Detection catches manipulated payments; velocity and spend caps, first-contact limits, and merchant pinning are computed server-side from observed scans, so they bind on every scanned payment no matter what the model believes; and
- Honest limitations: injection detection is heuristic, not semantic; novel phrasings can evade the content tier (our public eval corpus tracks known gaps, and the prompt-injection benchmark publishes its results along with what our own corpus can't show). The reputation registry accepts unauthenticated reports and should be treated as a signal, not ground truth.
Contributing
Issues and PRs welcome — particularly new detector patterns (with tests) and SDK improvements (the TS and Python clients live in sdk/ and sdk-python/). Run npm test before submitting; every detector change needs a covering test. By submitting a contribution you agree to license it to TollWarden, LLC under the terms of the LICENSE, and you grant TollWarden, LLC the right to relicense the contribution as part of the Licensed Work (including under the Change License).
License
Business Source License 1.1 (source-available). You can read, run, modify, and self-host TollWarden, and use the SDKs/MCP server against the hosted service — including inside commercial products. What the license restricts is offering the code itself as a competing paid payment-security service. Each version converts to Apache 2.0 four years after release. Versions ≤ 1.4.0 remain MIT.
Source: README.md at commit ca88d1c
Tools
0Version history
2- v1.6.0LatestSep 23, 2026
- v1.5.0Sep 19, 2026
Score
40% of the applicable checks met
How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.
- Reached in the last 30 days25 pts
Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.
- Auth declared10 pts
Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.
- Tools listed15 pts
Answer tools/list without a credential, or ship a package that starts in a sandbox without one.
- DescriptionRequired15 pts
- Logo10 pts
- Documentation15 pts
- License10 pts
Declare a license in the repository or the registry entry.