feat(parser_gateway): x402 gating, attestation verifier, module split - #449
Draft
pepe-anchor wants to merge 4 commits into
Draft
Conversation
The gateway forwards parse responses to callers over the public internet, but has no way to tell an unattested response from a real one unless it checks the signature itself. Add a pinned-pubkey verifier: every response's Signature.public_key must match a value provisioned to the gateway at boot (TVC_DEMO_PINNED_PUBKEY_HEX or _FILE), or the caller gets 502 instead of a forwarded response. This is explicitly demo-only (see the module doc for the production replacement sketch using the Turnkey SDK's Nitro attestation verifier). It is a stopgap that lets the x402 settle-on-success contract skip settlement for anything that didn't come from the pinned enclave key, which is the property that matters until real attestation lands. Ported from the unmerged spec/x402-tvc-enforced branch (PR #304), adapted for main's generated SignatureScheme, which has no from_i32 helper (only two variants exist, so the unsupported-scheme branch just names the known one and falls back to the raw int). Co-Authored-By: Claude <noreply@anthropic.com>
Cloud Run-style deploys that open the gateway to allUsers (so AI-agent callers without gcloud identity tokens can reach it) need something to keep random crawlers off the endpoint before the x402 settlement layer runs. Add an Authorization: Bearer <token> gate that is off by default (GATEWAY_AUTH_BEARER_TOKEN / _FILE both unset behaves exactly as before) and, when configured, rejects everything except /health with 401 + WWW-Authenticate. Constant-time compare via subtle::ConstantTimeEq, since a timing leak on a shared secret defeats the point of having one. Whether this stays once X-Stamp validation lands in the enclave (a later PR in this stack) is an open question to settle in that PR's description; this is a weaker, different trust boundary (public gateway vs. enclave ingress) so keeping both is plausible defense in depth. Ported from the unmerged spec/x402-tvc-enforced branch (PR #304, commit 9208e00); no changes needed, the module is self-contained. Co-Authored-By: Claude <noreply@anthropic.com>
Env-driven config for the x402 (HTTP 402 Payment Required) handshake: which facilitator to probe, which network/asset/payTo to advertise in a 402, and a build_middleware() that turns the configured price tags into an x402-axum X402LayerBuilder ready to wrap a route. Three profiles (local/payai/custom) cover offline dev, the payai facilitator, and bring-your-own config. The local profile seeds both an EVM (base-sepolia) and a Solana (solana-devnet) tag with burn-style payTo addresses so a single gateway answers either chain out of the box with zero required env vars. Ported from the unmerged spec/x402-tvc-enforced branch (PR #304) at its final state; no changes needed beyond re-flowing a few comments to ASCII. Note what did NOT come along: the branch's later per-request network derivation (matching the 402's advertised network to the parse request's chain) lives entirely in the hand-rolled TVC-enforced handler (handlers/parse_tvc.rs), which is out of scope here because it needs the gateway-signed payment-marker infra from a parallel PR. This crate's v2 route stays on the static-middleware path: fixed price tags for the life of the process, same as before this port. Co-Authored-By: Claude <noreply@anthropic.com>
…g + attestation + bearer auth
main.rs had grown into one file holding the Turnkey envelope
construction, the health and parse handlers, and all of main()'s
wiring. Adding x402 gating, TVC attestation, and the bearer-token gate
on top of that would have made it worse. Split into state.rs
(AppState), handlers/{health,parse}.rs, plus the attestation/auth/
x402_config modules landed in the previous three commits, so main()
is now just: build the attestation verifier, build the router, probe
the x402 facilitator and mount /v2 behind its middleware if that
succeeds, layer the optional bearer-token gate, serve.
The mock boot-proof handling (MOCK_BOOT_PROOF_* constants,
mock_boot_proof(), and the error_response(msg) shim over
host_primitives::turnkey::error_response) moves into handlers/parse.rs,
since that's the only remaining consumer; the wire-shape tests
(mock_boot_proof_matches_production_wire_shape,
error_response_carries_mock_boot_proof) move with it unchanged, same
mock constant values, same assertions.
Bumps axum 0.6.20 -> 0.8 for this crate (parser/cli already runs 0.8,
so two axum majors in the workspace is the existing status quo) and
migrates main() off the removed axum::Server API onto
tokio::net::TcpListener + axum::serve.
v1 stays open; v2 is gated by x402-axum's middleware built from
x402_config, mounted only if the configured facilitator answers a
/supported probe at startup. Route logic, timeouts, and error handling
are otherwise unchanged from what main.rs already did.
Ported from the unmerged spec/x402-tvc-enforced branch (PR #304) at
its pre-VPM state (commits 8124e71, 7c14465, f323def), since the
later TVC-enforced handler needs the gateway-signed payment-marker
types that a parallel PR is still landing. Also carries bd3b065's
64 KiB public body cap.
Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Host-side half of the x402 work, ported from the unmerged PR #304 onto current main. PR #304 is stacked on #295, which is closed, so its base is a dead branch and it sits 132 commits behind main. This PR brings the gateway part forward on its own so it can actually be reviewed.
Stacked on #446 rather than
main: #446 moves the Turnkey envelope intohost_primitives::turnkey, and building the module split on top of it avoids introducing a second copy of those types and the delete-vs-modify conflict that would follow.What
parser_gateway's singlemain.rsinto modules:lib.rs,state.rs,auth.rs,attestation.rs,x402_config.rs,handlers/{mod,health,parse}.rs.parser/clialongside 0.6.20 inmetrics, so two majors in the workspace is the existing status quo.Deliberately left out, because they need
host_primitives::payment_markerfrom #448 which is not in this base:signing.rs(GatewaySigner) andbin/gateway_keygen.rshandlers/parse_tvc.rsandbin/tvc_probe.rs(these also need the pivot's v2 route)parse_tvc.rsConsequence to be explicit about: after this merges the gateway can do x402 verify and settle, but cannot yet sign a VerifiedPaymentMarker, so the trust pair is not complete until the follow-up PR that lands
signing.rs.Test evidence
The two boot-proof regression tests survive the module split and their mock constant values are byte-for-byte unchanged:
mock_boot_proof_matches_production_wire_shape,error_response_carries_mock_boot_proof. Nothing in this PR's scope needs a live facilitator, network access, or an enclave, so no tests were skipped.Rollback
Revert the commits. The gateway is used only in non-TEE local dev and CI, never wrapping a real enclave, so a revert cannot affect a production deployment.
Linear
PRS-581
Stacked on #446. Supersedes the gateway portion of #304.
🤖 Generated with Claude Code