Skip to content

x402Serve() has no replay protection or rate limiting — document as a known limitation #91

Closed

Description

@Eras256

Context

While verifying a third party's ("Built with Nirium" on nirium.xyz/stats) claim about their production x402 setup, we confirmed that x402Serve() has no protection against a reused X-PAYMENT header and no rate limiting. Both have to be built by the integrator on top of it.

Evidence

Downloaded the real published tarball for the version pinned in that integrator's package.json (nirium@0.11.0) and grepped the full dist/index.js for replay, nonce, rate limit, dedupe, idempot, single-use — zero matches. Read the x402Serve() function body directly (dist/index.js lines 565-702 in that tarball): it validates payTo/routes/facilitatorApiKey, builds the route map, and wraps paymentMiddlewareFromConfig from @x402/express. No claim tracking, no store, no window counter anywhere in the function.

The integrator (Edgadafi/remesa-liquidez, commit 1e0cbd59, confirmed via the GitHub Deployments API as the live "Production" deployment) built both protections themselves, mounted before x402Serve():

  • Single-use X-PAYMENT: backend/src/middleware/paymentGuard.ts — hashes X-PAYMENT + method + URL, claims it once via SET NX on Upstash, releases the claim if the request doesn't end in 2xx (so a legitimate retry after a facilitator failure isn't blocked), and fails closed (503) in production without a durable store.
  • Rate limiting: backend/src/middleware/rateLimit.ts — sliding window per IP on Upstash, mounted per-route-group in app.ts, fails open on store failure.

Why it matters

x402Serve() doesn't itself claim to do either of these, and neither does the README. But someone who copies our own examples/deploy-x402-vercel or any of the SDK's other x402 examples and doesn't independently build what this integrator built ends up with an endpoint where the same signed payment proof can be replayed as many times as it's resent before the facilitator's own settlement window closes, and with no defense against a request flood.

Proposed directions (not asking for either in this issue, just registering the gap)

  • Document it as a known limitation in the README and in /developers — "x402Serve() verifies and settles the payment; it does not deduplicate a proof across requests or rate-limit callers. Add your own guard (see [example link]) if you need either."
  • Evaluate, separately, whether an opt-in replayGuard/rateLimit config on x402Serve() itself (backed by a pluggable store, matching the KV-store pattern this integrator used) is worth adding to the SDK later.

Verification

Confirmed by downloading the real npm tarball for nirium@0.11.0 (not reading the repo source) and grepping the actual dist/index.js that ships to consumers, cross-checked against the integrator's own commit history via the GitHub API (not just their word).

🤖 Generated with Claude Code

Activity

  1. Eras256 commented on Sep 24, 2026

    @Eras256
    CollaboratorAuthor

    Resolved in two merged PRs:

    The design (the store interface, releasing a claim only on a non-2xx response, fail-closed for replay / fail-open for rate limiting) is generalized from, and credited in the code and CHANGELOG.md to, the real production integrator this issue was originally filed about, Edgadafi/remesa-liquidez.

    Tests reproducing the exact scenarios discussed here (reused proof -> 409, released on failure -> allowed retry, store down -> 503 not silent pass-through, rate limit -> 429, store down -> fail-open) pass in CI: https://github.com/nirium-protocol/nirium/actions/runs/36051437859

    Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationenhancementNew feature or requestx402

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions