x402 Integration Mistakes and How to Set Spending Limits
Most x402 integration mistakes fall into three groups: the client signs whatever terms the server sends, the seller serves without binding payment to one resource, and limits exist only per payment. Set limits in layers: SDK caps and asset allowlists, an expected-price check before signing, and a total budget enforced onchain by the session key.
What the disclosure found
The security firm V12 published a review of the x402 codebase on October 22, 2026, reporting 25 findings: 3 critical and 13 high reported on August 12, then 9 medium and low submitted on September 9. The three critical issues were a default client that picked the first payment option a server offered with no spending policy, a Solana facilitator that accepted an approval instruction (which moves no tokens) as a transfer, and a Sign-In-With-X client that signed login challenges relayed from other sites.
The fixes landed in mid-August. V12 dates them to August 20; the npm registry shows @x402/fetch 2.23.0 published on August 18, and The Hacker News and a Cloud Security Alliance note (October 26) give 2.23.0 as the minimum safe TypeScript version. The CSA note also points out that no published advisory covers these findings, so npm audit and Dependabot would not flag an old version. Neither V12 nor the x402 Foundation has reported exploitation.
A Vietnamese short on the EricCyberSecurity channel (November 1, 2026) summarised the disclosure as a lesson about spending limits. This page does not reproduce the video. It groups the classes of mistake the disclosure exposed and shows where each limit should live.
The mistakes, grouped
Twenty-five findings are hard to act on one at a time. Grouped by who made the mistake, they reduce to four habits.
Client: letting the server choose the terms
The core error is treating the 402 response as instructions rather than as an offer. The server sets the amount, the asset, the network, and the recipient. A client that selects accepts[0] and signs has handed all four decisions to the counterparty. Variants of the same habit: skipping schema validation on decoded payment requirements, signing a sign-in message whose domain and URI were copied from the response instead of compared with the host actually contacted, and turning spend controls off in production because they were off in tests.
Seller: serving without binding payment to delivery
On the selling side the mistakes let a buyer get more than was paid for. Two faults do most of the damage: settling the same payment identifier more than once, and treating a payment as proof of entitlement to anything rather than to the one thing it bought. Both are catalogued, with the research behind them, in the six attacks on x402 payments. What belongs here is the build-side fix: consume each identifier exactly once in storage every serving process shares, check entitlement per route, send Cache-Control: no-store on paid responses so a CDN cannot hand the body to the next unpaid caller, and wait for settlement before delivering anything expensive.
Facilitator: trusting a verdict nobody checked
The Solana finding is a reminder that a facilitator returning valid is a claim, not a fact. V12 found the approval and transfer instructions share a data layout and differ only in their first byte, and the verifier did not check it. Sellers depending on a facilitator should pin its version (@x402/svm 2.23.0 or later for Solana verification) and, for anything valuable, confirm receipt onchain independently.
Operations: no inventory, no alerts
Because no advisory exists, the only way to know an agent runs a vulnerable client is to inventory x402 packages, including transitive ones, and compare versions by hand. Search the codebase for spendControls: false and treat each hit as a disabled control that needs an owner. Log every 402 challenge received, so a price change shows up as an event rather than a surprise on the statement.
Setting spending limits in layers
No single limit covers the failure modes above. The current TypeScript client gives three in-process layers, and a fourth has to sit outside the process.
Layer 1, spend controls. In @x402/core (checked at 2.30.0) the client allows only assets its scheme recognises as USD stablecoins and caps each payment at $1 by default. maxAmountPerPayment raises or lowers that ceiling; allowedAssets opts in other tokens with an atomic-unit cap per asset. Network scoping is separate: register only the networks the agent should pay on, such as eip155:8453 for Base. Passing spendControls: false disables all of it. Note that applySpendControls can only be called once per client; a second call throws rather than layering.
Layer 2, policies. A PaymentPolicy filters the offered options after spend controls and before selection. Use it for rules about who gets paid, for example dropping any option whose payTo is not the address already pinned for that service.
Layer 3, a pre-sign check against expected price. The cap answers whether a payment is affordable, not whether it is right. The CSA note cites an issue report in which a 0.01 USDC price was inflated to 0.10 USDC and signed in three of three testnet sessions, all under the cap. An onBeforePaymentCreation hook can compare the requested amount with an operator-controlled quote for that resource and abort on a mismatch.
Layer 4, a total budget the agent cannot touch. Every SDK control is per payment. A loop of payments just under the cap passes each check. The total belongs in the account itself: a session key registered with a per-period cap and a short expiry, holding a float sized to the loss you are willing to accept. A compromised process cannot raise a limit enforced by the contract that validates its signatures.
Choosing the numbers
Set per-payment caps per resource class, close to the expected price, rather than one global figure. A cap ten times the normal price lets a tenfold increase through unnoticed.
Size the period cap from the other direction: the largest loss acceptable before a human looks. That number, not expected usage, is what the session key should enforce.
Treat the first payment to any new seller as special. With no history there is nothing to validate the price or recipient against, so require approval or keep it deliberately small, then store what was approved as the baseline.
Large payments that need a person should be approved through a channel the agent cannot reach. An approval code delivered to an inbox the agent reads is not an approval.
Frequently asked questions
- What did the V12 x402 disclosure find?
- V12 reported 25 findings in the x402 codebase on October 22, 2026: 3 critical, 13 high, and 9 medium or low. The critical ones were an uncapped default client, a Solana facilitator that accepted an approval as a payment, and a sign-in client that signed challenges relayed from other sites. All three were fixed between August 18, when the npm packages went out, and August 20, the date V12 gives.
- Does the x402 client SDK have a default spending limit?
- Current @x402/core clients apply spend controls by default: only recognised USD stablecoins, capped at $1 per payment. The cap is adjustable through maxAmountPerPayment, other tokens can be allowed per asset, and spendControls: false removes the controls entirely. The cap is per payment only, and it does not compare the price with what the resource should cost.
- Is a per-payment cap enough to protect an agent?
- No. It stops one oversized charge but not many charges just below the cap, and it does not notice a price inflated within the cap. Pair it with an expected-price check before signing and a total budget enforced by a session key onchain, which the agent process cannot raise even if it is compromised.
- What should an x402 seller do to avoid serving content for free?
- Bind each payment to the specific resource and price, accept each payment identifier once, and send Cache-Control: no-store on paid responses so shared caches cannot replay them. Pin the facilitator version, and for high-value items wait for settlement and confirm receipt onchain before delivering, rather than trusting a verification result alone.