fix(passthrough): stop forwarding client Accept-Encoding upstream

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
mateo 2026-08-14 21:11:29 +00:00
parent f03df1bb42
commit 367f00315f
2 changed files with 78 additions and 10 deletions

View file

@ -18,12 +18,26 @@ _PASS_THROUGH_PROTECTED_HEADERS: Final[frozenset] = frozenset(
"x-goog-api-key",
"host",
"content-length",
"accept-encoding",
}
)
# Header name prefix used to block AWS SigV4 signing headers from being overridden.
_PASS_THROUGH_PROTECTED_HEADER_PREFIXES: Final[tuple] = ("x-amz-",)
# Client headers that must never reach the upstream provider. `accept-encoding` is
# dropped so httpx negotiates a content coding it actually has a decoder for: an
# encoding it cannot decode (e.g. brotli, absent the optional `brotli` package)
# leaves the body compressed while get_response_headers strips Content-Encoding,
# handing the client bytes it has no way to read.
_NON_FORWARDED_REQUEST_HEADERS: Final[frozenset[str]] = frozenset(
{
"content-length",
"host",
"accept-encoding",
}
)
class BasePassthroughUtils:
@staticmethod
@ -66,17 +80,14 @@ class BasePassthroughUtils:
e.g., 'x-pass-anthropic-beta: value' becomes 'anthropic-beta: value'
"""
if forward_headers is True:
# Header We Should NOT forward
request_headers.pop("content-length", None)
request_headers.pop("host", None)
custom_header_names: Final = {header_name.lower() for header_name in headers}
for header_name in list(request_headers.keys()):
if header_name.lower() in custom_header_names:
request_headers.pop(header_name, None)
# Combine request headers with custom headers
headers = {**request_headers, **headers}
forwardable_headers: Final = {
header_name: header_value
for header_name, header_value in request_headers.items()
if header_name.lower() not in _NON_FORWARDED_REQUEST_HEADERS
and header_name.lower() not in custom_header_names
}
headers = {**forwardable_headers, **headers}
# Process x-pass- prefixed headers (strip prefix and forward)
# Credential and protocol-level headers are excluded from this mechanism.

View file

@ -0,0 +1,57 @@
import httpx
from httpx._decoders import SUPPORTED_DECODERS
from litellm.passthrough.utils import BasePassthroughUtils
ANTHROPIC_MESSAGES_URL = "https://api.anthropic.com/v1/messages"
def _build_upstream_request(forwarded_headers: dict) -> httpx.Request:
with httpx.Client(headers={"user-agent": "litellm/test"}) as client:
return client.build_request("POST", ANTHROPIC_MESSAGES_URL, headers=forwarded_headers)
def test_client_accept_encoding_is_not_forwarded_upstream():
headers = BasePassthroughUtils.forward_headers_from_request(
request_headers={
"accept-encoding": "br",
"anthropic-version": "2023-06-01",
"host": "localhost:4000",
"content-length": "123",
},
headers={"x-api-key": "sk-anthropic"},
forward_headers=True,
)
assert "accept-encoding" not in {name.lower() for name in headers}
assert headers["anthropic-version"] == "2023-06-01"
assert "host" not in headers
assert "content-length" not in headers
def test_client_accept_encoding_is_not_forwarded_via_x_pass_prefix():
headers = BasePassthroughUtils.forward_headers_from_request(
request_headers={"x-pass-accept-encoding": "br"},
headers={},
forward_headers=False,
)
assert "accept-encoding" not in headers
def test_upstream_request_only_advertises_decodable_encodings():
"""A content coding httpx cannot decode would reach the client still compressed,
with Content-Encoding stripped by get_response_headers (LIT-5613)."""
forwarded_headers = BasePassthroughUtils.forward_headers_from_request(
request_headers={"accept-encoding": "br, zstd, exotic"},
headers={"x-api-key": "sk-anthropic"},
forward_headers=True,
)
advertised = {
value.strip().lower()
for value in _build_upstream_request(forwarded_headers).headers["accept-encoding"].split(",")
}
assert advertised
assert advertised <= set(SUPPORTED_DECODERS)