mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
Forward allowlisted x-litellm-api-key on A2A hops
_forwarding_headers stripped every x-litellm-* name, so an admin extra_headers entry for x-litellm-api-key never reached the agent. Only mint reserved identity headers (user/team/trace id).
This commit is contained in:
parent
22b36cbcf6
commit
1cd284cdd5
2 changed files with 59 additions and 1 deletions
|
|
@ -172,6 +172,18 @@ async def _resolve_backend_auth_header(
|
|||
return await resolve_a2a_hop_auth_header(litellm_params, custom_llm_provider)
|
||||
|
||||
|
||||
# Identity / hop headers LiteLLM mints itself. Drop client-supplied copies (any
|
||||
# casing) so they cannot sit beside the authenticated values. Other x-litellm-*
|
||||
# names, including an admin-allowlisted x-litellm-api-key, still pass through.
|
||||
_RESERVED_X_LITELLM_HEADERS: Final = frozenset(
|
||||
{
|
||||
"x-litellm-user-id",
|
||||
"x-litellm-team-id",
|
||||
"x-litellm-trace-id",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _forwarding_headers(
|
||||
caller_identity: Mapping[str, str],
|
||||
request_data: Mapping[str, object],
|
||||
|
|
@ -180,10 +192,11 @@ def _forwarding_headers(
|
|||
) -> dict[str, str] | None:
|
||||
backend_auth: Final = tuple(backend_auth_header.items()) if backend_auth_header else ()
|
||||
minted_names: Final = frozenset(name.lower() for name, _ in backend_auth)
|
||||
blocked: Final = _RESERVED_X_LITELLM_HEADERS | minted_names
|
||||
passthrough: Final = tuple(
|
||||
(name, value)
|
||||
for name, value in (agent_extra_headers.items() if agent_extra_headers else ())
|
||||
if not name.lower().startswith("x-litellm-") and name.lower() not in minted_names
|
||||
if name.lower() not in blocked
|
||||
)
|
||||
trace_id: Final = request_data.get("litellm_trace_id")
|
||||
trace: Final = (("X-LiteLLM-Trace-Id", str(trace_id)),) if trace_id else ()
|
||||
|
|
|
|||
|
|
@ -2684,3 +2684,48 @@ def test_forwarding_headers_minted_bearer_replaces_a_forwarded_authorization_of_
|
|||
)
|
||||
|
||||
assert merged == {"X-Custom": "kept", "Authorization": "Bearer minted-token"}
|
||||
|
||||
|
||||
def test_forwarding_headers_keeps_allowlisted_x_litellm_api_key():
|
||||
"""Admin-configured extra_headers may include x-litellm-api-key. That name is not a minted
|
||||
caller-identity header, so it must reach the backend agent (#43450)."""
|
||||
from litellm.proxy.agent_endpoints.a2a_endpoints import _forwarding_headers
|
||||
|
||||
merged = _forwarding_headers(
|
||||
caller_identity={"X-LiteLLM-User-Id": "u1", "X-LiteLLM-Team-Id": "t1"},
|
||||
request_data={},
|
||||
agent_extra_headers={
|
||||
"authorization": "Bearer client",
|
||||
"x-litellm-api-key": "sk-forward-me",
|
||||
"x-litellm-user-id": "attacker",
|
||||
},
|
||||
backend_auth_header=None,
|
||||
)
|
||||
|
||||
assert merged is not None
|
||||
assert merged.get("x-litellm-api-key") == "sk-forward-me"
|
||||
assert merged.get("authorization") == "Bearer client"
|
||||
assert merged.get("X-LiteLLM-User-Id") == "u1"
|
||||
assert merged.get("X-LiteLLM-Team-Id") == "t1"
|
||||
# Spoofed lowercase identity must not sit beside the minted header.
|
||||
assert "x-litellm-user-id" not in merged
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("method", ["message/send", "message/stream"])
|
||||
async def test_message_methods_forward_allowlisted_x_litellm_api_key(method: str):
|
||||
agent = _make_agent_mock()
|
||||
agent.extra_headers = ["authorization", "x-litellm-api-key"]
|
||||
mock_request = _make_request_mock(method, _HELLO_MESSAGE_PARAMS)
|
||||
mock_request.headers = {
|
||||
"authorization": "Bearer client-key",
|
||||
"x-litellm-api-key": "sk-allowlisted",
|
||||
}
|
||||
user_api_key_dict = UserAPIKeyAuth(api_key="sk-test", user_id="u1", team_id="t1")
|
||||
|
||||
captured = await _invoke_message_method(method, mock_request, user_api_key_dict, agent=agent)
|
||||
|
||||
forwarded = captured.agent_extra_headers or {}
|
||||
assert forwarded.get("authorization") == "Bearer client-key"
|
||||
assert forwarded.get("x-litellm-api-key") == "sk-allowlisted"
|
||||
assert forwarded.get("X-LiteLLM-Team-Id") == "t1"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue