diff --git a/gateway/platforms/api_server.py b/gateway/platforms/api_server.py index 5414b3ec0487..7a33519ead40 100644 --- a/gateway/platforms/api_server.py +++ b/gateway/platforms/api_server.py @@ -1236,7 +1236,13 @@ class APIServerAdapter(BasePlatformAdapter): auth_header = request.headers.get("Authorization", "") if auth_header.startswith("Bearer "): token = auth_header[7:].strip() - if hmac.compare_digest(token, self._api_key): + # Compare as bytes: ``hmac.compare_digest`` raises TypeError on a + # str containing non-ASCII characters, and ``token`` is the raw + # client-supplied header. A stray non-ASCII byte in the key would + # otherwise crash this handler (500) instead of returning a clean + # 401. Encoding both sides keeps the timing-safe comparison and + # matches web_server.py's dashboard-token check. + if hmac.compare_digest(token.encode(), self._api_key.encode()): return None # Auth OK logger.warning( diff --git a/tests/gateway/test_api_server.py b/tests/gateway/test_api_server.py index abe8cdd64d34..dc8ff81a992d 100644 --- a/tests/gateway/test_api_server.py +++ b/tests/gateway/test_api_server.py @@ -526,6 +526,27 @@ class TestAuth: assert result is not None assert result.status == 401 + def test_non_ascii_bearer_token_returns_401_not_500(self): + """A non-ASCII byte in the bearer token must be rejected with 401, not + crash the handler: hmac.compare_digest raises TypeError on a str with + non-ASCII characters, and the token is raw client input.""" + config = PlatformConfig(enabled=True, extra={"key": "sk-test123"}) + adapter = APIServerAdapter(config) + mock_request = MagicMock() + mock_request.headers = {"Authorization": "Bearer ské-not-the-key"} + result = adapter._check_auth(mock_request) # must not raise + assert result is not None + assert result.status == 401 + + def test_non_ascii_key_config_still_authenticates(self): + """A non-ASCII configured key must still match its exact value byte for + byte (bytes comparison keeps this working).""" + config = PlatformConfig(enabled=True, extra={"key": "sk-tést-kéy"}) + adapter = APIServerAdapter(config) + mock_request = MagicMock() + mock_request.headers = {"Authorization": "Bearer sk-tést-kéy"} + assert adapter._check_auth(mock_request) is None + # --------------------------------------------------------------------------- # Concurrency cap (gateway.api_server.max_concurrent_runs) — #7483