-
Notifications
You must be signed in to change notification settings - Fork 1
Fix critical security vulnerability in Stripe webhook handler #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
google-labs-jules
wants to merge
5
commits into
saas
Choose a base branch
from
security-fix-stripe-webhook-317896489714379337
base: saas
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
bcec57e
fix: secure stripe webhook secret selection 🔒
google-labs-jules[bot] bb18788
fix: secure stripe webhook secret selection 🔒
google-labs-jules[bot] beb2bb5
fix: secure stripe webhook secret selection 🔒
google-labs-jules[bot] ca9ce05
docs: remove fixed vulnerability from security audit 📝
google-labs-jules[bot] 391a3da
chore: remove regression test and temporary files 🧹
google-labs-jules[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # Security Audit Findings | ||
|
|
||
| ## Critical Issues | ||
|
|
||
| ### 1. Stripe Webhook Signature Bypass (FIXED) | ||
|
|
||
| **Severity:** Critical | ||
| **Description:** The Stripe webhook handler `_try_construct_event` was configured to accept payloads signed with the Test Secret even when the application was running in Production mode. This would allow an attacker knowing the Test Secret to forge events (e.g., subscription creation) against the Production environment. | ||
| **Fix:** The logic in `src/api/routes/payments/webhooks.py` was updated to strictly enforce secret usage based on the environment (`DEV_ENV`). Production only uses `STRIPE_WEBHOOK_SECRET`, and non-production environments use `STRIPE_TEST_WEBHOOK_SECRET`. | ||
|
|
||
| ## Low/Medium Issues | ||
|
|
||
| ### 1. `alert_admin` DB Session Handling | ||
| **Severity:** Low | ||
| **Description:** The `alert_admin` tool manually manages a database session obtained from `get_db_session` generator. While it correctly closes it, the generator remains suspended until garbage collection. | ||
| **Recommendation:** Ensure `get_db_session` is used as a context manager if possible, or refactor tool to use `scoped_session` similar to `agent_stream_endpoint`. | ||
|
|
||
| ### 2. Missing Rate Limiting on Webhooks | ||
| **Severity:** Low | ||
| **Description:** While signature verification prevents unauthorized payloads, the webhook endpoint is still exposed to DoS attacks. | ||
| **Recommendation:** Implement rate limiting (e.g., via Nginx or application middleware) for the webhook endpoint. | ||
|
|
||
| ### 3. Usage Reset Logic | ||
| **Severity:** Low | ||
| **Description:** The `handle_usage_reset_webhook` trusts `invoice.payment_succeeded` events to reset usage. Ensure idempotency keys are handled to prevent double-processing if Stripe retries webhooks (though resetting to 0 is idempotent-ish). | ||
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
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
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
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
Empty file.
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import os | ||
| import sys | ||
| import pytest | ||
| import stripe | ||
| import time | ||
| from unittest.mock import patch | ||
| from fastapi import HTTPException | ||
|
|
||
| # We need to add the root to sys.path to import src | ||
| sys.path.append(os.getcwd()) | ||
|
|
||
| # Import the module under test | ||
| from src.api.routes.payments.webhooks import _try_construct_event | ||
|
|
||
|
|
||
| def test_stripe_webhook_vuln_repro(): | ||
| """ | ||
| Verify that _try_construct_event REJECTS a payload signed with the | ||
| TEST secret when configured for PROD. | ||
| """ | ||
|
|
||
| # Setup test secrets | ||
| PROD_SECRET = "whsec_prod_secret_12345" | ||
| TEST_SECRET = "whsec_test_secret_67890" | ||
|
|
||
| # Mock global_config to simulate PROD environment | ||
| with patch("src.api.routes.payments.webhooks.global_config") as mock_config: | ||
| mock_config.configure_mock( | ||
| DEV_ENV="prod", | ||
| STRIPE_WEBHOOK_SECRET=PROD_SECRET, | ||
| STRIPE_TEST_WEBHOOK_SECRET=TEST_SECRET, | ||
| ) | ||
|
|
||
| # Create a payload | ||
| payload = b'{"id": "evt_test", "object": "event"}' | ||
| timestamp = int(time.time()) | ||
| header = f"t={timestamp},v1=dummy_signature" | ||
|
|
||
| # The function under test | ||
| try: | ||
| with patch("stripe.Webhook.construct_event") as mock_construct: | ||
| # This mock simulates: | ||
| # - Fails if secret is PROD_SECRET (simulating bad signature because payload signed with TEST) | ||
| # - Succeeds if secret is TEST_SECRET | ||
| def side_effect(payload, sig_header, secret): | ||
| if secret == TEST_SECRET: | ||
| return {"type": "test_event"} | ||
| raise stripe.error.SignatureVerificationError( | ||
| "Invalid signature", sig_header=sig_header, http_body=payload | ||
| ) | ||
|
|
||
| mock_construct.side_effect = side_effect | ||
|
|
||
| _try_construct_event(payload, header) | ||
|
|
||
| # If we get here, it means it accepted it | ||
| pytest.fail( | ||
| "Vulnerability STILL PRESENT: _try_construct_event accepted TEST secret in PROD mode!" | ||
| ) | ||
|
|
||
| except HTTPException as e: | ||
| # If it raises HTTPException(400), it means it rejected it (secure behavior) | ||
| assert e.status_code == 400 | ||
| assert e.detail == "Invalid signature" | ||
| print( | ||
| "\n[SUCCESS] _try_construct_event rejected TEST secret in PROD mode." | ||
| ) | ||
| except Exception as e: | ||
| pytest.fail(f"Unexpected error: {e}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| # Manually run the test function if executed as script | ||
| try: | ||
| test_stripe_webhook_vuln_repro() | ||
| except Exception as e: | ||
| print(e) |
Empty file.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.