Skip to content

Security model

tx402 signs payment authorizations on behalf of software that may be running unattended for hours. That makes its security properties load-bearing in a way an ordinary HTTP client’s are not. This page states them plainly, including the limits.

It is buyer-side and non-custodial. tx402 never holds funds and never has custody of a key it did not receive from you. It calls no facilitator endpoint — /verify and /settle are the merchant’s business, not the buyer’s — so the buyer’s attack surface is the merchant’s challenge, the RPC endpoints it reads balances from, and its own configuration.

The threat that shapes the design is the one the target users actually face: an autonomous agent running a long loop, reading untrusted content, and able to make HTTP requests. If that content can induce a payment, the agent’s wallet is only as safe as the guardrails between the request and the signature.

Policy evaluation and the atomic budget reservation both complete before any signer is invoked, on every attempt. A refused request produces zero signatures — the signer is never called, so a hardware wallet never prompts and a KMS never records a use. The test suite asserts a signer call count of zero for every policy, planning, liquidity, and chain-identity refusal.

Each paid attempt produces exactly one authorization with a cryptographically fresh 32-byte nonce, and no authorization is ever transmitted twice. A re-challenge re-plans and re-signs from scratch rather than reusing anything.

Lifetime is min(60 seconds, merchant's stated bound) and can never exceed the merchant’s own. The bound is computed after the message that it constrains exists, so it is true by construction rather than by timing.

Before the external signer is invoked, tx402 re-derives the approved plan and asserts every field of the payload against it — chain, token contract, domain, payer, recipient, amount, validity window, nonce length. On Solana it decodes the serialized transaction rather than inspecting the builder’s own objects, so a construction bug is caught rather than agreed with.

Alongside that, the signer receives a human-readable presentation: domain, asset, atomic and decimal amount, recipient, network, expiry, and request hash. A hardware wallet can show a person what they are approving.

A paid retry that would follow a redirect to a different origin fails before the signature is transmitted. Same-origin redirects are not followed either in v0.1 — see ADR-014 for why, and what it would take to change.

Signatures, private keys, complete signed transactions, and authorization payloads are never written to the event stream, never embedded in an error, and never serialized. toJSON / to_dict on a tx402 error deliberately omits the cause and the traceback, which is where a URL with credentials would otherwise appear.

This is tested rather than asserted: the suite seeds a real secret into every input the request path touches — signing key, bearer token, query credential, body, settlement id — then searches the entire serialised event stream for each one, in hex, base64, and base64url, on the success path and on the refusal path.

The PAYMENT-REQUIRED header is decoded under hard limits: strict base64, ≤64 KiB decoded, JSON depth ≤16, duplicate keys rejected, ≤32 requirements. The resource URL’s origin is validated against the URL you requested, and the challenge is bound to the method tx402 itself issued rather than to one the challenge claims.

Networks, token contracts, decimals, and RPC endpoints come from a release manifest that is Ed25519-signed and verified offline at client construction, against keys compiled into the package. A tampered manifest fails construction rather than routing a payment to an attacker’s contract.

Stated plainly, because a security page that only lists strengths is not useful.

  • A compromised process. A key in your process memory is readable by anything that can read your process. tx402 reduces how often it is needed, not what an attacker with code execution can do. Use an external signer.
  • A malicious merchant charging what it said it would. tx402 enforces your caps and the protocol’s rules. If you allow $0.10 and the merchant asks for $0.10 for nothing, you pay $0.10 for nothing. Caps bound the damage; they do not judge value.
  • Settlement outcomes. tx402 does not call the facilitator, so it knows what the merchant tells it. AmbiguousPaymentError exists because sometimes the merchant tells it nothing.
  • A hostile RPC endpoint’s data, beyond chain identity. tx402 proves the chain, caps the time, and fails over. It does not independently verify a returned balance.
  • Cross-process budget. The default ledger is in-memory and per-client. Two processes have two budgets, and the hourly window resets on restart (ADR-007).
  • Anything after v0.1’s scope: no cross-chain swaps, no asynchronous settlement, no streaming payments.

Use GitHub Private Vulnerability Reporting on neogeeks/tx402. Please do not open a public issue for a security problem. The full policy, including scope and what to expect, is in SECURITY.md.