fix(security): widen non-ASCII compare_digest crash fix to all sibling sites

Same bug class as the salvaged #65305/#65307: hmac.compare_digest (and
secrets.compare_digest) raise TypeError when given a str containing
non-ASCII characters, and these call sites feed it raw request input.
Compare as UTF-8 bytes everywhere:

- gateway/platforms/msgraph_webhook.py: clientState from request body
- gateway/platforms/whatsapp_cloud.py: hub.verify_token query param +
  X-Hub-Signature-256 header (comment claimed 'works on str' — it
  doesn't for non-ASCII)
- plugins/platforms/feishu: verification token + x-lark-signature
- plugins/platforms/raft: bridge token header
- plugins/platforms/line: X-Line-Signature
- plugins/platforms/sms: X-Twilio-Signature
- tools/code_execution_tool.py: sandbox RPC token (both loops)

Regression tests for the two gateway-core sites (msgraph, whatsapp).
This commit is contained in:
Teknium 2026-07-16 06:47:36 -07:00
parent 4ccb232af9
commit a6d9d1d2cf
9 changed files with 76 additions and 13 deletions

View file

@ -316,8 +316,30 @@ class TestMSGraphNotifications:
assert calls, "hmac.compare_digest was never called; clientState check is not timing-safe"
provided, expected = calls[0]
assert provided == "expected-client-state"
assert expected == "expected-client-state"
assert provided == b"expected-client-state"
assert expected == b"expected-client-state"
@pytest.mark.anyio
async def test_non_ascii_client_state_rejected_without_raising(self):
"""A non-ASCII clientState (attacker-controlled request body) must be
rejected with 403, not crash the handler: hmac.compare_digest raises
TypeError on a str containing non-ASCII characters."""
adapter = _make_adapter()
payload = {
"value": [
{
"id": "notif-nonascii",
"subscriptionId": "sub-1",
"changeType": "updated",
"resource": "communications/onlineMeetings/meeting-x",
"clientState": "ské-not-the-secret",
}
]
}
response = await adapter._handle_notification(
_FakeRequest(json_payload=payload)
)
assert response.status == 403
@pytest.mark.anyio
async def test_duplicate_notification_deduped(self):

View file

@ -353,6 +353,22 @@ class TestWebhookVerify:
assert response.status == 403
@pytest.mark.asyncio
async def test_verify_rejects_non_ascii_token_without_raising(self):
"""A non-ASCII verify_token (raw query param) must be rejected with
403, not crash the handler: hmac.compare_digest raises TypeError on a
str containing non-ASCII characters."""
adapter = _make_adapter(verify_token="shared-secret-123")
request = _verify_request({
"hub.mode": "subscribe",
"hub.verify_token": "ské-not-the-secret",
"hub.challenge": "abc-12345",
})
response = await adapter._handle_verify(request)
assert response.status == 403
@pytest.mark.asyncio
async def test_verify_rejects_wrong_mode(self):
adapter = _make_adapter(verify_token="shared-secret-123")