From efb6c214983880e3a8a54fcf3253f6ad9af9c155 Mon Sep 17 00:00:00 2001 From: Drexuxux Date: Thu, 16 Jul 2026 04:31:49 +0300 Subject: [PATCH] fix(api-server): reject a non-ASCII bearer token with 401 instead of crashing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _check_auth gates every OpenAI-compatible API server endpoint. It compared the client's raw bearer token against the configured key with hmac.compare_digest on two str values. compare_digest raises TypeError on a str containing non-ASCII characters, and the token comes straight from the Authorization header — so a request with a single non-ASCII byte in the key (a stray unicode char, a smart quote, a pasted BOM) crashed the handler with an unhandled TypeError. Every endpoint calls _check_auth without a try/except, so the framework turned that into a 500 Internal Server Error instead of the intended 401 Invalid API key. Compare as bytes, matching web_server.py's dashboard-token check (hmac.compare_digest(auth.encode(), expected.encode())). Encoding both sides keeps the timing-safe comparison and its semantics identical for valid keys while making a non-ASCII token fail closed with a clean 401. Adds regression tests: a non-ASCII bearer token returns 401 (no raise), and a non-ASCII configured key still authenticates against its exact value. --- gateway/platforms/api_server.py | 8 +++++++- tests/gateway/test_api_server.py | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) 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