diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 1d3ef2e10c2..064538ef3bf 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -1500,10 +1500,13 @@ def add_guardrails_from_policy_engine( Add guardrails from the policy engine based on request context. This function: - 1. Gets matching policies based on team_alias, key_alias, and model - 2. Resolves guardrails from matching policies (including inheritance) - 3. Adds guardrails to request metadata - 4. Tracks applied policies in metadata for response headers + 1. Extracts "policies" from request body (if present) for dynamic policy application + 2. Gets matching policies based on team_alias, key_alias, and model (via attachments) + 3. Combines dynamic policies with attachment-based policies + 4. Resolves guardrails from all policies (including inheritance) + 5. Adds guardrails to request metadata + 6. Tracks applied policies in metadata for response headers + 7. Removes "policies" from request body so it's not forwarded to LLM provider Args: data: The request data to update @@ -1519,6 +1522,10 @@ def add_guardrails_from_policy_engine( from litellm.proxy.policy_engine.policy_resolver import PolicyResolver from litellm.types.proxy.policy_engine import PolicyMatchContext + # Extract dynamic policies from request body (if present) + # These will be combined with attachment-based policies + request_body_policies = data.pop("policies", None) + registry = get_policy_registry() verbose_proxy_logger.debug( f"Policy engine: registry initialized={registry.is_initialized()}, " @@ -1545,12 +1552,18 @@ def add_guardrails_from_policy_engine( verbose_proxy_logger.debug(f"Policy engine: matched policies via attachments: {matching_policy_names}") - if not matching_policy_names: + # Combine attachment-based policies with dynamic request body policies + all_policy_names = set(matching_policy_names) + if request_body_policies and isinstance(request_body_policies, list): + all_policy_names.update(request_body_policies) + verbose_proxy_logger.debug(f"Policy engine: added dynamic policies from request body: {request_body_policies}") + + if not all_policy_names: return # Filter to only policies whose conditions match the context applied_policy_names = PolicyMatcher.get_policies_with_matching_conditions( - policy_names=matching_policy_names, + policy_names=list(all_policy_names), context=context, ) diff --git a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py index b9485a2e4cb..da6a5aeab09 100644 --- a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py +++ b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py @@ -1548,3 +1548,50 @@ def test_add_guardrails_from_policy_engine(): policy_registry._initialized = False attachment_registry._attachments = [] attachment_registry._initialized = False + + +def test_add_guardrails_from_policy_engine_accepts_dynamic_policies_and_pops_from_data(): + """ + Test that add_guardrails_from_policy_engine accepts dynamic 'policies' from the request body + and removes them to prevent forwarding to the LLM provider. + + This is critical because 'policies' is a LiteLLM proxy-specific parameter that should + not be sent to the actual LLM API (e.g., OpenAI, Anthropic, etc.). + """ + from litellm.proxy.policy_engine.policy_registry import get_policy_registry + + # Setup test data with 'policies' in the request body + data = { + "model": "gpt-4", + "messages": [{"role": "user", "content": "Hello"}], + "policies": ["PII-POLICY-GLOBAL", "HIPAA-POLICY"], # Dynamic policies - should be accepted and removed + "metadata": {}, + } + + user_api_key_dict = UserAPIKeyAuth( + api_key="test-key", + team_alias="test-team", + key_alias="test-key", + ) + + # Initialize empty policy registry (we're just testing the accept and pop behavior) + policy_registry = get_policy_registry() + policy_registry._policies = {} + policy_registry._initialized = False + + # Call the function - should accept dynamic policies and not raise an error + add_guardrails_from_policy_engine( + data=data, + metadata_variable_name="metadata", + user_api_key_dict=user_api_key_dict, + ) + + # Verify that 'policies' was removed from the request body + assert "policies" not in data, "'policies' should be removed from request body to prevent forwarding to LLM provider" + + # Verify that other fields are preserved + assert "model" in data + assert data["model"] == "gpt-4" + assert "messages" in data + assert data["messages"] == [{"role": "user", "content": "Hello"}] + assert "metadata" in data