From 02b45552b923922ad241ba7383e45074248392ac Mon Sep 17 00:00:00 2001 From: mateo Date: Fri, 14 Aug 2026 21:58:25 +0000 Subject: [PATCH] fix(passthrough): drop configured Accept-Encoding custom headers too Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/passthrough/utils.py | 24 +++++++++---------- .../passthrough/test_passthrough_utils.py | 11 +++++++++ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/litellm/passthrough/utils.py b/litellm/passthrough/utils.py index 01ed8221198..31e1c0a7821 100644 --- a/litellm/passthrough/utils.py +++ b/litellm/passthrough/utils.py @@ -1,4 +1,5 @@ from collections.abc import Mapping +from itertools import chain from typing import Final from urllib.parse import parse_qs @@ -27,7 +28,7 @@ _PASS_THROUGH_PROTECTED_HEADER_PREFIXES: Final[tuple] = ("x-amz-",) # `accept-encoding` is dropped so httpx negotiates a coding it has a decoder for: one it # cannot decode leaves the body compressed while Content-Encoding is stripped downstream. -_NON_FORWARDED_REQUEST_HEADERS: Final[frozenset[str]] = frozenset( +_NON_FORWARDED_HEADERS: Final[frozenset[str]] = frozenset( { "content-length", "host", @@ -76,15 +77,14 @@ class BasePassthroughUtils: with the prefix stripped, regardless of forward_headers setting. e.g., 'x-pass-anthropic-beta: value' becomes 'anthropic-beta: value' """ - if forward_headers is True: - custom_header_names: Final = {header_name.lower() for header_name in 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} + custom_header_names: Final = {header_name.lower() for header_name in headers} + client_headers: Final = request_headers.items() if forward_headers is True else () + merged_headers: Final = { + header_name: header_value + for header_name, header_value in chain(client_headers, headers.items()) + if header_name.lower() not in _NON_FORWARDED_HEADERS + and (header_name in headers or header_name.lower() not in custom_header_names) + } # Process x-pass- prefixed headers (strip prefix and forward) # Credential and protocol-level headers are excluded from this mechanism. @@ -100,9 +100,9 @@ class BasePassthroughUtils: header_name, ) continue - headers[actual_header_name] = header_value + merged_headers[actual_header_name] = header_value - return headers + return merged_headers class CommonUtils: diff --git a/tests/test_litellm/passthrough/test_passthrough_utils.py b/tests/test_litellm/passthrough/test_passthrough_utils.py index ad01c9d42da..55df39d6235 100644 --- a/tests/test_litellm/passthrough/test_passthrough_utils.py +++ b/tests/test_litellm/passthrough/test_passthrough_utils.py @@ -39,6 +39,17 @@ def test_client_accept_encoding_is_not_forwarded_via_x_pass_prefix(): assert "accept-encoding" not in headers +def test_configured_custom_accept_encoding_is_dropped(): + headers = BasePassthroughUtils.forward_headers_from_request( + request_headers={}, + headers={"x-api-key": "sk-anthropic", "Accept-Encoding": "br"}, + forward_headers=False, + ) + + assert "accept-encoding" not in {name.lower() for name in headers} + assert headers["x-api-key"] == "sk-anthropic" + + 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)."""