mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-15 23:31:29 +00:00
fix(proxy): drop non-ASCII forwarded header values before calling the LLM provider
This commit is contained in:
parent
8ce365511f
commit
67aee5841c
2 changed files with 82 additions and 1 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue