Skip to content

feat(x402): production-grade HTTP payment rails, persistent stores, w… - #480

Open
Unclebaffa wants to merge 4 commits into
Bitcoindefi:mainfrom
Unclebaffa:feature/x402-payment-rails
Open

feat(x402): production-grade HTTP payment rails, persistent stores, w…#480
Unclebaffa wants to merge 4 commits into
Bitcoindefi:mainfrom
Unclebaffa:feature/x402-payment-rails

Conversation

@Unclebaffa

@Unclebaffa Unclebaffa commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

[EPIC] x402 Payment Rails — production-grade HTTP payment gate

Executive Summary

This PR evolves the initial in-memory x402 prototype into a production-grade payment infrastructure for Open Stellar. It delivers persistent receipt and subscription storage, automated settlement webhooks with multi-layered SSRF protection, multi-chain settlement quotes (Stellar XLM, Base ETH, BNB Chain), on-chain transaction verification, recurring billing with monthly usage caps, a 5-line developer SDK (@open-stellar/x402), and public APIs backing the x402 service catalog marketplace and payment explorer.


Implemented Sub-Issues & Feature Checklist

  • x402 Persistent Receipt Store: Replaced in-memory registry with atomic disk persistence (.data/x402-receipts.json) using process-PID swap files.
  • x402 Webhook Callbacks: Automated HTTP POST notifications dispatched upon settlement with redirect: 'manual' SSRF isolation.
  • x402 Subscriptions & Recurring Billing: Monthly quota caps, automated renewal cycles, 24-hour grace periods, and paused states.
  • Multi-Chain Quotes: Unified quote generation supporting Stellar (XLM), Base (ETH), and BNB Chain (BNB) within a single quote payload.
  • npm Developer SDK (@open-stellar/x402): Workspace package exporting withX402() higher-order route wrapper for gating Next.js App Router API routes in 5 lines.
  • x402 Service Marketplace: GET /api/protocol/x402/services endpoint serving registered API services with pricing, uptime, and reputation metadata.
  • Payment Explorer: /explorer UI & GET /api/explorer/receipts endpoint for querying and filtering receipts by agent, service, or chain.

Technical Architecture & Core Subsystems

┌─────────────────────────────────────────────────────────────────────────────┐
│                          Next.js Route / SDK Consumer                       │
│                                (withX402 Wrapper)                           │
└──────────────────────────────────────┬──────────────────────────────────────┘
                                       │
                                       ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                             x402 Payment Engine                             │
│  ┌──────────────────────────┐  ┌──────────────────────┐  ┌───────────────┐ │
│  │   Multi-Chain Quotes     │  │   On-Chain Horizon   │  │ Subscriptions │ │
│  │ (Stellar / Base / BNB)   │  │   Payment Verifier   │  │  & Usage Caps │ │
│  └─────────────┬────────────┘  └──────────┬───────────┘  └───────┬───────┘ │
└────────────────│──────────────────────────│──────────────────────│──────────┘
                 │                          │                      │
                 ▼                          ▼                      ▼
┌──────────────────────────┐   ┌────────────────────────┐  ┌──────────────────┐
│   Receipt Store (.json)  │   │  SSRF-Guarded Webhook   │  │ Subscription     │
│   (PID Swap Writes)      │   │  Dispatcher ('manual') │  │ Store (.json)    │
└──────────────────────────┘   └────────────────────────┘  └──────────────────┘

1. Persistent Storage Infrastructure

  • Receipt Store (lib/protocols/x402-receipt-store.ts): Durably persists payment receipts to .data/x402-receipts.json. Writes use process PID + timestamp temporary files (${DB_PATH}.${process.pid}.${timestamp}.tmp) swapped atomically to prevent file corruption during server restarts.
  • Subscription Store (lib/protocols/x402-subscription-store.ts): Stores active subscriptions, monthly usage counters (callsUsed), statuses (active, grace, paused), and billing event histories in .data/x402-subscriptions.json. Includes serializeWrite async mutex queues and synchronous durable updates (saveX402SubscriptionStoreRecordSync) to prevent call-counter loss on serverless cold starts.

2. On-Chain Stellar Horizon Verification

  • Verifier Engine (lib/protocols/x402.ts): verifyStellarPayment() queries Stellar Horizon API endpoints (https://horizon.stellar.org/transactions/:hash/operations) and verifies transaction success before accepting quotes.
  • payment & create_account Schema Mapping: Correctly maps both standard XLM native transfers (to, from, amount, asset_type === 'native') and new account activations (account, funder, starting_balance). Enforces mandatory recipient, sender, and minimum XLM amount matching (op.amount >= expectedAmountXlm), rejecting underpayments or dust attacks.

3. Webhook Delivery & Multi-Layered SSRF Guard

  • Dispatcher (lib/protocols/x402-webhooks.ts): dispatchX402SettlementWebhook() posts JSON payloads ({ event: 'x402.settlement', timestamp, receipt }) upon settlement.
  • Numeric IP & Domain SSRF Protection: isPrivateOrLoopbackHost() parses hostnames, decimal IPv4 integers (e.g. 2130706433), and IPv4-mapped IPv6 (::ffff:169.254.169.254), rejecting loopback (127.0.0.0/8), private subnets (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), link-local/cloud metadata (169.254.0.0/16), .internal, and .local targets.
  • HTTP Redirect Isolation: sendWebhookHttpRequest() sets redirect: 'manual' on fetch() calls and rejects 30x HTTP redirects, preventing attackers from bypassing initial domain checks via HTTP redirects.

4. Authentication & Fail-Closed Security

  • Route Authorization (app/api/protocol/x402/webhooks/route.ts): isAuthorized() fails closed in production whenever no admin secret is configured.
  • Constant-Time Secret Comparison: Utilizes crypto.timingSafeEqual with length-matched byte buffers (safeCompare()) to eliminate timing side-channel attacks on Authorization: Bearer and X-Admin-Secret headers.

5. Developer SDK (@open-stellar/x402)

  • Workspace package packages/x402 and wrapper lib/sdk/x402-sdk.ts enable route gating in 5 lines:
    import { withX402 } from '@open-stellar/x402'
    
    export const GET = withX402({ serviceId: 'oracle-service', unitPriceUsd: 0.05 }, async (req) => {
      return Response.json({ data: 'Protected Payload' })
    })

File Changes Matrix

File Path Description / Responsibility
lib/protocols/x402.ts Core payment engine, quotes, Horizon on-chain verifier, subscription limits
lib/protocols/x402-subscription-store.ts Durable subscription disk persistence & write mutex serialization
lib/protocols/x402-receipt-store.ts Persistent receipt store with PID atomic file swap writing
lib/protocols/x402-webhooks.ts Webhook delivery log, numeric IP SSRF guard, and redirect: 'manual' fetch
lib/sdk/x402-sdk.ts @open-stellar/x402 SDK route gating wrapper (withX402)
app/api/protocol/x402/webhooks/route.ts Webhook delivery log & dispatch API with fail-closed constant-time auth
app/api/protocol/x402/services/route.ts Service catalog marketplace API
app/api/protocol/x402/subscriptions/renew/route.ts Automated recurring billing renewal route
packages/x402/package.json @open-stellar/x402 workspace SDK package manifest
package-lock.json Synchronized workspace registry locking for Playwright CI actions
__tests__/x402-ssrf-guard.test.ts Unit tests for SSRF validation, numeric IP encodings, and route auth
__tests__/x402-stellar-verification.test.ts Unit tests for Horizon payment & create_account on-chain verification
__tests__/x402-subscription-concurrency.test.ts Integration tests for serialized concurrent store writes
__tests__/x402-subscriptions-persistence.test.ts Integration tests for subscription disk persistence & renewals
__tests__/x402-webhooks.test.ts Integration tests for settlement webhook delivery

Security Audit & Hardening Matrix

Security / Integrity Vulnerability Root Cause Implemented Technical Fix
SSRF via Webhook Targets Untrusted URLs sent to fetch() isPrivateOrLoopbackHost() blocks RFC1918, 127.0.0.0/8, link-local/cloud metadata 169.254.169.254, and numeric/IPv6 encodings.
SSRF via 30x HTTP Redirects fetch() defaulted to redirect: 'follow' Set redirect: 'manual' on fetch() calls and reject 30x HTTP redirect responses.
Webhook Auth Bypass Spoofable Host header check when secret unconfigured isAuthorized() fails closed in production if no secret is set, ignoring Host headers.
Secret Timing Attack Non-constant-time string === comparison Implemented safeCompare() using crypto.timingSafeEqual with length-checked byte buffers.
Stellar Dust / Underpayment Attack Payment verifier omitted amount validation verifyX402Settlement passes expectedAmountXlm and verifyStellarPayment rejects operations where op.amount < expectedAmountXlm.
create_account Verification Bypass create_account ops use different Horizon field names Mapped account, funder, and starting_balance fields for create_account operations and required mandatory field matches.
Store Write Race Conditions Concurrent async writes corrupted JSON disk store Wrapped file operations in an in-process promise queue (serializeWrite) and PID-tagged .tmp swap files.
Call Counter Loss Debounced flushing lost counters on cold restarts Replaced hot-path debouncing with synchronous durable counter persistence (saveX402SubscriptionStoreRecordSync).

Verification & CI Testing

  1. Vitest Unit & Integration Suite:
    npm test
    # Test Files: 103 passed (103)
    # Tests:      640 passed (640)
  2. TypeScript Strict Type Check:
    npx tsc --noEmit
    # Exit code 0 (0 errors)
  3. CI Dependency Locking:
    Updated package-lock.json with workspace package registry entry @open-stellar/x402 to ensure npm ci succeeds in GitHub Actions Playwright E2E workflows.

Closes #18

…ebhooks, multi-chain quotes, recurring billing, and npm SDK
Comment thread app/api/protocol/x402/webhooks/route.ts
Comment thread lib/protocols/x402-subscription-store.ts Outdated
Comment thread lib/protocols/x402.ts
Comment thread lib/sdk/x402-sdk.ts Outdated
…debounced call flushing, and Stellar payment verification
Comment thread app/api/protocol/x402/webhooks/route.ts
Comment thread lib/protocols/x402.ts Outdated
Comment thread lib/protocols/x402-webhooks.ts Outdated
Comment thread lib/protocols/x402.ts
Comment thread app/api/protocol/x402/webhooks/route.ts Outdated
…ck, numeric IP SSRF guard, durable counter saving, timing-safe auth comparison, and update package-lock.json
Comment thread lib/protocols/x402.ts
Comment thread lib/protocols/x402-webhooks.ts
@gitar-bot

gitar-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 11 resolved / 11 findings

Production-grade HTTP payment infrastructure with multi-chain quotes, persistent stores, and developer SDK, addressing webhook authentication flaws, Stellar verification gaps, SSRF vulnerabilities, and concurrency issues.

✅ 11 resolved
Security: Unauthenticated webhook endpoint enables SSRF and fake payment events

📄 app/api/protocol/x402/webhooks/route.ts:17-31 📄 lib/protocols/x402-webhooks.ts:22-36
POST /api/protocol/x402/webhooks is publicly reachable (middleware only rate-limits, no auth) and passes an attacker-controlled targetUrl straight into fetch(cleanUrl) inside dispatchX402SettlementWebhook. An attacker can force the server to issue POST requests to arbitrary internal hosts (e.g. http://169.254.169.254/ cloud metadata, internal admin services) — a classic SSRF. Additionally the attacker-controlled receipt body is forwarded to publishSystemEvent({ type: 'payment.received', ... }), letting anyone inject spoofed settlement events into the system bus. Require authentication on this route and validate targetUrl against an allowlist / block private and link-local IP ranges before fetching.

Bug: Subscription store write is not concurrency-safe (lost updates/corruption)

📄 lib/protocols/x402-subscription-store.ts:31-45
writeSubscriptions uses a temp path keyed only on process.pid (${DB_PATH}.${process.pid}.tmp). Two concurrent requests in the same process write to the same temp file before renaming, so writes can interleave and corrupt the file. Even without that, saveX402SubscriptionStoreRecord does a non-atomic read-modify-write (readSubscriptions → filter → writeSubscriptions), so concurrent updates to different subscriptions overwrite each other and lose data. Use a unique temp filename per write (include a random suffix) and serialize read-modify-write access (in-process mutex/queue) around the store.

Performance: Full-file subscription rewrite on every metered request

📄 lib/protocols/x402.ts:467-470 📄 lib/protocols/x402-subscription-store.ts:47-53
checkX402Subscription({ consumeCall: true }) now calls saveX402SubscriptionStoreRecord on every authorized call, and that helper reads the entire subscriptions JSON file, parses it, filters, re-serializes the whole array and writes+renames it. Since gateX402Request invokes this on every request served through an active subscription, each hot-path API call incurs a full-file O(n) disk read+write. Under load this becomes a serious bottleneck and I/O amplification. Consider debouncing/batching persistence of callsUsed, or writing only periodically / on a background flush rather than synchronously per request.

Security: SDK gate authorizes Stellar payments without on-chain verification

📄 lib/sdk/x402-sdk.ts:68-82 📄 lib/protocols/x402.ts:244-246
The new gateX402Request authorizes a request when settleX402 returns ok, but for chain === 'stellar' settleX402 accepts the payment based solely on the txHash matching a 64-hex-char regex — it never queries Horizon to confirm the transaction exists, succeeded, and paid the expected amount to the expected address. An attacker who obtains a quote's paymentRef (returned in the 402 response) can replay it with any well-formed fake txHash and gain authorized access to every route wrapped with withX402. Verify Stellar settlements against the network (amount/destination/success) as is done for EVM chains via verifyEvmPayment, before treating the receipt as accepted.

Security: Webhook auth bypass via Host header when no admin secret set

📄 app/api/protocol/x402/webhooks/route.ts:7-18
In isAuthorized, when neither ADMIN_SECRET nor X402_ADMIN_SECRET is configured, the function returns process.env.NODE_ENV !== 'production' || host.includes('localhost'). The host value comes from the client-controlled Host header, so in a production deployment that has no admin secret configured, any attacker can bypass the check simply by sending Host: localhost (or any host containing that substring) and gain unauthenticated access to the webhook dispatch endpoint — the very SSRF/abuse vector this commit intends to close. Fail closed instead: when no secret is configured, deny in production and only allow in non-production environments.

...and 6 more resolved from earlier reviews

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqubecloud

Copy link
Copy Markdown

@Unclebaffa

Copy link
Copy Markdown
Contributor Author

@leocagli Please review and merge

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[EPIC] x402 Payment Rails — production-grade HTTP payment gate

1 participant