Skip to content

fix(payment-middleware): fail closed when X402_SERVER_ADDRESS missing outside dev (closes #112)#116

Merged
whoabuddy merged 1 commit intomainfrom
fix/112-fail-closed-payment-middleware
Apr 30, 2026
Merged

fix(payment-middleware): fail closed when X402_SERVER_ADDRESS missing outside dev (closes #112)#116
whoabuddy merged 1 commit intomainfrom
fix/112-fail-closed-payment-middleware

Conversation

@whoabuddy
Copy link
Copy Markdown
Contributor

Summary

Closes #112 (fail-open audit findings).

Two payment middleware sites silently skipped payment verification when X402_SERVER_ADDRESS was missing, intended as a local-dev convenience but failing open in any environment:

  • src/middleware/x402.ts:292 (per-route x402 middleware)
  • src/index.ts:305 (global request middleware)

If X402_SERVER_ADDRESS were ever unset in staging or production (bad deploy, secret rotation glitch, env-var migration, env-var name typo), every paid endpoint would silently become free with only a warn log.

Fix (Option A from the audit)

Restrict the skip to ENVIRONMENT === \"development\". In staging and production:

  • Log at error level (not warn)
  • Return HTTP 503 with { error: \"x402 misconfiguration\", code: \"NOT_CONFIGURED\" }

ENVIRONMENT is already plumbed through wrangler.jsonc per env (development / staging / production).

Why ENVIRONMENT and not X402_NETWORK

X402_NETWORK is typed strictly as \"mainnet\" | \"testnet\" in src/types.ts:53 — using a \"local\" literal would require widening that type. ENVIRONMENT is already declared as string (line 50) and already takes the development value in local dev (wrangler.jsonc:35). Cleaner.

Test plan

  • npm run check passes
  • Local dev (ENVIRONMENT=development, no X402_SERVER_ADDRESS) — skip behavior preserved
  • Staging without X402_SERVER_ADDRESS — returns 503 + error log instead of free responses
  • Production with X402_SERVER_ADDRESS set — unchanged

🤖 Generated with Claude Code

… outside dev

Closes #112.

Two payment middleware sites silently skipped verification when
X402_SERVER_ADDRESS was missing, intended as a local-dev convenience:

- src/middleware/x402.ts:292 (per-route middleware)
- src/index.ts:305 (global middleware)

If X402_SERVER_ADDRESS were ever unset in staging or production (bad
deploy, secret rotation glitch, env-var migration), all paid endpoints
would silently become free with only a warn log.

Restrict the skip to ENVIRONMENT === "development". In staging and
production, missing config now returns HTTP 503 with code
"NOT_CONFIGURED" instead of fail-opening to free.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 30, 2026 11:58
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
x402-api-staging 6fd4315 Apr 30 2026, 11:58 AM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
x402-api-production 6fd4315 Apr 30 2026, 11:58 AM

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses audit finding #112 by changing x402 payment middleware behavior to fail closed when X402_SERVER_ADDRESS is missing outside local development, preventing paid endpoints from silently becoming free due to misconfiguration.

Changes:

  • Gate the “skip payment verification when X402_SERVER_ADDRESS is missing” behavior to ENVIRONMENT === "development" only.
  • In non-development environments, log at error level and return HTTP 503 with { error: "x402 misconfiguration", code: "NOT_CONFIGURED" }.
  • Apply the same fail-closed guard in both the per-route x402 middleware and the global request middleware.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/middleware/x402.ts Updates per-route x402 middleware to skip only in development; otherwise returns 503 on missing server address.
src/index.ts Updates global middleware to skip only in development; otherwise returns 503 on missing server address before applying paid-route middleware.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/middleware/x402.ts
Comment on lines +291 to +305
// Check if x402 is configured. Skip is allowed only in local development —
// staging/production deploys must have X402_SERVER_ADDRESS or all paid routes
// silently become free.
if (!c.env.X402_SERVER_ADDRESS) {
log.warn("X402_SERVER_ADDRESS not configured, skipping payment verification");
return next();
if (c.env.ENVIRONMENT === "development") {
log.warn("X402_SERVER_ADDRESS not configured (local dev), skipping payment verification");
return next();
}
log.error("X402_SERVER_ADDRESS not configured outside local dev — refusing request", {
environment: c.env.ENVIRONMENT,
});
return c.json(
{ error: "x402 misconfiguration", code: "NOT_CONFIGURED" },
503
);
Comment thread src/index.ts
Comment on lines +305 to +318
// Skip is allowed only in local development — staging/production deploys must
// have X402_SERVER_ADDRESS or all paid routes silently become free.
if (!c.env.X402_SERVER_ADDRESS) {
c.var.logger.warn("X402_SERVER_ADDRESS not configured, skipping payment verification");
return next();
if (c.env.ENVIRONMENT === "development") {
c.var.logger.warn("X402_SERVER_ADDRESS not configured (local dev), skipping payment verification");
return next();
}
c.var.logger.error("X402_SERVER_ADDRESS not configured outside local dev — refusing request", {
environment: c.env.ENVIRONMENT,
});
return c.json(
{ error: "x402 misconfiguration", code: "NOT_CONFIGURED" },
503
);
Comment thread src/index.ts
Comment on lines +305 to +318
// Skip is allowed only in local development — staging/production deploys must
// have X402_SERVER_ADDRESS or all paid routes silently become free.
if (!c.env.X402_SERVER_ADDRESS) {
c.var.logger.warn("X402_SERVER_ADDRESS not configured, skipping payment verification");
return next();
if (c.env.ENVIRONMENT === "development") {
c.var.logger.warn("X402_SERVER_ADDRESS not configured (local dev), skipping payment verification");
return next();
}
c.var.logger.error("X402_SERVER_ADDRESS not configured outside local dev — refusing request", {
environment: c.env.ENVIRONMENT,
});
return c.json(
{ error: "x402 misconfiguration", code: "NOT_CONFIGURED" },
503
);
@whoabuddy whoabuddy merged commit 43d12c7 into main Apr 30, 2026
9 checks passed
@whoabuddy whoabuddy deleted the fix/112-fail-closed-payment-middleware branch April 30, 2026 12:04
Copy link
Copy Markdown
Contributor

@arc0btc arc0btc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closes a real security gap — payment routes silently becoming free on any config drift is exactly the kind of thing that's invisible until it's a problem. Good catch and clean fix.

What works well:

  • Fail-closed is the right default. A 503 is far better than silent free access in staging/production.
  • Using ENVIRONMENT === "development" is the right choice — X402_NETWORK would have required type widening, and ENVIRONMENT is already set to "development" in local wrangler config. The PR body explains this clearly, which I appreciate.
  • Logging at error level in misconfigured non-dev environments is correct — this is a condition that needs to page someone, not just be noticed in logs.
  • The code: "NOT_CONFIGURED" field in the 503 body is a nice touch for operators debugging the issue.

[suggestion] Extract the shared guard into a helper (src/middleware/x402.ts + src/index.ts)
The 11-line misconfiguration guard block is duplicated exactly in both files. If the behavior needs to change again (e.g., different HTTP status, additional context in the error body), it'll need to change in two places. Not blocking — the duplication here is small and the fix is more important than the cleanup. Worth a follow-up once this is merged.

[nit] The 503 response doesn't include a Retry-After header. For a misconfiguration error, retry won't help — so this is fine as-is. Just noting it in case the team wants to add an ops hint for callers.

Operational note: We've seen x402 env var issues operationally when rotating secrets or updating wrangler configs mid-deploy. Having the middleware fail with a loud 503 instead of silently passing requests through is exactly what we'd want — makes the misconfiguration immediately visible in monitoring rather than only discoverable by checking revenue.

Test plan looks right. The staging-without-X402_SERVER_ADDRESS case is the critical one to verify before 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.

audit: x402-api fail-open audit findings — payment middleware skips verification on missing X402_SERVER_ADDRESS

3 participants