Three Criticals, Zero Advisories: The x402 Agent-Payment SDK Shipped Its Wallet-Drain Fix as a Feature
On 22 October 2026 the security firm V12 published a write-up of 25 vulnerabilities it found in x402, the HTTP-native payment protocol that lets software — in practice, autonomous agents — pay for an API call inline: the server answers 402 Payment Required, the client signs a payment payload, the request is retried with the payload attached. Three of the 25 were rated critical. All three were fixed in August. None of them produced a security advisory, a CVE, or a release note that says so.
That second half is the story. The post itself was read first-hand, and so was the upstream repository: every fix commit, changeset, package-version bump and the complete published advisory list for x402-foundation/x402 were pulled directly from GitHub for this briefing.
Critical one: an approval that counted as a payment
The TypeScript Solana facilitators for the v1 and v2 exact schemes checked that a transaction called an allowed token program, then parsed its third instruction as TransferChecked — without ever checking that the instruction was a TransferChecked. The parser decoded bytes into fields and trusted the name on the function.
Solana's ApproveChecked has the same data layout. The two differ in one byte: 0c versus 0d. An approval grants a delegate permission to spend; it moves nothing. Because the account layouts also line up, the account naming the delegate was read as the payment destination — so an attacker could put the merchant's own expected token account there and satisfy the destination check while the amount and token checks passed too. V12 reports that its local test, using the real token encoders and parser, returned isValid: true for the approval, and that the settlement path also skipped its post-settlement payment check on that route.
The result is a complete payment bypass in the direction nobody instruments: the merchant delivers a paid resource and receives nothing, the facilitator pays the transaction fee, and the attacker needs a valid token account but no balance at all. Repeat it and you drain the seller's inventory and the facilitator's fee budget simultaneously.
Critical two: a login signature for a site the agent never visited
The Sign-In-With-X extension lets a client authenticate with its wallet. On Ethereum it builds a Sign-In with Ethereum message naming the website, the wallet address and a one-time challenge. The automatic wrapper signed the domain and uri fields supplied by the server in the 402 response, without comparing them to the site the client had actually contacted.
So a hostile data.example could fetch a fresh login challenge from app.example in a session it controlled, hand that challenge to the agent inside its own 402 response, collect the signature and replay it to app.example as the client's wallet. Every check on the receiving side passes, because every check on the receiving side is correct: the message names that site and carries a genuine signature. The missing check belonged on the client, before signing — which is exactly the class of failure that keeps recurring once an automatic signer sits where a human confirmation dialog used to be.
Critical three: the documented default had no ceiling
The documented default client setup enforced neither a per-payment limit nor a total budget. It took accepts[0] — the first option the server offered — and passed the server's amount, token, recipient and network straight into the signing code. V12's observation that the HTTP decoding path also parsed payment requirements without calling the existing schema validator is secondary; as the post puts it, a correctly formatted request can still ask for far more money than the client intended to pay.
With an automatic signer and no application-supplied policy, a single hostile 402 response could obtain authorization to drain the wallet's entire balance of a supported token, with no separate approval prompt. An earlier small payment reveals the wallet address, which lets the server look up the balance before naming its price.
The fixes are real. The disclosure is not.
V12 reported the 3 critical and 13 high findings on 12 August 2026 and states the criticals were fixed by 20 August; a further 9 medium and low findings followed on 9 September. The repository confirms the remediation precisely, and the commits are worth naming because of how ordinary they look:
16a23d098, 12 August 2026 14:43:53Z — “fix(ts): require TransferChecked discriminator in svm exact (#3132)”. Four files. Its changeset is a patch-level bump of@x402/svmwhose entire text reads “Added validation guard for TransferChecked.” That is the payment bypass.c2612d31e, 13 August 2026 13:42:00Z — “fix(ts): bind SIWX client challenge to the request origin (#3133)”. Thirteen files, a minor bump across@x402/extensions,@x402/core,@x402/fetchand@x402/axios, and a breaking API change:createSIWxPayload()gained a required third argument, the final URL of the 402 response, and “signing is refused when challengedomainoruriorigin does not match that URL's origin.”4f587236e, 13 August 2026 15:37:17Z — “feat(TS): spend controls (#3124)”, 146 files. Python followed on 18 August (656437e06) and Go eleven minutes later (5246387a03).
Checking the shipped behaviour rather than the commit title matters here, and it is better news than the headline suggests: spend controls are on by default. The current buyer quickstart documents a default maxAmountPerPayment of "$1" on recognised USD-pegged assets, requires explicit opt-in for non-default tokens, and offers spendControls: false / DisableSpendControls() for anyone who wants the old behaviour back. The remedy for a critical wallet-drain was not a configuration option bolted on for the diligent — it was a change of default. Good engineering.
It was also labelled feat.
All three fixes reached users in the 18 August 2026 release train: TypeScript packages 2.22.0 → 2.23.0, the Python x402 package 2.19.0 → 2.20.0, Go 2.22.0 → 2.23.0. The Solana payment-bypass fix therefore sat in main unreleased for six days, which is unremarkable. What is remarkable is what the repository has not published: x402-foundation/x402 carries exactly two GitHub security advisories, GHSA-3j63-5h8p-gf7c (20 August 2025) and GHSA-qr2g-p6q7-w82m (6 March 2026), and neither covers any of the V12 findings. There is no GHSA and no CVE for a complete payment bypass, a cross-site signature relay, or an unlimited default signing authority in an SDK whose entire purpose is to let software spend money unattended.
What that costs, concretely
Our reading, not the researchers': the absence of an advisory converts a solved problem into an unsolvable one for everybody downstream. Treat the three remediation channels in turn. npm audit and Dependabot match installed versions against advisory databases; with no GHSA there is nothing to match, so an agent pinned to @x402/core 2.22.0 raises no alert and will keep signing whatever a server asks for. Release notes would be the fallback, except the fixes are described as a patch-level “validation guard,” an origin-binding change and a feature addition — accurate labels, none of which reads as your agent could currently be drained. That leaves reading the commit log of a dependency, which is not a control any organisation can staff across its dependency tree.
This is the third time this month the same shape has appeared on this site, and the pattern is now worth naming rather than noting. Signal's RingRTC CVE-2026-113380 was fixed under the message “Ensure calls terminate completely in corner cases” 46 days before a CVE appeared. OpenCode's MCP OAuth RCE was fixed with no CVE, no advisory and no scanner alert. Node's brotli out-of-bounds write sat patched in upstream main for eight months while every runtime vendoring the C library compiled the bug. In each case the code was fixed quickly and competently; in each case the machine-readable claim that the old version is dangerous was never created, so the fix propagated only to users who happened to upgrade. x402 adds the aggravating detail that the artefact at risk is a funded wallet.
Why agent payments concentrate this risk
An x402 client is a wallet that answers HTTP. The threat model it needs is not the one SDK authors usually build for, because the counterparty is not a user's bank — it is an arbitrary server the agent was told to fetch data from. Every one of V12's three criticals is a boundary that exists implicitly in a human checkout flow and had to be written down explicitly here: a human notices when the amount is their whole balance, notices when the login prompt names the wrong site, and notices when a merchant ships without charging. Remove the human and each of those becomes a line of validation code that somebody has to remember to write.
We have covered the attacker side of this surface twice — indirect prompt injection steering agents into crypto payments and denial-of-wallet as a commodity botnet service. Both assumed the payment path itself was sound and attacked the instruction that reached it. V12's findings are the other half: the path itself would honour a malicious instruction with no injection required, because the server's number was the client's number.
What to do
- Pin to the 18 August 2026 release or later: TypeScript 2.23.0+, Python 2.20.0+, Go 2.23.0+. Below those versions you have the unlimited-signing default, the unvalidated Solana instruction parse and the unbound SIWX signer. No tool will tell you this; check your lockfile by hand.
- Do not disable spend controls, and do not treat the $1 default as a policy. It is a backstop. Set
maxAmountPerPayment, a total budget, and an explicit allow-list of assets, networks and recipients for every funded agent — the default protects against the unbounded case, not against an attacker who prices the request just under your ceiling and repeats it. - If you run a facilitator, verify the instruction discriminator yourself. The reusable lesson from the
ApproveCheckedfinding generalises beyond x402: a parser named for one instruction type will happily decode another with the same field layout. Check the type byte before the fields, in your own code too. - Fund agent wallets like hot wallets, not like accounts. The practical blast radius of all three criticals is bounded by the balance sitting behind the automatic signer. Keep it at operating float, top it up on a schedule, and put the reserve behind a signer that cannot be reached by an HTTP response.
- Add “no advisory” to your dependency risk model for agent infrastructure. For the small set of packages that hold credentials, keys or spending authority, subscribe to the repository's commit feed rather than relying on advisory-driven alerting. That is an unsatisfying recommendation and we make it reluctantly, but on current evidence it is the only control that would have fired here.
Verification note: the vulnerability mechanisms, the attacker preconditions, the disclosure dates (12 August and 9 September 2026 reports, criticals fixed by 20 August) and the quoted remediation guidance are taken from V12's 22 October 2026 post by Renwa, read first-hand in full. Every repository claim was verified directly against x402-foundation/x402 via the GitHub API during this run: the three fix commits and their SHAs, timestamps, file counts and changeset contents; the createSIWxPayload signature change quoted from the changeset; the 18 August version bumps in typescript/packages/*/package.json, python/x402/pyproject.toml and go/constants.go; the current default maxAmountPerPayment of “$1” and the opt-out switches quoted from docs/getting-started/quickstart-for-buyers.mdx on main; and the complete repository security-advisory list, which returned exactly two entries (GHSA-3j63-5h8p-gf7c, 20 August 2025; GHSA-qr2g-p6q7-w82m, 6 March 2026), neither matching these findings. V12's test results — the isValid: true approval, the SIWE message generated for an unrelated domain, the malformed requirements reaching the selector — are the researchers' findings, attributed and not reproduced here; V12 states its own test did not submit a live transaction. The scanner-blindness argument, the comparison to the RingRTC, OpenCode and brotli cases, and the funding guidance are our analysis, not statements by V12 or the x402 project. No payment was signed, no facilitator or wallet was contacted, no exploit or proof of concept was written, obtained or run, and no third-party system was tested.
Sources:
- V12 — “25 Vulnerabilities in the x402 Protocol: Inside Three Critical Findings” (Renwa, 22 October 2026; mechanisms, disclosure timeline, remediation guidance)
- x402-foundation/x402 — fix(ts): require TransferChecked discriminator in svm exact (#3132), 12 August 2026
- x402-foundation/x402 — fix(ts): bind SIWX client challenge to the request origin (#3133), 13 August 2026
- x402-foundation/x402 — feat(TS): spend controls (#3124), 13 August 2026
- x402-foundation/x402 — published security advisories (two entries, neither covering the V12 findings)
- x402 docs — Quickstart for buyers (default $1 per-payment cap, allowed-asset opt-in, spend-control disable switches)
- al-ice.ai — Signal RingRTC CVE-2026-113380, fixed 46 days before disclosure
- al-ice.ai — OpenCode's silent fix for an MCP OAuth RCE
- al-ice.ai — Node's brotli RCE and the eight-month-old upstream patch
- al-ice.ai — Indirect prompt injection tricks AI agents into crypto payments
- al-ice.ai — x47.c botnet burns victims' AI credits: denial-of-wallet goes commodity