The core of hyperflask/flask-stripe-checkout is solid: webhook signatures are verified with stripe.Webhook.construct_event on the raw body, and no keys are committed. These are the edge cases that pass review and bite in production. Offered constructively to the maintainers and to anyone with similar code.
1. High: fulfilment on the success redirect, no payment check
cart_blueprint.py → checkout_success() retrieves the session on GET /cart/checkout/success?session_id=... and sends a checkout.session.success signal. It never checks session.payment_status == "paid", and the URL can be reloaded.
If you fulfil on that signal you can fulfil twice (reload) or fulfil delayed-payment orders (SEPA, ACH, Boleto) that later fail. Fix: check payment_status, and fulfil in checkout.session.completed / checkout.session.async_payment_succeeded webhooks.
2. Medium: webhook secret silently falls back to the Flask secret key
webhooks_endpoint_secret=app.config.get("STRIPE_WEBHOOKS_ENDPOINT_SECRET", webhooks_endpoint_secret) or app.secret_key
A Stripe signing secret is whsec_...; SECRET_KEY never matches, so a missing config means every webhook returns 400 with no hint. Fix: fail loudly at startup.
3. Medium: no event deduplication
dispatch_webhook_event_as_signal() dispatches every delivery. Stripe retries and may deliver the same event more than once. Fix: make handlers idempotent on event["id"] or offer an is_duplicate hook.
4. Medium: upgrade_subscription() crashes for customers without a subscription
subs = stripe.Subscription.list(customer=customer_id)
if not subs:
return subscription_checkout(...)
sub_item_id = subs["data"][0]["items"]["data"][0]["id"]
ListObject is a non-empty dict (object, data, has_more...), so it's always truthy: the new-subscription branch never runs and subs["data"][0] raises IndexError. Fix: if not subs.data:.
5. Low: missing Stripe-Signature header returns 500
request.headers["Stripe-Signature"] raises KeyError → HTTP 500 instead of 400. Use .get().
6. Low: no idempotency key on Checkout Session creation
Low risk (a duplicate is just an unused session), listed for completeness.
Full report with scanner output: sample audit. If you want the same review on your own public repo, it's a $39 done-for-you audit. Corrections welcome in the comments.
Top comments (0)