fix(api-server): reject a non-ASCII bearer token with 401 instead of crashing

_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.
This commit is contained in:
Drexuxux 2026-07-16 04:31:49 +03:00 committed by Teknium
parent 27b31bb7ff
commit efb6c21498
2 changed files with 28 additions and 1 deletions

View file

@ -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(

View file

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