From b0ea19a9cd04ab9bb76749cc4de2ecb1aeb86c04 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Wed, 7 Oct 2026 08:04:48 +0530 Subject: [PATCH] fix(webhook): a whsec_ Svix secret that decodes to an empty or blank key fails closed _is_usable_secret only sees the encoded string, so "whsec_" (decodes to b"") and "whsec_ICAg" (decodes to three spaces) passed it and keyed the Svix HMAC with a public value: any caller could sign a request. Apply the same blank-key rule to the decoded key before computing the HMAC. --- gateway/platforms/webhook.py | 2 ++ tests/gateway/test_webhook_adapter.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/gateway/platforms/webhook.py b/gateway/platforms/webhook.py index fc886965a0..9cb204261d 100644 --- a/gateway/platforms/webhook.py +++ b/gateway/platforms/webhook.py @@ -174,6 +174,8 @@ def _validate_svix_signature(body: bytes, secret: str, msg_id: str, timestamp: s except (binascii.Error, ValueError): logger.debug("[webhook] Invalid whsec_ Svix signing secret") return False + if not key.strip(): # "whsec_" alone decodes to b"": a public HMAC key, same as a blank secret + return False else: # Some providers document Svix-style headers but hand out raw shared secrets. logger.debug("[webhook] Validating Svix-style signature with raw secret") diff --git a/tests/gateway/test_webhook_adapter.py b/tests/gateway/test_webhook_adapter.py index db0d1898f6..62e3abff2d 100644 --- a/tests/gateway/test_webhook_adapter.py +++ b/tests/gateway/test_webhook_adapter.py @@ -278,6 +278,8 @@ class TestValidateSignature: ("real-secret", "attacker-secret", b'{"a":1}', b'{"a":1}', False, False), ("real-secret", None, b'{"a":1}', b'{"a":2}', False, False), # tampered body ("real-secret", None, b'{"a":1}', b'{"a":1}', True, False), # replayed stale timestamp + ("whsec_", None, b'{"a":1}', b'{"a":1}', False, False), # decodes to an empty key + ("whsec_ICAg", None, b'{"a":1}', b'{"a":1}', False, False), # decodes to whitespace ], ) def test_standard_webhooks_headers_validate_like_svix(self, secret, sign_with, body, received, stale, expected):