diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index c622c85321d..a3a126bf2a6 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -212,6 +212,15 @@ _STANDARD_LOGGING_METADATA_KEYS: frozenset = frozenset( StandardLoggingMetadata.__annotations__.keys() ) +# Regex matching sensitive x-* headers that must NOT appear in +# requester_custom_headers (defense-in-depth — clean_headers() strips most +# of these upstream via _SPECIAL_HEADERS_CACHE). +_SENSITIVE_X_HEADER_RE = re.compile( + r"^(x-litellm-api-key|x-api-key|x-goog-api-key" + r"|x-mcp-(auth|servers|access-groups)" + r"|x-mcp-.+-(authorization|x-api-key))$" +) + ### GLOBAL VARIABLES ### # Cache custom pricing keys as frozenset for O(1) lookups instead of looping through 49 keys @@ -4768,18 +4777,13 @@ class StandardLoggingPayloadSetup: if proxy_server_request is not None: _request_headers = proxy_server_request.get("headers", {}) if _request_headers and isinstance(_request_headers, dict): - _sensitive_x_header = re.compile( - r"^(x-litellm-api-key|x-api-key|x-goog-api-key" - r"|x-mcp-(auth|servers|access-groups)" - r"|x-mcp-.+-(authorization|x-api-key))$" - ) custom_headers = { k: v for k, v in _request_headers.items() if k.lower().startswith("x-") and v is not None and isinstance(v, str) - and not _sensitive_x_header.match(k.lower()) + and not _SENSITIVE_X_HEADER_RE.match(k.lower()) } if custom_headers: clean_metadata["requester_custom_headers"] = custom_headers diff --git a/tests/mcp_tests/test_mcp_logging.py b/tests/mcp_tests/test_mcp_logging.py index 3432bbb5c73..006617b7f06 100644 --- a/tests/mcp_tests/test_mcp_logging.py +++ b/tests/mcp_tests/test_mcp_logging.py @@ -545,6 +545,11 @@ async def test_mcp_sensitive_headers_not_in_logging_callback(): raw_headers = { "authorization": "Bearer secret-token", "x-litellm-api-key": "sk-secret-key", + "x-api-key": "provider-api-key", + "x-goog-api-key": "google-api-key", + "x-mcp-auth": "legacy-mcp-auth", + "x-mcp-servers": "server1,server2", + "x-mcp-access-groups": "group1", "x-mcp-github-authorization": "Bearer gh-token", "x-mcp-zapier-x-api-key": "zapier-secret", "x-custom-safe-header": "safe-value", @@ -585,9 +590,15 @@ async def test_mcp_sensitive_headers_not_in_logging_callback(): # Sensitive headers must NOT appear assert "authorization" not in custom_headers - # x-litellm-api-key is stripped by clean_headers (SpecialHeaders) + # Auth headers stripped by clean_headers + regex denylist assert "x-litellm-api-key" not in custom_headers - # Server-specific MCP auth headers are filtered by regex + assert "x-api-key" not in custom_headers + assert "x-goog-api-key" not in custom_headers + # MCP config headers stripped by clean_headers + regex denylist + assert "x-mcp-auth" not in custom_headers + assert "x-mcp-servers" not in custom_headers + assert "x-mcp-access-groups" not in custom_headers + # Server-specific MCP auth headers filtered by regex assert "x-mcp-github-authorization" not in custom_headers assert "x-mcp-zapier-x-api-key" not in custom_headers