fix: add headers to metadata for guardrails on pass-through endpoints (#17992)

Fixes #17477

Guardrails couldn't access request headers (like User-Agent) on Bedrock
pass-through endpoints because headers were only stored in
data["proxy_server_request"]["headers"] but not in data["metadata"]["headers"]
where guardrails typically look for them.

This fix adds headers to metadata in add_litellm_data_to_request() so
guardrails can access User-Agent, API keys, and other header-based checks
on all endpoints including Bedrock pass-through.

Test added to verify headers are available in metadata for guardrails.
This commit is contained in:
Nicolai van der Smagt 2025-12-16 09:11:34 +01:00 • committed by GitHub
parent 50606bf090
commit 09ba89d4fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 75 additions and 0 deletions

View file

@ -843,6 +843,11 @@ async def add_litellm_data_to_request( # noqa: PLR0915
)
)
# Add headers to metadata for guardrails to access (fixes #17477)
# Guardrails use metadata["headers"] to access request headers (e.g., User-Agent)
if _metadata_variable_name in data and isinstance(data[_metadata_variable_name], dict):
data[_metadata_variable_name]["headers"] = _headers
# check for forwardable headers
data = LiteLLMProxyRequestSetup.add_headers_to_llm_call_by_model_group(
data=data, headers=_headers, user_api_key_dict=user_api_key_dict

View file

@ -1884,3 +1884,73 @@ async def test_bedrock_router_passthrough_metadata_initialization():
# Verify response was returned
assert result == mock_response
@pytest.mark.asyncio
async def test_add_litellm_data_to_request_adds_headers_to_metadata():
"""
Test that add_litellm_data_to_request adds headers to metadata for guardrails.
This test verifies the fix for issue #17477 where guardrails couldn't access
request headers (like User-Agent) on Bedrock pass-through endpoints.
The fix ensures headers are available in data["metadata"]["headers"] so
guardrails can validate User-Agent, API keys, and other header-based checks.
"""
from litellm.proxy.litellm_pre_call_utils import add_litellm_data_to_request
from litellm.proxy._types import UserAPIKeyAuth
# Create mock request with headers including User-Agent
mock_request = MagicMock(spec=Request)
mock_request.method = "POST"
mock_request.url = MagicMock()
mock_request.url.path = "/bedrock/model/my-model/converse"
mock_request.headers = Headers(
{
"content-type": "application/json",
"user-agent": "claude-cli/2.0.69 (external, cli)",
"authorization": "Bearer sk-test-key",
"x-custom-header": "test-value",
}
)
mock_request.query_params = QueryParams({})
# Create mock user API key dict
mock_user_api_key_dict = UserAPIKeyAuth()
# Create mock proxy config
mock_proxy_config = MagicMock()
mock_proxy_config.pass_through_endpoints = []
# Initial data dict (simulating Bedrock pass-through)
data = {
"model": "my-bedrock-model",
"messages": [{"role": "user", "content": "Hello"}],
}
# Call add_litellm_data_to_request
result = await add_litellm_data_to_request(
data=data,
request=mock_request,
user_api_key_dict=mock_user_api_key_dict,
proxy_config=mock_proxy_config,
general_settings={},
version="1.0",
)
# Verify headers are added to metadata for guardrails
assert "metadata" in result, "metadata should be present in result"
assert "headers" in result["metadata"], "headers should be present in metadata"
assert isinstance(
result["metadata"]["headers"], dict
), "headers should be a dictionary"
# Verify specific headers are accessible (important for guardrails)
headers = result["metadata"]["headers"]
assert (
"user-agent" in headers or "User-Agent" in headers
), "User-Agent header should be accessible in metadata"
# Also verify proxy_server_request has headers (original location)
assert "proxy_server_request" in result
assert "headers" in result["proxy_server_request"]