x402 SDK flaw let malicious APIs drain AI agent wallets
The x402 payment SDKs shipped with no spending limit, so one hostile API response could empty an agent's wallet. Fixed versions and the settings to apply.
By Yaali. October 25, 2026, 6 min read, AI security, Vulnerabilities, Patching.
Security firm V12 published 25 findings in the x402 payment SDKs on October 22, three of them rated critical. The most serious for anyone running AI agents: the default client applied no spending limit at all. A malicious or compromised API could answer a request with a payment demand for the wallet's entire balance of a supported token, and an agent that signs payments automatically would sign it.
The x402 maintainers fixed all three critical flaws in the August 18 releases (TypeScript @x402/* 2.23.0, Python x402 2.20.0, and the Go module released the same day). No CVE or GitHub advisory was issued, so npm audit and PyPI show nothing for the old versions. If you built an agent that pays for APIs with x402 before mid-August and have not upgraded since, nothing has warned you.

How the 402 payment flow works
x402 revives the HTTP status code 402 Payment Required so that a client, often an AI agent, can pay for an API call in stablecoins without an account or API key. The agent requests a resource. The server replies 402 with a PAYMENT-REQUIRED header: base64-encoded JSON listing one or more payment options in an accepts array. Each option names the scheme, the network (such as eip155:8453 for Base), the token asset, the amount, the recipient payTo, and a timeout.
The client picks one option, signs a payment authorization with the agent's wallet key, and repeats the request with that signature in a PAYMENT-SIGNATURE header. On EVM chains the usual mechanism is an EIP-3009 transferWithAuthorization signature, which lets a third party submit the transfer on the payer's behalf. The server, or a facilitator service it uses, verifies the signature, settles the transfer on chain, and returns the content with a PAYMENT-RESPONSE header.
What the default client got wrong
Before 2.23.0, the documented default setup chose accepts[0], the first option the server offered, and built the payment from the server's values. Amount, token, recipient and network all flowed straight into the signing code. There was no per-payment cap and no total budget. V12 also found that the HTTP decoding path parsed the payment requirements without calling the schema validator, although it notes that validation alone would not help, because a well-formed request can still ask for far too much.
V12 described the attack in two steps. A hostile server first charges a small, normal-looking fee. That payment reveals the agent's wallet address, so the server can look up its balance on chain. On the next request it asks for the full balance, the agent signs, and the attacker submits the signed transfer. The only limits were the balance itself and any restrictions enforced by an external signer.
The risk applied to agents that sign automatically and whose application set no payment policy of its own. A paywall in a browser, where a person approves each payment, was not exposed in the same way.
The other two critical findings matter for the same deployments:
- The TypeScript Solana facilitator checked the token program but not the instruction type. An
ApproveCheckedinstruction differs fromTransferCheckedonly in its first byte, so a token approval that moves no funds passed as payment. V12 called it "a complete payment bypass".@x402/svm2.0.0 to 2.22.0 are affected; Python and Go already checked the type. - The client signed Sign-In-With-X (SIWX) login messages using the domain and URI from the server's 402 response, without comparing them to the host it had actually contacted. A malicious API could relay another site's login challenge and sign in there as the agent's wallet.
V12 reported the critical and high findings to the x402 team on August 12. No exploitation has been reported; V12 ran its tests locally.
What to do

Upgrade. In TypeScript, move every @x402/* package to 2.23.0 or later; the current release is 2.28.0, published September 29. The packages release in lockstep, so check that no older @x402/svm or @x402/extensions copy is left behind as a transitive dependency: run npm ls --all @x402/core @x402/svm @x402/extensions @x402/fetch and read the lockfile. In Python, upgrade x402 to 2.20.0 or later (current is 2.25.0). Upgrade the Go module to a release from August 18 or later.
Check what the new default does. From 2.23.0 the client only pays in assets the SDK recognises as US dollar stablecoins, such as USDC, and caps each payment at $1. Payments above the cap or in other tokens are filtered out before the agent signs. You change this with spendControls in x402Client.fromConfig():
maxAmountPerPayment: '$5'raises the USD cap. Set it to the largest single fee you actually expect to pay.allowedAssetsopts in other tokens, each with an optional per-asset cap in atomic units (for example'2000000'for 2 USDC with 6 decimals).spendControls: falseremoves every limit. The SDK documentation reserves it for flows where a person confirms each payment and for tests. Search your code for it; an unattended agent should never have it.
In Python the same options live in spend_controls, with max_amount_per_payment defaulting to "$1".
Add a total budget yourself. The built-in controls cap each payment, not the total. A hostile server can still charge $1 per call for as long as the agent keeps calling. Track cumulative spend per session or per day in an onBeforePaymentCreation hook and return { abort: true } once it is exceeded. Use a policies filter to accept only the payTo addresses of the services you meant to pay, and register only the networks you intend to use. Keep only a working balance in the agent's wallet.
If you cannot upgrade today. Register a policy that drops any option above a set amount: registerPolicy in TypeScript and Go, or register_policy with the built-in max_amount() policy in Python, which takes the limit in atomic units. Also update any direct call to createSIWxPayload: from 2.23.0 it takes a third argument, the final URL of the 402 response after redirects, and refuses to sign on a mismatch. The @x402/fetch and @x402/axios wrappers pass it for you.
Check whether you were already hit. Pull the outgoing transfers from each agent wallet on a block explorer and compare them with what the agent should have paid: look for any single transfer far above your normal fee and for recipients you do not recognise. If your agent logs payloads in onAfterPaymentCreation, compare the signed amounts with your expected prices. For SIWX, review sign-ins on any service that accepts the agent's wallet as a login. If you run a Solana facilitator on @x402/svm before 2.23.0, reconcile recorded settlements with the chain (solana confirm -v <SIGNATURE>) and look for "paid" requests whose transaction holds an approval instead of a transfer.
The wider lesson for agent builders
The fix arrived without an advisory, so dependency scanners never flagged it. Read the changelogs of any SDK that signs payments for your agents, and treat the amount in a 402 response as input from an untrusted party.
We review agent payment flows and wallet handling as part of our AI-native systems work and in red team code review. If you want to know how much one of your agents could be made to pay, open the chat and Yaali, our AI agent, will pass your question to an engineer.
Sources: The Hacker News, V12 research, Safeguard, DEV Community analysis, @x402/core on npm, x402 on PyPI.
Read next
- Claude flags 493 attack surface issues in open source
- Claude Agent SDK flaw: a session ID that runs commands
- Langflow MCP flaw under attack: a backdoor in settings
Back to the blog, or tell us about your system in the chat. Yaali, our AI agent, answers first and brings in an engineer.