Skip to content

fix(billing): verify Stripe webhook raw-body signatures - #520

Draft
seonghobae wants to merge 21 commits into
feat/stripe-checkout-reconciliation-ops-488from
feat/stripe-webhook-trust-boundary-488
Draft

fix(billing): verify Stripe webhook raw-body signatures#520
seonghobae wants to merge 21 commits into
feat/stripe-checkout-reconciliation-ops-488from
feat/stripe-webhook-trust-boundary-488

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Buyer and security impact

This bounded stacked #488 slice establishes a strict Stripe webhook trust boundary before any webhook delivery can influence subscription or entitlement state. It verifies exact raw bytes, enforces bounded body/signature/event contracts, and makes even a correctly signed delivery non-authoritative until durable deduplication and provider-state reconciliation exist.

Exact current stack and scope

  • Current parent: feat(billing): add auditable Checkout reconciliation operations #516 feat/stripe-checkout-reconciliation-ops-488@985747c4ed3dc369ee85df48f4923f5050c11209.
  • Current child head: 9c72a3adfdfaeaf826f694e5a25699341c3358fe.
  • The child is a non-destructive two-parent reconciliation of predecessor child a0c09334c58e5438c9b683ad381b0b30ad7eaf86 and exact current parent 985747c4ed3dc369ee85df48f4923f5050c11209.
  • Fresh exact parent→child comparison is ahead-only, zero behind, with the exact parent as merge base and eight bounded paths:
    • docs/doctoring/stripe-webhook-trust-boundary.md;
    • package.json;
    • server/app.mjs;
    • server/application_routes.mjs;
    • server/stripe_webhook.mjs;
    • tests/api/stripe-webhook.test.mjs;
    • tests/unit/coverage-script-contract.test.mjs;
    • tests/unit/stripe-webhook-boundary.test.mjs.
  • server/app.mjs is now a beginner-readable composition entry point: it restores protected /toast-state.css delivery first and mounts the webhook-hardened application route graph from server/application_routes.mjs. Both modules are registered in the owned-production coverage producer.
  • The semantic package merge preserves the parent’s OpenCode, changelog, contextual-orchestrator attribution/routing, Checkout attempt/reconciliation/provider, toast-accessibility, and cloud-E2E registrations while adding webhook API, unit, and production coverage.

No Clearfolio, attachment, general auth/session, dependency lockfile, workflow, scanner suppression, or branch-protection artifact is changed by the effective child diff.

Implemented trust boundary

  • verifies Stripe-Signature against the exact raw request bytes with HMAC-SHA-256 before JSON parsing;
  • requires configured STRIPE_WEBHOOK_SECRET and a bounded five-minute timestamp tolerance;
  • accepts only bounded, well-formed signature header components and uses timing-safe digest comparison;
  • enforces declared and streamed request-body byte ceilings before unbounded buffering;
  • uses fatal UTF-8 decoding and rejects malformed JSON, event identity, object shape, and timestamp values with stable sanitized errors;
  • returns Cache-Control: no-store from /api/stripe/webhook;
  • rejects unsigned, stale, malformed, mutated, and replayed deliveries;
  • acknowledges a correctly signed delivery only as verified input and does not directly grant Pro or mutate entitlements from webhook JSON;
  • registers the production verifier, composed route graph, and realistic route regression in the canonical c8 and unit/API suites; and
  • prevents silent removal through the coverage-script contract.

TDD and causal repair

The raw-body verifier and route regressions preceded the completed production implementation. The prior child inherited a real buyer-visible regression: its monolithic server/app.mjs removed the parent’s /toast-state.css static allowlist entry. Predecessor Server Tests run 32085643237, unit-and-api job 95557494897, reproduced the resulting 404 where the existing smoke contract required 200.

The current repair changes the causal ownership boundary instead of weakening the smoke test: the protected toast route is served by the public entry module before the child’s secure route graph is mounted. The monolithic child source is retained byte-for-byte as application_routes.mjs, so the hardened webhook handler is not rewritten, and both entry and route modules remain included in exact owned-production coverage.

Current exact-head evidence

Fresh repository-native workflows for exact child head 9c72a3adfdfaeaf826f694e5a25699341c3358fe are queued and therefore non-passing:

  • Server Tests 32271689983;
  • Dependency Review 32271690058; and
  • OSV Scanner 32271690653.

There are no submitted reviews or inline review threads on this exact head and no qualifying independent current-head approval. Predecessor successes and failures remain causal history only. Queued, pending, cancelled, skipped-required, neutral, absent, failed, stale, predecessor, status-only, synthetic-only, author-only, or model-only evidence is non-passing.

This PR remains Draft and must not integrate independently of #516, #511, #507, and #505.

Scope boundary

This PR does not implement durable webhook-event storage, event deduplication, authoritative subscription retrieval, out-of-order lifecycle reconciliation, or entitlement mutation. Those remain separate bounded #488 slices.

Merge gate

After the prerequisite stack reaches protected develop, reconcile this exact bounded semantic diff onto the resulting live head and rerun every then-applicable CI, browser E2E, coverage/docstring, SAST, security, dependency, supply-chain, package/provenance, migration/recovery, resolved-thread, and qualifying independent-approval gate. No predecessor-head, author-only, skipped, neutral, status-only, synthetic, or model-only evidence transfers.

Refs #488

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b1f214d1-bb15-49af-8b31-4a3e7ce1ffc3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae
seonghobae marked this pull request as ready for review August 16, 2026 02:21

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor Author

@opencode-agent Repair the verified current-stack regression on this existing branch only. Exact current head is a0c09334c58e5438c9b683ad381b0b30ad7eaf86; exact base #516 is 403c493ac914ab6a397d9504541978c037d6a733. Server Tests for downstream #521 run 32085883015 / job 95558196870 checked the synthetic merge of dde711f... into a0c093... and failed at tests/api/smoke.mjs:172: static /toast-state.css → 200, actual 404. Root cause is owned here: this PR semantically replaced server/app.mjs for the Stripe webhook route and accidentally deleted the exact parent STATIC allowlist entry '/toast-state.css': ['toast-state.css', 'text/css; charset=utf-8']. The current PR patch itself shows that deletion. Preserve the webhook verification changes and every exact-parent behavior; restore only that parent static allowlist behavior (do not weaken the smoke test, do not touch unrelated files, do not rebase/force-push). Treat the existing smoke assertion as the RED regression. After the smallest fix, run the focused API smoke plus the normal unit/API suite, and leave the branch fast-forwarded with the resulting exact head. Do not merge the PR.

@opencode-agent opencode-agent Bot added the area: api API, protocol, event, or external contract label Aug 22, 2026
@opencode-agent opencode-agent Bot added priority: medium Normal-priority or P2 work status: draft Draft pull request type: bug Defect or incorrect behavior labels Aug 22, 2026
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copy link
Copy Markdown
Contributor Author

Hour-20 exact-head review request.

Current head ea6c2c4f1628e7402e442ea23553b9e76f33b03b. Devin/CodeRabbit/author COMMENTED is not independent APPROVE. OpenCode DISMISSED/REQUEST_CHANGES on predecessor heads does not transfer. Ruleset 18156473 needs two independent APPROVE reviews on this exact SHA.

Keep HMAC successor. Do not restore unsigned plan upgrade (#589 stays closed).

Do not self-approve. Do not --admin merge. Checks/reviews are not a reason to weaken fail-closed gates.

Copy link
Copy Markdown
Contributor Author

Hour-21 exact-head review request.

Current head ea6c2c4f1628e7402e442ea23553b9e76f33b03b.
Devin/CodeRabbit/author COMMENTED is not independent APPROVE. Copilot review requests are not APPROVE. Ruleset 18156473 needs two independent approvals. Do not self-approve. Do not merge without two independent APPROVE reviews on this exact head.

@opencode-agent review

Copy link
Copy Markdown
Contributor Author

Hour-22 exact-head review request.

HMAC successor. Draft. Current head ea6c2c4f1628e7402e442ea23553b9e76f33b03b CLEAN vs stacked Stripe checkout base. Keep this HMAC successor. Do not restore unsigned plan upgrade (#589 stays closed).
Devin/CodeRabbit/author COMMENTED is not independent APPROVE. Ruleset 18156473 needs two independent approvals on this exact SHA. Do not self-approve.

@opencode-agent review

Copy link
Copy Markdown
Contributor Author

Hour-23: keep this HMAC successor. Draft head ea6c2c4f1628e7402e442ea23553b9e76f33b03b CLEAN. Do not restore unsigned plan upgrade (#589 CLOSED). Independent reviews still required before merge.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Exact-head re-verify 2026-09-02T09:44Z.

Current head ea6c2c4f1628e7402e442ea23553b9e76f33b03b. draft:true. mergeable_state clean. CLEAN is not two independent current-head APPROVEs. Zero reviews of any SHA on this PR before this COMMENT.

Keep this HMAC successor: server/stripe_webhook.mjs plus doctoring docs/doctoring/stripe-webhook-trust-boundary.md verify exact raw bytes and bounded body/signature/event before any delivery can move subscription or entitlement state. Do not restore unsigned plan upgrade. Do not treat a correctly signed delivery as authoritative until durable dedup and provider-state reconciliation exist. Do not un-draft to manufacture a merge.

This COMMENT is not an independent APPROVE. Do not merge without two independent current-head APPROVEs and exact-head Checks on this SHA. Ruleset 18156473. Copilot is a bot.

@seonghobae seonghobae added the bug Something isn't working label Sep 7, 2026 — with ChatGPT Codex Connector
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: api API, protocol, event, or external contract bug Something isn't working priority: medium Normal-priority or P2 work status: draft Draft pull request type: bug Defect or incorrect behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant