diff --git a/tests/test_litellm/passthrough/test_passthrough_utils.py b/tests/test_litellm/passthrough/test_passthrough_utils.py deleted file mode 100644 index 55df39d6235..00000000000 --- a/tests/test_litellm/passthrough/test_passthrough_utils.py +++ /dev/null @@ -1,68 +0,0 @@ -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_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).""" - 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) diff --git a/tests/test_litellm/proxy/pass_through_endpoints/test_vertex_passthrough_load_balancing.py b/tests/test_litellm/proxy/pass_through_endpoints/test_vertex_passthrough_load_balancing.py index aaf1dad4910..08376c4c7b4 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/test_vertex_passthrough_load_balancing.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/test_vertex_passthrough_load_balancing.py @@ -1,5 +1,6 @@ from unittest.mock import AsyncMock, MagicMock, patch +import httpx import pytest from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import ( @@ -538,6 +539,81 @@ def test_forward_headers_from_request_protected_headers_not_overwritten(): assert "Anthropic-Beta" not in result +def test_forward_headers_drops_client_accept_encoding(): + """ + A client's Accept-Encoding must not reach the vendor API: the proxy can only + decode what httpx supports, and an undecodable body reaches clients compressed + with Content-Encoding stripped (LIT-5613). + """ + from litellm.passthrough.utils import BasePassthroughUtils + + result = 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 result} + assert result["anthropic-version"] == "2023-06-01" + assert "host" not in result + assert "content-length" not in result + + +def test_forward_headers_drops_accept_encoding_from_x_pass_prefix_and_custom_headers(): + """ + Neither the x-pass- mechanism nor endpoint-configured custom headers may + reintroduce an Accept-Encoding the proxy cannot decode. + """ + from litellm.passthrough.utils import BasePassthroughUtils + + via_prefix = BasePassthroughUtils.forward_headers_from_request( + request_headers={"x-pass-accept-encoding": "br"}, + headers={}, + forward_headers=False, + ) + via_custom_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 via_prefix + assert "accept-encoding" not in {name.lower() for name in via_custom_headers} + assert via_custom_headers["x-api-key"] == "sk-anthropic" + + +def test_forwarded_headers_only_advertise_encodings_httpx_can_decode(): + """ + The outbound request must negotiate a content coding httpx has a decoder for, + otherwise the vendor replies with a body the proxy passes through unreadable. + """ + from httpx._decoders import SUPPORTED_DECODERS + + from litellm.passthrough.utils import BasePassthroughUtils + + forwarded_headers = BasePassthroughUtils.forward_headers_from_request( + request_headers={"accept-encoding": "br, zstd, exotic"}, + headers={"x-api-key": "sk-anthropic"}, + forward_headers=True, + ) + with httpx.Client(headers={"user-agent": "litellm/test"}) as client: + upstream_request = client.build_request( + "POST", + "https://api.anthropic.com/v1/messages", + headers=forwarded_headers, + ) + + advertised = {value.strip().lower() for value in upstream_request.headers["accept-encoding"].split(",")} + + assert advertised + assert advertised <= set(SUPPORTED_DECODERS) + + def test_forward_headers_custom_wins_case_insensitive_over_request_authorization(): """ When forwarding request headers, provider-signed/custom headers must win