mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-15 09:12:18 +00:00
fix(guardrails): reserve the identity fields that live outside the key namespace
Attribution is not only the user_api_key_ namespace: pre-call processing also fills in the caller's ip address, user agent and agent id, and a caller could set those on /apply_guardrail and have a guardrail attribute the call to someone else. Reserve them by name alongside the namespace.
This commit is contained in:
parent
a5827b3a4e
commit
b1da2511cc
2 changed files with 35 additions and 6 deletions
|
|
@ -2349,18 +2349,25 @@ def _metadata_fields(value: object) -> Mapping[str, object]:
|
|||
|
||||
_CALLER_OWNED_KEY_FIELDS: Final[frozenset[str]] = frozenset({"user_api_key_end_user_id", "user_api_key_request_route"})
|
||||
|
||||
_RESERVED_IDENTITY_FIELDS: Final[frozenset[str]] = frozenset(
|
||||
{"requester_ip_address", "user_agent", "agent_id", "actor_agent_id"}
|
||||
)
|
||||
|
||||
|
||||
def _is_key_identity_field(key: str) -> bool:
|
||||
"""Whether a metadata field attributes the authenticated key rather than the request.
|
||||
"""Whether a metadata field attributes the authenticated caller rather than the request.
|
||||
|
||||
The ``user_api_key_`` namespace also carries two fields that describe the caller's own
|
||||
request: the end user it is made for, and the route it came from. A guardrail needs both
|
||||
to decide, and on ``/apply_guardrail`` the proxy's own values say nothing about the call
|
||||
the caller is asking about.
|
||||
Pre-call processing owns the whole ``user_api_key_`` namespace, except the two fields that
|
||||
describe the caller's own request: the end user it is made for, and the route it came from.
|
||||
A guardrail needs both to decide, and on ``/apply_guardrail`` the proxy's own values say
|
||||
nothing about the call the caller is asking about.
|
||||
|
||||
Attribution also reaches outside that namespace, so the fields pre-call processing fills in
|
||||
from the key and the connection are reserved by name as well.
|
||||
"""
|
||||
if key in _CALLER_OWNED_KEY_FIELDS:
|
||||
return False
|
||||
return key == "user_api_key" or key.startswith("user_api_key_")
|
||||
return key in _RESERVED_IDENTITY_FIELDS or key == "user_api_key" or key.startswith("user_api_key_")
|
||||
|
||||
|
||||
def _guardrail_request_metadata(
|
||||
|
|
|
|||
|
|
@ -2515,6 +2515,28 @@ class TestGuardrailRequestMetadata:
|
|||
"user_api_key_request_route": "/v1/chat/completions",
|
||||
}
|
||||
|
||||
def test_identity_fields_outside_the_key_namespace_are_reserved_too(self) -> None:
|
||||
"""The proxy sets who is calling and from where. A caller cannot claim another's."""
|
||||
metadata: Final = _guardrail_request_metadata(
|
||||
{
|
||||
"requester_ip_address": "203.0.113.9",
|
||||
"user_agent": "forged/1.0",
|
||||
"agent_id": "someone-elses-agent",
|
||||
"topic": "tax",
|
||||
},
|
||||
{
|
||||
"requester_ip_address": "198.51.100.4",
|
||||
"user_agent": "litellm-cli/2.3",
|
||||
"agent_id": "billing-agent",
|
||||
},
|
||||
)
|
||||
assert metadata == {
|
||||
"topic": "tax",
|
||||
"requester_ip_address": "198.51.100.4",
|
||||
"user_agent": "litellm-cli/2.3",
|
||||
"agent_id": "billing-agent",
|
||||
}
|
||||
|
||||
def test_a_guardrail_can_annotate_what_it_is_handed(self) -> None:
|
||||
"""OpenAI moderation and Presidio write their detection details into this mapping."""
|
||||
metadata: Final = _guardrail_request_metadata({}, {"user_api_key_alias": "billing-app"})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue