feat: enforce request-bound payment authorization v2#313
Conversation
Co-authored-by: codex <codex@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR makes authorization v2 request-bound by adding audience, method, resource, content type, body hash, and payer validation. It enforces minimum authorization version 2 by default, propagates binding data into receipts, updates SDK/web validation, and adds deterministic E2E and deployment coverage. ChangesRequest-bound authorization
Estimated code review effort: 5 (Critical) | ~90 minutes Possibly related issues
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4b5abb0d44
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-authored-by: codex <codex@users.noreply.github.com>
|
@codex review Please perform a deep review of the latest commit. Inspect every changed file and trace the payment authorization flow across gateway, verifier, web, SDK, E2E tests, receipts, cache behavior, configuration, deployment files, and documentation. Focus on correctness, security, replay resistance, request binding, cross-service contract parity, failure paths, compatibility, CI coverage, and missing regression tests. |
|
Codex Review: Didn't find any major issues. Breezy! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
MicroAI-Paygate/tests/e2e.test.ts
Lines 101 to 105 in 65d3b33
In the new deterministic CI path where run_e2e.sh starts the OpenRouter mock when no key is set, the signed happy path should require HTTP 200. Keeping this upstream_unavailable escape means CI still passes if the mock never starts or OPENROUTER_URL wiring breaks, so the new E2E no longer proves the loopback provider path works; remove this fallback or gate it behind an explicit live-provider mode.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Please perform another deep review of the latest commit. Re-check every changed file and the complete request-bound authorization v2 flow, including cache-hit body binding, audience normalization, verifier error mapping, deterministic readiness, CORS challenge/preflight behavior, deployment configuration, cross-service parity, and regression coverage. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
gateway/cache_integration_test.go (1)
205-217: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAssert the complete 402 payment context in the response body.
As per coding guidelines: "gateway/**/*.go: X402 payment flow: missing signatures should produce a complete 402 payment context".
While the test currently asserts the
402status code, consider parsing the response body and verifying that the complete payment context is actually returned, ensuring full regression coverage for this specific requirement.♻️ Proposed refactor to assert context
if w4.Code != 402 { t.Errorf("Expected status 402 for missing signature, got %d", w4.Code) } + + var w4Resp map[string]interface{} + if err := json.Unmarshal(w4.Body.Bytes(), &w4Resp); err == nil { + if _, hasCtx := w4Resp["paymentContext"]; !hasCtx { + t.Errorf("Expected 402 response to contain a complete paymentContext") + } + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@gateway/cache_integration_test.go` around lines 205 - 217, Extend the missing-signature case for reqNoSig in the cache integration test to parse w4’s response body and assert that the complete x402 payment context is present, not just the 402 status. Reuse the existing response schema or payment-context symbols from the test suite, and retain the current status assertion.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@gateway/cache_integration_test.go`:
- Around line 205-217: Extend the missing-signature case for reqNoSig in the
cache integration test to parse w4’s response body and assert that the complete
x402 payment context is present, not just the 402 status. Reuse the existing
response schema or payment-context symbols from the test suite, and retain the
current status assertion.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4105c87a-9c9f-4845-856b-c21f2ae7b46d
📒 Files selected for processing (12)
.github/workflows/e2e.ymlDEPLOY.mdgateway/README.mdgateway/cache_integration_test.gogateway/config.gogateway/config_test.gogateway/errors_test.gorun_e2e.shtests/README.mdtests/mock-openrouter.tsweb/scripts/deployed-smoke-test.tsweb/src/app/docs/protocol/page.mdx
🚧 Files skipped from review as they are similar to previous changes (11)
- tests/mock-openrouter.ts
- gateway/config.go
- gateway/config_test.go
- run_e2e.sh
- DEPLOY.md
- tests/README.md
- web/src/app/docs/protocol/page.mdx
- .github/workflows/e2e.yml
- gateway/errors_test.go
- gateway/README.md
- web/scripts/deployed-smoke-test.ts
There was a problem hiding this comment.
💡 Codex Review
MicroAI-Paygate/gateway/main.go
Line 342 in 2e75343
No CI test exercises the gateway's CORS middleware with a browser-style OPTIONS request containing Access-Control-Request-Headers: ...x-402-payer; the Bun E2E requests do not enforce CORS, and deployed-smoke-test.ts is not run by a workflow. If this allowlist entry or the middleware ordering regresses, every browser v2 signed retry will be blocked before reaching the gateway despite the service tests passing. The allowlist change is correct; please add a router-level test asserting the allowed origin, POST method, and all signed-retry headers.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-authored-by: codex <codex@users.noreply.github.com>
|
Addressed the preflight regression-test finding in 68239b7. The gateway now exposes its real CORS middleware setup to a router-level test that sends a browser-style OPTIONS request and asserts the exact origin, POST, and all signed-retry headers including X-402-Payer. |
|
@codex review Please perform another deep review of the latest commit. Verify the router-level CORS preflight regression, numeric audience-port canonicalization, authorization v2 documentation parity, deterministic E2E guidance, cache body binding, cross-service contracts, security failure paths, and all affected tests. |
Co-authored-by: codex <codex@users.noreply.github.com>
|
Addressed the remaining cache-challenge coverage suggestion in 006520a. The Redis-backed cache integration test now parses the missing-signature response and verifies every required authorization v2 field, including the exact request-body hash. Focused and full gateway tests plus go vet pass. |
|
@codex review Please perform another deep review of the latest commit. Re-check every changed file and trace authorization v2 end to end, with special attention to cache-hit and missing-signature behavior, exact request-body binding, CORS preflight coverage, audience normalization, verifier parity, receipts, deployment configuration, documentation, and regression tests. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 006520acd6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-authored-by: codex <codex@users.noreply.github.com>
|
@codex review Please perform another deep review of the latest commit. Verify the cold-run E2E budget, browser-compatible IP audience normalization, staged v1-to-v2 deployment order, receipt schema 2.0 with legacy 1.0 verification, fresh nonce and exact-body cache authorization, cross-service serialization parity, security failure paths, and all regression tests. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4ee9ebdd0a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-authored-by: codex <codex@users.noreply.github.com>
|
@codex review Please perform another deep review of the latest commit. Verify exact v2 receipt-to-authorization binding and substitution resistance, version-discriminated OpenAPI receipt schemas, trailing-dot and ambiguous IP audience handling, cross-service receipt serialization, compatibility with stored v1 receipts, security failure paths, and all regression tests. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c705e6dd94
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-authored-by: codex <codex@users.noreply.github.com>
|
@codex review Please perform a deep review of the current head. Trace the payment authorization and receipt contract end to end across gateway, verifier, SDK, web, E2E tests, OpenAPI, configuration, and deployment compatibility. Focus on correctness, security, canonicalization, replay behavior, backward compatibility, and missing negative tests. |
| if endsInIPv4Number(asciiHostname) { | ||
| return "", fmt.Errorf("PAYGATE_AUDIENCE must contain a canonical IP address") | ||
| } | ||
| return strings.ToLower(asciiHostname), nil |
There was a problem hiding this comment.
🟡 Medium gateway/config.go:143
normalizeAudienceHostname returns a hostname with a trailing dot for input like gateway.example。, bypassing the rejection of trailing-dot hosts. The early check on line 120 only catches an ASCII ., but idna.Lookup.ToASCII normalizes Unicode dot-equivalent characters such as 。 (U+3002) to ., so the trailing dot is introduced after the check. The returned canonical hostname then ends with ., violating the canonical-origin policy. Consider rejecting a trailing dot again after the ToASCII call.
asciiHostname, err := idna.Lookup.ToASCII(hostname)
if err != nil {
return "", fmt.Errorf("PAYGATE_AUDIENCE must contain a valid hostname: %w", err)
}
+ if strings.HasSuffix(asciiHostname, ".") {
+ return "", fmt.Errorf("PAYGATE_AUDIENCE hostname must not end with a dot")
+ }
if address, err := netip.ParseAddr(asciiHostname); err == nil {
return address.String(), nil
}
if endsInIPv4Number(asciiHostname) {
return "", fmt.Errorf("PAYGATE_AUDIENCE must contain a canonical IP address")
}
return strings.ToLower(asciiHostname), nil🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @gateway/config.go around line 143:
`normalizeAudienceHostname` returns a hostname with a trailing dot for input like `gateway.example。`, bypassing the rejection of trailing-dot hosts. The early check on line 120 only catches an ASCII `.`, but `idna.Lookup.ToASCII` normalizes Unicode dot-equivalent characters such as `。` (U+3002) to `.`, so the trailing dot is introduced after the check. The returned canonical hostname then ends with `.`, violating the canonical-origin policy. Consider rejecting a trailing dot again after the `ToASCII` call.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e564accc6e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-authored-by: codex <codex@users.noreply.github.com>
|
@codex review Please perform another deep review of the current head. Trace custom protocol-adapter payer resolution and exact receipt binding across the SDK and browser, including request/response byte hashes, raw-body parsing, signature trust, malformed inputs, backward compatibility, and all cross-service authorization v2 behavior. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e9f55ed81b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Serialize according to the versioned Go receipt contract. | ||
| const receiptJSON = serializeReceiptForGateway(signedReceipt.receipt); |
There was a problem hiding this comment.
Pin the gateway key before accepting v2 receipts
The new v2 serialization makes these receipts pass verifyReceipt, but the function still authenticates the signature only against server_public_key supplied inside the same untrusted receipt. A forged receipt can therefore include an attacker-controlled key and matching signature, pass the new authorization/body-hash comparisons, be saved by use-x402.ts, and later be displayed as “Receipt verified.” Require an out-of-band trusted gateway public key, as the TypeScript SDK already does, and compare the recovered key against it before accepting the receipt.
Useful? React with 👍 / 👎.
| receiptMatchesPaymentAuthorization(receipt, context, payer) && | ||
| receiptMatchesExchange( | ||
| receipt, | ||
| new URL(getSummarizeUrl()).pathname, |
There was a problem hiding this comment.
Derive receipt endpoints consistently for prefixed gateways
When NEXT_PUBLIC_GATEWAY_URL contains a path prefix, which x402-client.test.ts explicitly supports, this comparison expects the receipt endpoint to include that public prefix. The Go gateway records c.Request.URL.Path, so a reverse proxy that strips /paygate before forwarding—the only way the current unprefixed Gin route can handle that deployment—produces /api/ai/summarize in the receipt, and the browser rejects an otherwise successful paid response. Derive the expected receipt endpoint from the API route as the SDK does, rather than from the full prefixed public URL.
Useful? React with 👍 / 👎.
| if (isPaymentContextV2(paymentContext) && payer === undefined) { | ||
| payer = await this.signer.getAddress?.(); | ||
| } |
There was a problem hiding this comment.
Respect a custom adapter's undefined payer result
When a custom adapter implements getPayer but deliberately returns undefined—for example because its signing implementation resolves a delegated or protocol-specific payer internally—this fallback silently replaces that decision with signer.getAddress(). The substituted EOA is then passed into signing, header construction, and the new receipt-authorization comparison, so an otherwise valid custom adapter can create the wrong authorization or have its successful receipt rejected. Use the signer fallback only when the adapter does not implement getPayer, rather than whenever the implemented hook returns undefined.
Useful? React with 👍 / 👎.
Summary
X-402-PayerSecurity properties
PAYGATE_AUDIENCE, never request host or forwarded headersMIN_AUTHORIZATION_VERSION=2Validation
cd gateway && go test -count=1 ./... && go vet ./...cd verifier && cargo fmt -- --check && cargo clippy -- -D warnings && cargo test— 51 passedcd sdk/typescript && bun run typecheck && bun test && bun run build— 26 passed, 1 opt-in live test skippedcd web && bun run lint && bun run typecheck && bun run test:unit && bun run build— 76 passedVERIFIER_NONCE_STORE=memory ./run_e2e.sh— 4 passedFollow-up to #306.
Note
Enforce request-bound payment authorization v2 across gateway, verifier, and SDK
PaymentAuthorizationv2; legacy v1 contexts are rejected by default.PaymentContextserver-side, validatesX-402-Payeras a hex address, and requires the verifier's recovered address to match the claimed payer.MIN_AUTHORIZATION_VERSION(default 2) and returnsauthorization_version_too_old(400) for older contexts; new error codessigner_mismatchandinvalid_authorization_contextare also mapped.validateReceiptFormatstrictly rejects extra or missing fields.PAYGATE_AUDIENCEis now a required, canonicalized gateway environment variable; startup fails if it is missing or malformed.X-402-Payeron signed retries.OPENROUTER_API_KEYand instead start a deterministic local mock on port 3100; the CI workflow always runs setup and test steps.authorization_version_too_oldrejections untilMIN_AUTHORIZATION_VERSIONis lowered to 1 during migration.Macroscope summarized e9f55ed.
Summary by CodeRabbit
X-402-Payer.MIN_AUTHORIZATION_VERSION(defaulting to2) and stricterPAYGATE_AUDIENCEorigin normalization; added a Prometheus metric for authorization outcomes.