
TollWarden
com.tollwarden由 tollwarden.com 發布v1.6.0更新於 Oct 24, 2026
Screens x402 payments before settlement for injected payee addresses and sellers who never deliver.
概覽
在結算前篩檢 x402 小額付款,對被注入的收款地址與不交貨的賣家回傳允許、標記或封鎖的判定。
- 可以做什麼
- TollWarden 是針對 x402 代理的諮詢式、非託管付款防火牆。它提供對外付款與收到的 402 報價的掃描端點,以及交易對手信譽查詢與檢舉、交付結果記錄、金鑰生命週期管理和可選的人工審批。檢查項目涵蓋付款中繼資料中的隱私與金鑰外洩、nonce 重放、重複購買、超額付款、提示注入觸發的付款、資源 URL 風險以及實測交付歷史。判定會附上逐項原因與 Ed25519 證明。
- 適用情境
- 當自主代理透過 x402 付款,且你希望在結算前防範被竄改的收款地址、被重放的授權、虛高價格或交付紀錄差的賣家時使用。它鎖定在付款前會讀取不可信網頁或工具內容的代理。
- 執行需求
- 以本機 stdio 程序在桌面執行。README 展示了 npm 與 pip 用戶端套件及框架整合,服務透過 HTTP 存取。API 金鑰由金鑰端點簽發,並透過 X-API-Key 標頭傳送;每個金鑰前 100 次呼叫免費,之後透過 x402 付費。清單中未宣告任何環境變數或標頭。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 TollWarden,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
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.
來源:README.md,提交 ca88d1c
工具
0版本歷史
2- v1.6.0最新Sep 23, 2026
- v1.5.0Sep 19, 2026
評分
已滿足適用檢查項的 40%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分
請在儲存庫或註冊中心條目中宣告授權條款。