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:
@@ -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")
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user