
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 分
请在仓库或注册中心条目中声明许可证。