From 1e02e695f2784fa594b9e30cb7e3805d625eb2d9 Mon Sep 17 00:00:00 2001 From: Jaeyeon Kim Date: Sat, 28 Feb 2026 02:10:27 +0900 Subject: [PATCH] feat: add include_litellm_headers setting to strip x-litellm-* response headers Co-Authored-By: Claude Opus 4.6 --- litellm/__init__.py | 1 + litellm/proxy/common_request_processing.py | 4 ++ .../proxy/test_common_request_processing.py | 67 +++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/litellm/__init__.py b/litellm/__init__.py index 50fa0e76755..8756c515680 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -351,6 +351,7 @@ anthropic_beta_headers_url: str = os.getenv( "https://raw.githubusercontent.com/BerriAI/litellm/main/litellm/anthropic_beta_headers_config.json", ) suppress_debug_info = False +include_litellm_headers: bool = True dynamodb_table_name: Optional[str] = None s3_callback_params: Optional[Dict] = None datadog_llm_observability_params: Optional[Union[DatadogLLMObsInitParams, Dict]] = None diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 1269f58213a..19b18d1cba0 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -395,6 +395,10 @@ class ProxyBaseLLMRequestProcessing: litellm_logging_obj: Optional[LiteLLMLoggingObj] = None, **kwargs, ) -> dict: + # When include_litellm_headers is False, skip all x-litellm-* headers + if litellm.include_litellm_headers is False: + return {} + exclude_values = {"", None, "None"} hidden_params = hidden_params or {} diff --git a/tests/test_litellm/proxy/test_common_request_processing.py b/tests/test_litellm/proxy/test_common_request_processing.py index 9b905d24fd1..84b614978cb 100644 --- a/tests/test_litellm/proxy/test_common_request_processing.py +++ b/tests/test_litellm/proxy/test_common_request_processing.py @@ -691,6 +691,73 @@ class TestProxyBaseLLMRequestProcessing: float(headers_7["x-litellm-key-spend"]) == 0.001 ) # Should use original spend on error + def test_get_custom_headers_included_by_default(self): + """ + Test that x-litellm-* headers are included by default + (backwards compatibility). + """ + mock_user_api_key_dict = MagicMock(spec=UserAPIKeyAuth) + mock_user_api_key_dict.tpm_limit = None + mock_user_api_key_dict.rpm_limit = None + mock_user_api_key_dict.max_budget = None + mock_user_api_key_dict.spend = 0.0 + + # Ensure default is True + original_value = litellm.include_litellm_headers + litellm.include_litellm_headers = True + try: + headers = ProxyBaseLLMRequestProcessing.get_custom_headers( + user_api_key_dict=mock_user_api_key_dict, + call_id="test-call-id", + model_id="test-model-id", + version="1.0.0", + ) + + assert "x-litellm-call-id" in headers + assert headers["x-litellm-call-id"] == "test-call-id" + assert "x-litellm-model-id" in headers + assert headers["x-litellm-model-id"] == "test-model-id" + assert "x-litellm-version" in headers + assert headers["x-litellm-version"] == "1.0.0" + finally: + litellm.include_litellm_headers = original_value + + def test_get_custom_headers_excluded_when_disabled(self): + """ + Test that x-litellm-* headers are excluded when + include_litellm_headers is set to False. + """ + mock_user_api_key_dict = MagicMock(spec=UserAPIKeyAuth) + mock_user_api_key_dict.tpm_limit = None + mock_user_api_key_dict.rpm_limit = None + mock_user_api_key_dict.max_budget = None + mock_user_api_key_dict.spend = 0.0 + + original_value = litellm.include_litellm_headers + litellm.include_litellm_headers = False + try: + headers = ProxyBaseLLMRequestProcessing.get_custom_headers( + user_api_key_dict=mock_user_api_key_dict, + call_id="test-call-id", + model_id="test-model-id", + version="1.0.0", + response_cost=0.001, + hidden_params={ + "_response_ms": 100.0, + "litellm_overhead_time_ms": 10.0, + }, + ) + + # All headers should be stripped + assert headers == {} + assert "x-litellm-call-id" not in headers + assert "x-litellm-model-id" not in headers + assert "x-litellm-version" not in headers + assert "x-litellm-response-cost" not in headers + assert "x-litellm-key-spend" not in headers + finally: + litellm.include_litellm_headers = original_value + @pytest.mark.asyncio async def test_queue_time_seconds_is_set_in_metadata(self, monkeypatch): """