fix(proxy): drop non-ASCII forwarded header values before calling the LLM provider

This commit is contained in:
Devin AI 2026-07-25 18:17:38 +00:00
parent 8ce365511f
commit 67aee5841c
2 changed files with 82 additions and 1 deletions

View file

@ -703,6 +703,29 @@ def clean_headers(
return clean_headers
def _is_transmittable_header(name: object, value: object) -> bool:
"""
Whether a header name/value pair can be forwarded over HTTP.
httpx (and Python's http.client) encode header values with a single-byte
codec, so a non-ASCII value such as CJK text raises UnicodeEncodeError deep
in the request stack and surfaces to the client as a 500. See
https://github.com/BerriAI/litellm/issues/34633
"""
if not isinstance(name, str):
return False
if isinstance(value, bytes):
return True
if not isinstance(value, str):
return False
try:
name.encode("ascii")
value.encode("ascii")
except UnicodeEncodeError:
return False
return True
class LiteLLMProxyRequestSetup:
@staticmethod
def _get_timeout_from_request(headers: dict) -> Optional[float]:
@ -875,7 +898,18 @@ class LiteLLMProxyRequestSetup:
else:
returned_headers["x-litellm-{}".format(k)] = str(v)
return returned_headers
transmittable_headers = {
name: value for name, value in returned_headers.items() if _is_transmittable_header(name, value)
}
dropped_headers = returned_headers.keys() - transmittable_headers.keys()
if dropped_headers:
verbose_logger.warning(
"Skipping non-ASCII request header(s) %s when forwarding to the LLM provider; "
"HTTP header values must be ASCII-encodable (see issue #34633)",
sorted(dropped_headers),
)
return transmittable_headers
@staticmethod
def add_headers_to_llm_call_by_model_group(data: dict, headers: dict, user_api_key_dict: UserAPIKeyAuth) -> dict:

View file

@ -27,6 +27,7 @@ from litellm.proxy.litellm_pre_call_utils import (
add_litellm_data_to_request,
check_if_token_is_service_account,
clean_headers,
_is_transmittable_header,
)
from litellm.types.utils import CredentialItem
@ -5402,6 +5403,52 @@ async def test_overwrite_user_with_key_hash_stamps_master_key_alias(monkeypatch)
assert updated_data["user"] == LITELLM_PROXY_MASTER_KEY_ALIAS
@pytest.mark.parametrize(
"name, value, expected",
[
("x-project", "acme", True),
("x-project", b"\xe4\xb8\xad", True),
("x-project", "中文", False),
("x-project", "café", False),
("x-中文", "acme", False),
("x-count", 5, False),
],
)
def test_is_transmittable_header(name, value, expected):
assert _is_transmittable_header(name, value) is expected
def test_add_headers_to_llm_call_drops_non_ascii_header_values():
"""
Regression for https://github.com/BerriAI/litellm/issues/34633
A forwarded header whose value contains non-ASCII characters must not reach
httpx/http.client (which encode header values with a single-byte codec and
raise UnicodeEncodeError -> 500). ASCII headers alongside it must survive.
"""
import httpx
non_ascii_wire_value = "中文".encode("utf-8")
headers = Headers(
raw=[
(b"x-keep", b"plain-ascii"),
(b"x-non-ascii", non_ascii_wire_value),
(b"anthropic-beta", b"prompt-caching-2024-07-31"),
]
)
user_api_key_dict = UserAPIKeyAuth(api_key="sk-test")
result = LiteLLMProxyRequestSetup.add_headers_to_llm_call(
headers, user_api_key_dict
)
assert result == {
"x-keep": "plain-ascii",
"anthropic-beta": "prompt-caching-2024-07-31",
}
httpx.Request("POST", "http://localhost:4000", headers=result)
@pytest.mark.asyncio
async def test_overwrite_user_with_key_hash_rejects_alias_without_marker(monkeypatch):
from litellm.constants import LITELLM_PROXY_MASTER_KEY_ALIAS