Source
Independent Forge (GPT-5.5) cross-vendor authority audit of the platform spine at main (commit 2be0a02). Verdict: CONCERNS.
Severity: HIGH (live binary trusts a constant webhook signature)
What
services/api/src/webhook.rs:130-136 (real StripeWebhookVerifier::verify is a stub that rejects everything), :93-100 (MockWebhookVerifier accepts the constant "mock_valid_signature", :42), wired into the live app() at services/api/src/lib.rs:95 and :117-123.
The default app() (the binary's router via main.rs:build_router) mounts MockWebhookVerifier, which accepts a hardcoded signature present in the source. The real StripeWebhookVerifier::verify returns Err(InvalidSignature) unconditionally and is wired nowhere.
Why it is a defect
As shipped, the running binary's webhook endpoint trusts a known constant, so anyone who reads the source can POST a forged checkout.session.completed and grant any account_id an active paid plan (apply_action -> set_account_state). The structure is honest (the real verifier fails closed, never silently accepts) and is the documented issue #32 deferral, but the live wiring is the mock.
Fix
Implement the real t/v1 HMAC-SHA256 plus constant-time compare in StripeWebhookVerifier::verify, and make the binary's app() select the real verifier whenever STRIPE_WEBHOOK_SECRET is present (mirror the /license/refresh env-gating so a configured deploy can never fall back to the mock). Add a fixture test with a real whsec_ secret. Until then the webhook reconcile path must be considered unauthenticated.
Note
Idempotency itself was verified correct (same event id twice = one effect). Security-sensitive webhook code: requires human review before merge.
Source
Independent Forge (GPT-5.5) cross-vendor authority audit of the platform spine at
main(commit2be0a02). Verdict: CONCERNS.Severity: HIGH (live binary trusts a constant webhook signature)
What
services/api/src/webhook.rs:130-136(realStripeWebhookVerifier::verifyis a stub that rejects everything),:93-100(MockWebhookVerifieraccepts the constant"mock_valid_signature",:42), wired into the liveapp()atservices/api/src/lib.rs:95and:117-123.The default
app()(the binary's router viamain.rs:build_router) mountsMockWebhookVerifier, which accepts a hardcoded signature present in the source. The realStripeWebhookVerifier::verifyreturnsErr(InvalidSignature)unconditionally and is wired nowhere.Why it is a defect
As shipped, the running binary's webhook endpoint trusts a known constant, so anyone who reads the source can POST a forged
checkout.session.completedand grant anyaccount_idan active paid plan (apply_action->set_account_state). The structure is honest (the real verifier fails closed, never silently accepts) and is the documented issue #32 deferral, but the live wiring is the mock.Fix
Implement the real
t/v1HMAC-SHA256 plus constant-time compare inStripeWebhookVerifier::verify, and make the binary'sapp()select the real verifier wheneverSTRIPE_WEBHOOK_SECRETis present (mirror the/license/refreshenv-gating so a configured deploy can never fall back to the mock). Add a fixture test with a realwhsec_secret. Until then the webhook reconcile path must be considered unauthenticated.Note
Idempotency itself was verified correct (same event id twice = one effect). Security-sensitive webhook code: requires human review before merge.