Files
litellm/tests/test_litellm/integrations
Mateo WangandGitHub 812a2217ca [internal copy of #29511] feat(guardrails): add sensitive data routing to on-premise models (#29531)
* feat(guardrails): add sensitive data routing to on-premise models

When a guardrail detects sensitive data, route to an on-premise model
instead of blocking or redacting. All subsequent requests in that
session continue routing to the same model (sticky routing).

New config options for guardrails:
- on_sensitive_data: 'block' (default) or 'route'
- sensitive_data_route_to_model: target model for rerouting
- sticky_session_routing: persist routing for session (default: true)

New exception SensitiveDataRouteException triggers rerouting when raised
by guardrails. The proxy catches it, stores the routing decision in
cache, and modifies the request's model field.

New hook _PROXY_SensitiveDataRoutingHandler checks incoming requests
against cached routing decisions and applies sticky routing.

https://claude.ai/code/session_01SQd4isBa3UyouRoGVou9dK

* fix: black formatting for custom_guardrail.py

https://claude.ai/code/session_01SQd4isBa3UyouRoGVou9dK

* test: improve test coverage for sensitive data routing feature

Add additional tests for:
- Cache key format and TTL constants
- Session ID extraction from multiple locations
- Custom guardrail initialization with routing config
- Exception string representation and custom messages
- Redis cache paths including fallback behavior
- Edge cases in pre-call hook

https://claude.ai/code/session_01SQd4isBa3UyouRoGVou9dK

* fix: use correct GuardrailRaisedException parameters

Replace invalid 'source' parameter with 'guardrail_name' to match
the exception's actual signature.

https://claude.ai/code/session_01SQd4isBa3UyouRoGVou9dK

* test: move sensitive data routing tests to hooks directory

Move test file to align with source code structure.

https://claude.ai/code/session_01SQd4isBa3UyouRoGVou9dK

* fix(guardrails): honor sticky_session_routing flag and scope session routing per API key

Propagate sticky_session_routing through SensitiveDataRouteException so a
guardrail configured with sticky_session_routing=False reroutes only the
triggering request without persisting a session override. Scope the routing
cache key to the requesting API key so sessions from different tenants cannot
collide, and warn when sticky routing is requested but the hook is not
registered.

* refactor(guardrails): dedupe session-id extraction and drop redundant import

Extract the shared session-id lookup into get_session_id_from_request_data
so the sensitive-data routing hook and CustomGuardrail no longer keep two
identical copies of the logic. Remove the redundant local import of
GuardrailRaisedException in handle_sensitive_data_detection, and document
that detection_info is surfaced in request metadata and logs so it must not
carry raw sensitive values.

* fix(guardrails): guard None user_api_key_dict in sensitive data route handler

* fix(responses): send application/json Content-Type on responses DELETE

OpenAI's responses DELETE endpoint now rejects requests that arrive without
a Content-Type header, defaulting them to application/octet-stream and
returning 'Unsupported content type: application/octet-stream'. The delete
handler sent no body and therefore no Content-Type, so the request failed.
Declare application/json on the delete request, matching the OpenAI SDK.

* fix(guardrails): backfill in-memory cache after redis hit in sensitive data routing

When _get_routed_model resolves a routing override from Redis it now also
populates the local in-memory cache. Without the write-back, a non-writing
instance that only ever reads from Redis would lose the sticky routing
decision the moment Redis became unavailable, silently reverting sensitive
sessions to the default model.

* fix(guardrails): scope sticky sensitive-data routing to JWT principal

Keyless auth (JWT and similar) has no api_key, so every such caller shared
the "default" cache namespace. One authenticated user could reuse another
user's session_id, trip the guardrail, and silently force the other user's
subsequent requests onto the cached on-prem model for the TTL.

Resolve the routing tenant from the api_key when present, otherwise from a
stable principal built from the user/team/org identity, before reading or
writing the session route.

* fix(guardrails): require route target model when on_sensitive_data='route'

* fix(guardrails): mark user_api_key_dict Optional in sensitive-data route handler

* fix(guardrails): use remaining redis ttl for local backfill and str env default

* fix(guardrails): graceful block when routing configured but no session_id

handle_sensitive_data_detection promised to raise only SensitiveDataRouteException
or GuardrailRaisedException, but when routing was configured and the request had no
session_id it let a ValueError from raise_sensitive_data_route_exception propagate,
surfacing as an HTTP 500 instead of a block. Fall back to a graceful block in that
case so the documented contract holds.

* fix(guardrails): run remaining guardrails after sensitive-data reroute

Defer the SensitiveDataRouteException until every guardrail in the
pre-call loop has run, so downstream security guardrails are no longer
skipped when an earlier guardrail triggers routing. The first reroute
wins and a later guardrail that blocks still propagates.

Also normalize on_sensitive_data to lowercase like sibling on_* config
fields so case-insensitive values are accepted.

* fix(guardrails): classify sensitive-data reroute as guardrail intervention

* fix(guardrails): record sensitive-data reroute as prometheus intervention not error

* fix(guardrails): record service span for routing guardrail and move case-normalizer to base params

Drop the early continue so a guardrail that signals sensitive-data routing still
emits its PROXY_PRE_CALL service span like every other callback.

Move the lowercase normalizer onto BaseLitellmParams so on_sensitive_data is
normalized consistently when BaseLitellmParams is constructed directly, matching
the cross-field route->model validator that already lives on the base.
2026-06-04 22:22:28 -07:00
..
2026-06-01 21:42:51 -07:00
2026-05-01 15:34:11 -07:00
2026-06-03 11:01:51 -07:00