fix: Separate an unreadable webhook payload from a forged one - #644
Merged
Conversation
razor-x
force-pushed
the
claude/python-sdk-audit-o2iid9-11-webhook
branch
2 times, most recently
from
August 28, 2026 20:43
0e85c8f to
daca433
Compare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y1RzepycXEYA3LStfjt8cY
razor-x
force-pushed
the
claude/python-sdk-audit-o2iid9-11-webhook
branch
from
August 28, 2026 21:26
daca433 to
838912a
Compare
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.
What
SeamWebhook.verifyconflated every failure with forgery — and had zero tests (SDK audit finding M5; ports PHP #469).Before this PR, a correctly-signed but unparseable payload leaked a raw
json.JSONDecodeErrorfrom inside svix, a signed non-event payload (null,[1],{}) crashed withAttributeErrorinseam_event_from_dict, and the README'sexcept Exception: return 400mapped all of it to a signature failure — so Svix retried a permanently-bad payload across its full backoff schedule while operators investigated a phantom forgery.SeamInvalidWebhookPayloadError(SeamError): raised only after verification succeeds, for a payload that is not valid JSON or does not contain a Seam event (event_id/event_type). Genuinely from Seam, permanently unreadable — a bug report, not a retry.SeamWebhookVerificationErrorbecomes a real class subclassing both svix'sWebhookVerificationErrorandSeamError(it was a bare re-export alias of the svix type, outside the SDK hierarchy).verifyre-raises svix failures as it, withraise ... from. Fully backward compatible:exceptclauses on the old alias or on svix's type still catch it — and it now also satisfiesexcept SeamError.svix-idvsSVIX-IDwith different values → verification error; identical values dedupe). A malformed signature/timestamp header that leaks a bareValueErrorout of the verifier is mapped to a verification error too.event_typestill parses to the forward-compatible fallback, unchanged.except Exception, with guidance on which to let Svix retry.Depends on #634 (SeamError base; contains its commit).
Testing
New
test/seam_webhook_test.py— the first webhook tests in this SDK (19 tests), minting real signatures with svix's ownWebhook.sign: valid event; mixed-case headers; tampered payload; wrong secret; expired timestamp; each missing header; conflicting and identical duplicate headers; malformed signature header; signed-but-unparseable JSON; signed non-events (null,[1],42,"event",{}); unknown event type fallback; hierarchy assertions.Revert check: with the old
seam_webhook.py, the tests fail with the audit's exact symptoms — rawjson.decoder.JSONDecodeErrorandAttributeError: 'NoneType' object has no attribute 'get'.Full suite: 206 passed; mypy, pylint (10.00), black, rstcheck clean.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Y1RzepycXEYA3LStfjt8cY
Generated by Claude Code