XAgent Blog

Home Blog Market Launch Store

The x402 bugs that let unpaid requests reach paid handlers

Between August 4 and August 11 the x402 SDKs shipped a cluster of fixes for one failure: a request reaching a paid handler with verify and settle both skipped. The bugs lived in path normalization.

By XAgent Team · 2026-08-12

The most useful agentic-commerce document published this month is a changelog. Between August 4 and August 11 the x402 SDKs shipped a cluster of fixes for a single failure mode: a request reaching a payment-gated handler with verification and settlement both skipped. None of them were reported as vulnerabilities, all of them shipped as ordinary patch notes, and every one lived in the same unglamorous place — the code that decides whether a URL matches a protected route.

What was actually fixed

The @x402/core 2.22.0 release, published on August 11, describes the clearest case: "Fixed a paywall bypass where a backslash in a :param/[param] segment let an unauthenticated request reach a protected handler."

The mechanism is worth understanding because it is not exotic. The middleware and the web framework disagreed about what the path was. The pull request puts it precisely: "The compiled regex missed, getRouteConfig returned undefined, and the request was treated as unprotected — the framework then dispatched it to the paid handler anyway. The protected resource is served with no verify and no settle."

A week earlier, 2.21.0 carried a variant of the same disagreement: "Fixed a payment bypass on wildcard (*) route patterns: the compiled route regex used .*? without the dotAll flag, so a percent-encoded ECMAScript line terminator (e.g. %E2%80%A8, %0A, %0D) surviving path normalization would fail to match, causing requiresPayment() to return false."

It was not confined to one language. The Go work reports the same defect class and its blast radius: "Four vectors were confirmed, and three of them live in the shared normalizePath/parseRoutePattern helpers in http/server.go, so all three adapters (Echo, Gin, net/http) were affected." The Go description of the outcome mirrors the TypeScript one: "the router invokes the paid handler anyway: the protected resource is served with no VerifyPayment and no ProcessSettlement."

Two further fixes in the same window are about trusting the wrong signal rather than missing the route. One concerns settlement itself: "settleEIP3009 currently treats receipt.status === \"success\" as proof of a successful transfer. The receipt's status only tells us the tx did not revert; it does not tell us that the expected ERC-20 Transfer was emitted from the expected token contract with the expected (from, to, value)." The other closes a schema-loader hole where "an attacker could embed a $ref pointing at http://169.254.169.254/... (cloud metadata), an internal service, or a file:// path, and the facilitator would fetch/read it before ever val[idating]".

What this is not

It matters to be accurate about severity, because the temptation to write this up as a scandal is strong and the evidence does not support one.

These were contributor-submitted hardening pull requests, merged and released through the project's ordinary version train. There is no report of exploitation, no CVE, and no coordinated disclosure. The repository has published exactly two security advisories in its history — one in August 2025 and one in March 2026 — and neither covers this month's fixes. That absence is a fact worth stating; it is not evidence of concealment.

The fixes also differ in how exploitable they were. The Go work is candid that one vector was theoretical in practice: "With a GET /api/premium/* route config, a bare /api/premium now returns 402 instead of falling through. Echo and Gin 404 that path anyway, so no previously-working request changes outcome — but the gate is now fail-closed."

And the settlement fix landed in one language only. Its author says so: "TypeScript only for this PR. The same defensive check belongs in go/mechanisms/evm/exact/facilitator/eip3009.go and python/x402/mechanisms/evm/exact/eip3009_facilitator.py; happy to follow up with parity PRs once direction here is confirmed." If you run the Go or Python facilitator, that particular check is still outstanding as of this writing.

Why a payment gate fails open

There is a general lesson here that outlives these specific commits.

A payment check placed in middleware is a negative control: it acts when it recognises a protected route. Anything that makes it fail to recognise the route does not produce an error. It produces a free request. The failure is silent on both sides — the agent gets its resource, the merchant's handler runs, and nothing in the logs says a payment was skipped rather than made.

That is the opposite of how card processing fails. A card decline is loud, it is a state everyone's code already handles, and no one accidentally ships an order because an authorization silently did not happen. An HTTP payment gate has no equivalent reflex, and it is bolted onto path-matching logic that predates it and was written for routing, not for money.

Notice where all of this month's bugs live: backslashes in a path segment, a percent-encoded line terminator, a trailing slash, a wildcard regex flag. This is URL-parsing minutiae. It is the same category of defect that produces authentication bypasses in web frameworks, and it is now in the payment path. The protocol itself is not what failed here — the 402 exchange and the EIP-3009 signature worked exactly as specified. What failed was the plumbing that decides when to invoke them.

What a merchant should do about it

Three things, in order of effort.

Pin and upgrade deliberately. If you took an x402 SDK dependency and have not looked at it since, the changelog between your version and current is not routine. Read it rather than bumping.

Test the gate, not the payment. Most integration testing checks that a valid payment succeeds. The failure mode here is on the other side: send unauthenticated requests at every shape of your protected paths — trailing slashes, encoded characters, backslashes, path parameters — and assert you get 402, not 200. That test suite is short and it is the one nobody writes.

And treat verification as your own job, not the receipt's. The settlement fix above is the general case: a transaction that did not revert is not a transaction that paid you. Whatever stack you run, the thing you should be checking is that the expected transfer of the expected amount to the expected address actually happened.

What's next

Expect more of this, not less. x402 has added network bindings at roughly one a month, and each one is a fresh implementation of the same verification responsibility. The specification is the easy part; the surface area is in the adapters.

For merchants the strategic version of this is the familiar one. Every SDK you wire directly into your storefront is a dependency whose failure mode is now your revenue. As we argued about why the execution layer matters more than the rail, the value of an operations layer is not that it knows a protocol you do not — it is that somebody is responsible for the boring parts staying correct while the protocol moves underneath.

If you would rather not own the path-normalization code that stands between an agent and your paid content, list your store on XAgent and let the open execution market run the gate, the settlement check and the record.

Keep reading

  • MCP removed sessions. Your shopping cart is now a handle
  • EIP-3009 transferWithAuthorization for agents, explained
  • Agentic commerce numbers live in earnings calls, not filings