The quote was the attack.
Nothing was stolen, as far as anyone has reported. Security firm V12 reviewed the official x402 SDKs — the libraries agents use to pay for API calls — and found 25 flaws, three of them critical. In one, the server wrote the bill and the client paid it, whatever it said. All three were fixed in August, quietly: no CVE, no advisory, and nothing in npm audit for the versions that had them.
What V12 found
V12 reported the three critical and thirteen high-severity findings to the x402 team on August 12, 2026, and nine medium and low ones on September 9. The report went public on October 22. Only the three critical findings are described; the other 22 are counted, not detailed.
The documented default setup paid the first supported option the server listed, with no per-payment limit and no total budget. The server chose the amount, the token, the recipient and the network. A hostile API could ask for the wallet's whole balance of a supported token — and a small earlier payment tells it which wallet to look up first.
Fixed: Spend controls (PRs #3124, #3154, #3156): by default, $1 per payment and recognised US-dollar stablecoins only.
The facilitator checked that the transaction called an allowed token program, then decoded the instruction as a TransferChecked without checking its type. ApproveChecked has the same layout and differs only in its first byte, so the approval's delegate was read as the payee. The attacker needs a token account but no balance, and the facilitator pays the fee. The merchant delivers and receives nothing.
Fixed: An instruction-type check (PR #3132), in @x402/svm 2.23.0. The Python and Go SDKs already had it.
The automatic wrapper copied the domain and uri from the server's 402 response into a sign-in message and signed it, without comparing them with the site the agent had contacted. A server could relay a fresh login challenge from another site and take over that session there. The target's own checks pass: the message names it and carries a genuine signature.
Fixed: Origin binding (PRs #3133, #3192, #3193): the client refuses unless the message names the site that sent the 402.
The first one is the one worth sitting with. Nothing about it is a parsing mistake. The client did what it was built to do — read the 402 response, take the first option, sign — and the response came from the party being paid. A price the server supplies is a claim, not a fact, and the default client treated it as a fact.
What the researchers concluded
V12 framed the fix as policy rather than parsing: requirements the server sends must be validated, but validation alone does not fix it, because a correctly formatted request can still ask for far more money than the client meant to pay. The report’s sentence on where that leaves an application is the one to keep:
“Optional policies only protect applications that actually enable them.”
V12 · Oct 22, 2026
The x402 Foundation’s fix reads that sentence correctly. The August releases did not just add a spending option; they turned a cap on by default — $1 per payment, recognised stablecoins only — so an application that never thought about budgets now has one. An application has to pass spendControls: false to lose it.
The disclosure is the weaker half. The changelogs list the fixes without calling them security fixes, no advisory or CVE was filed, and, per The Hacker News, npm audit returns nothing for the last TypeScript releases before the fixes. A team that relies on those tools to tell it when to upgrade was never told.
What a guard in the signing path does with each
We rebuilt each critical finding as its attack and ran it through the published wormhole-x402 package, version 0.9.9 from npm, with throwaway keys and no network, beside an honest control for each. Two of the three are refused by default. The third is not, and it is the more important result. On November 1 we released 0.10.0, which closes the two gaps 0.9.9 left; the second run below is that release, installed from npm.
On 0.9.9 the payment matched its quote exactly, because the quote was the attack. Conformance compares what the agent signs with what the server asked for; when the server is the attacker there is nothing to disagree with. The same check refuses the reverse — an honest $0.01 quote and a payment for the balance — with X402-101 and X402-102. 0.10.0 adds a spending policy the quote cannot set. With a $1 cap per payment, the whole-balance payment is refused with X402-501 before the wallet is asked, and a $0.01 payment still signs.
Refused both where the agent signs (inspectPayment) and where a merchant or facilitator checks the payment (verify): “payment contains a control-transferring instruction: ApproveChecked — delegates spending authority over the account.” The guard reads the byte the facilitator skipped. A real TransferChecked in the same transaction is allowed.
Both guarded signers, EVM and Solana, refuse the relayed sign-in by default: they sign only payments they can check, and a login is not one. On 0.9.9, an application that turned message signing on, to use Sign-In-With-X at all, got no site check, and the relayed login was signed. 0.10.0 binds a sign-in request to the site the agent contacted, by domain and URI: the relayed login is refused with X402-601, and the site's own login still signs.
wormhole-x402 0.9.9 (npm) · run Oct 31, 2026 · local, throwaway keys 1 a hostile API quotes the wallet's whole 25,000 USDC, to itself the payment that matches the quote ............... allow control: $0.01 quoted, the balance signed ........ refuse X402-101, X402-102 2 Solana: ApproveChecked where the TransferChecked should be inspectPayment, as the agent signs ............... refuse X402-006 verify, as a merchant or facilitator checks ...... refuse X402-006 control: a real TransferChecked .................. allow 3 a login for app.example, handed to an agent that called data.example EVM signer, default .............................. refused Solana signer, default ........................... refused either signer, message signing turned on ......... signed
wormhole-x402 0.10.0 (npm) · run Nov 1, 2026 · local, throwaway keys 1 a hostile API quotes the wallet's whole 25,000 USDC, to itself no spending policy, as in 0.9.9 .................. allow a $1 cap per payment ............................. refuse X402-501 the guarded signer, $1 cap ....................... refused X402-501, wallet not asked control: a $0.01 payment, $1 cap ................. signed 2 Solana: ApproveChecked where the TransferChecked should be inspectPayment and verify ........................ refuse X402-006 3 a login for app.example, handed to an agent that called data.example, message signing turned on, the contacted site given as origin EVM signer ....................................... refused X402-601 Solana signMessage, and signIn ................... refused X402-601 control: data.example's own login ................ signed
The first result is the argument this site makes everywhere else, turned on itself. Quote conformance stops a payment that disagrees with its quote — the substituted recipient, the inflated amount, the instruction that rewrites the payee. It cannot stop a quote that is itself the theft, because conformance has no opinion on whether a price is reasonable. That question needs a budget, and a budget is something only the person paying can set.
0.9.9 had none. 0.10.0 adds one, off unless you set it: a cap per payment and asset, the assets and payees allowed, and a lifetime total per guard, none of which a quote can change. Beside it stand x402’s own capped defaults in the fixed releases, and on our side the hosted policy layer— an approval threshold, a rolling daily budget across every key, a velocity ceiling and a kill switch, set by the account’s owner — which answers this payment needs_approval while still reporting its conformance verdict as allow. That is the honest shape of the answer: the payment was correct, and it was too much.
What this would not have stopped
The uncapped payment, with no spending policy set. wormhole-x402 checks that a payment is the one quoted. Its spending policy is off unless given, because a cap needs the payer’s own numbers: without one it allows a quote for the whole balance, as 0.9.9 did.
A hosted account with no policy set. V12’s sentence about optional policies applies to ours too. The policy layer holds a payment for approval only where the account’s owner has set a threshold or a budget; an account that never did gets conformance alone.
A login in a format it does not recognise. 0.10.0 holds EIP-4361 and Sign-In-With-Solana requests to the site the agent contacted, and refuses one that does not parse strictly. A login in some other format is not recognised as one, so keep message signing off unless the application needs it. On 0.9.9, an application that allowed message signing got no site check at all; there, x402’s own fixed clients — 2.23.0 and later — are the control.
A facilitator that never calls it. The Solana bug lived in the facilitator. The guard refuses the approval wherever it is consulted — as the agent signs, or as a merchant verifies — but a merchant relying on an unpatched facilitator, and checking nothing itself, is protected only by upgrading.
The other 22 findings. They are counted in the report and not described, so this page makes no claim about them. Upgrading is the fix for all 25; a guard in the signing path is the second line, not a substitute for it.
Sourcing
No exploitation. Neither V12 nor the x402 Foundation has reported an attack using any of the three. V12’s Solana proof was a local test with the real token encoders and parser; by its own account, it did not submit a live transaction. The figure in the header is a count of flaws, not a loss, and it should not be read as one.
The fix dates. V12 says the critical findings were fixed by August 20. The Foundation’s pull requests merged between August 12 and 18, and The Hacker News dates the releases to August 18. The ranges agree; the exact day depends on whether you count the merge or the release. The Hacker News also reports a separate payment bypass fixed in @x402/core 2.27.0 in September, which is not one of V12’s three and is not covered here.
Upgrade targets. @x402/fetch or @x402/axios 2.23.0 or later for the spending cap, @x402/extensions 2.23.0 or later for Sign-In-With-X, and @x402/svm 2.23.0 or later for Solana facilitators, with the matching Python and Go releases. In the guard, wormhole-x402 0.10.0 or later for the spending policy and the sign-in check.