Skip to content

fix(compliance-cli-python): verify HMAC over raw bytes, not a re-serialized parse#64

Open
ucekmez wants to merge 1 commit into
mainfrom
fix/python-compliance-cli-rawbody-hmac
Open

fix(compliance-cli-python): verify HMAC over raw bytes, not a re-serialized parse#64
ucekmez wants to merge 1 commit into
mainfrom
fix/python-compliance-cli-rawbody-hmac

Conversation

@ucekmez

@ucekmez ucekmez commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Audit finding (conformance vertical, #17): the Python compliance CLI verified
the webhook HMAC over json.dumps(parsed_body) — re-serializing the parse
instead of hashing the bytes the publisher signed. Key order, whitespace, and
unicode escaping differ, so the check produced false "signature mismatch"
results against perfectly conformant publishers. It also mishandled rotated
multi-signature headers via str.replace("v1,", "").

The TypeScript CLI already does this correctly (verifyWebhookSignature over
raw bytes); this brings the Python tooling to parity.

Change

  • Capture the exact received body bytes (_received_raw_body) in the webhook
    receiver, separate from the parsed JSON.
  • New tested verify_webhook_signature() helper mirroring the TS one:
    raw-byte signed content, space-separated v1 token rotation, timing-safe
    digest comparison, and structured reasons (ok / ok_via_multi_signature /
    missing_secret / malformed_header / no_v1_token / signature_mismatch).
  • Removed the now-unused inline hashlib/hmac/base64 imports.

Tests

tests/test_helpers.py: 25 passed. New TestVerifyWebhookSignature covers the
happy path over raw bytes, a regression test proving a re-serialized body now
fails
(the original bug), multi-signature rotation, and every reason branch.

Part of the EEP vertical-audit follow-up (Wave 1). Python-only; no wire/schema
change.

🤖 Generated with Claude Code

…alized parse

The Python compliance CLI computed the webhook HMAC over json.dumps() of
the *parsed* body, so key order, whitespace, and unicode escaping differed
from the bytes the publisher actually signed — producing false "signature
mismatch" results against conformant publishers. It also mishandled rotated
multi-signature headers via str.replace("v1,", "").

Capture the exact received body bytes in the receiver and verify through a
new, tested verify_webhook_signature() helper that mirrors the TypeScript
verifyWebhookSignature: raw-byte signed content, space-separated v1 token
rotation, timing-safe digest comparison, and structured reasons (ok /
ok_via_multi_signature / missing_secret / malformed_header / no_v1_token /
signature_mismatch). Brings the Python conformance tooling to parity with TS.

Signed-off-by: Ugur Cekmez <ucekmez@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant