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.
This commit is contained in:
kshitijk4poor
2026-10-07 08:04:48 +05:30
committed by kshitij
parent d28c513904
commit b0ea19a9cd
2 changed files with 4 additions and 0 deletions

View File

@@ -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")

View File

@@ -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):