mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-08 22:25:25 +00:00
fix(proxy): read guardrail config from admin metadata, fix tag routing consistency
Read guardrail control flags (disable_global_guardrails, opted_out_global_guardrails) from admin-configured key metadata instead of the request body. This ensures callers cannot override admin security policies. Fix tag-based routing to enforce strict tag checks regardless of whether the request includes tags. Fix budget limiter to use the same dynamic metadata key resolution as the tag router for consistent tag extraction.
This commit is contained in:
@@ -257,24 +257,24 @@ class CustomGuardrail(CustomLogger):
|
||||
|
||||
def get_disable_global_guardrail(self, data: dict) -> Optional[bool]:
|
||||
"""
|
||||
Returns True if the global guardrail should be disabled
|
||||
Returns True if the global guardrail should be disabled.
|
||||
|
||||
Reads from admin-configured key/team metadata only, not from
|
||||
the request body, to prevent callers from disabling guardrails.
|
||||
"""
|
||||
if "disable_global_guardrails" in data:
|
||||
return data["disable_global_guardrails"]
|
||||
metadata = data.get("litellm_metadata") or data.get("metadata", {})
|
||||
if "disable_global_guardrails" in metadata:
|
||||
return metadata["disable_global_guardrails"]
|
||||
return False
|
||||
admin_metadata = metadata.get("user_api_key_metadata") or {}
|
||||
return admin_metadata.get("disable_global_guardrails", False)
|
||||
|
||||
def get_opted_out_global_guardrails_from_metadata(self, data: dict) -> List[str]:
|
||||
"""
|
||||
Returns the list of global guardrail names the team/key has opted out of.
|
||||
|
||||
Reads from admin-configured key/team metadata only.
|
||||
"""
|
||||
if "opted_out_global_guardrails" in data:
|
||||
value = data["opted_out_global_guardrails"]
|
||||
return value if isinstance(value, list) else []
|
||||
metadata = data.get("litellm_metadata") or data.get("metadata", {})
|
||||
value = metadata.get("opted_out_global_guardrails")
|
||||
admin_metadata = metadata.get("user_api_key_metadata") or {}
|
||||
value = admin_metadata.get("opted_out_global_guardrails")
|
||||
return value if isinstance(value, list) else []
|
||||
|
||||
def _is_valid_response_type(self, result: Any) -> bool:
|
||||
@@ -417,7 +417,9 @@ class CustomGuardrail(CustomLogger):
|
||||
"""
|
||||
requested_guardrails = self.get_guardrail_from_metadata(data)
|
||||
disable_global_guardrail = self.get_disable_global_guardrail(data)
|
||||
opted_out_global_guardrails = self.get_opted_out_global_guardrails_from_metadata(data)
|
||||
opted_out_global_guardrails = (
|
||||
self.get_opted_out_global_guardrails_from_metadata(data)
|
||||
)
|
||||
verbose_logger.debug(
|
||||
"inside should_run_guardrail for guardrail=%s event_type= %s guardrail_supported_event_hooks= %s requested_guardrails= %s self.default_on= %s",
|
||||
self.guardrail_name,
|
||||
@@ -426,7 +428,10 @@ class CustomGuardrail(CustomLogger):
|
||||
requested_guardrails,
|
||||
self.default_on,
|
||||
)
|
||||
if self.default_on is True and self.guardrail_name in opted_out_global_guardrails:
|
||||
if (
|
||||
self.default_on is True
|
||||
and self.guardrail_name in opted_out_global_guardrails
|
||||
):
|
||||
return False
|
||||
|
||||
if self.default_on is True and disable_global_guardrail is not True:
|
||||
|
||||
@@ -977,6 +977,12 @@ async def add_litellm_data_to_request( # noqa: PLR0915
|
||||
"Setting client-provided x-api-key as api_key parameter (will override deployment key)"
|
||||
)
|
||||
|
||||
# Strip internal pipeline state from user input
|
||||
for _meta_key in ("metadata", "litellm_metadata"):
|
||||
_user_meta = data.get(_meta_key)
|
||||
if isinstance(_user_meta, dict):
|
||||
_user_meta.pop("_pipeline_managed_guardrails", None)
|
||||
|
||||
##########################################################
|
||||
# Init - Proxy Server Request
|
||||
# we do this as soon as entering so we track the original request
|
||||
|
||||
@@ -29,6 +29,9 @@ from litellm.caching.redis_cache import RedisPipelineIncrementOperation
|
||||
from litellm.integrations.custom_logger import CustomLogger, Span
|
||||
from litellm.litellm_core_utils.duration_parser import duration_in_seconds
|
||||
from litellm.router_strategy.tag_based_routing import _get_tags_from_request_kwargs
|
||||
from litellm.litellm_core_utils.core_helpers import (
|
||||
get_metadata_variable_name_from_kwargs,
|
||||
)
|
||||
from litellm.router_utils.cooldown_callbacks import (
|
||||
_get_prometheus_logger_from_callbacks,
|
||||
)
|
||||
@@ -100,9 +103,9 @@ class RouterBudgetLimiting(CustomLogger):
|
||||
self.dual_cache = dual_cache
|
||||
self.redis_increment_operation_queue: List[RedisPipelineIncrementOperation] = []
|
||||
asyncio.create_task(self.periodic_sync_in_memory_spend_with_redis())
|
||||
self.provider_budget_config: Optional[
|
||||
GenericBudgetConfigType
|
||||
] = provider_budget_config
|
||||
self.provider_budget_config: Optional[GenericBudgetConfigType] = (
|
||||
provider_budget_config
|
||||
)
|
||||
self.deployment_budget_config: Optional[GenericBudgetConfigType] = None
|
||||
self.tag_budget_config: Optional[GenericBudgetConfigType] = None
|
||||
self._init_provider_budgets()
|
||||
@@ -175,7 +178,10 @@ class RouterBudgetLimiting(CustomLogger):
|
||||
spend_map=spend_map,
|
||||
potential_deployments=potential_deployments,
|
||||
request_tags=_get_tags_from_request_kwargs(
|
||||
request_kwargs=request_kwargs
|
||||
request_kwargs=request_kwargs,
|
||||
metadata_variable_name=get_metadata_variable_name_from_kwargs(
|
||||
request_kwargs or {}
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -333,7 +339,10 @@ class RouterBudgetLimiting(CustomLogger):
|
||||
# Check tag budgets
|
||||
if self.tag_budget_config:
|
||||
request_tags = _get_tags_from_request_kwargs(
|
||||
request_kwargs=request_kwargs
|
||||
request_kwargs=request_kwargs,
|
||||
metadata_variable_name=get_metadata_variable_name_from_kwargs(
|
||||
request_kwargs or {}
|
||||
),
|
||||
)
|
||||
for _tag in request_tags:
|
||||
_tag_budget_config = self._get_budget_config_for_tag(_tag)
|
||||
@@ -459,7 +468,10 @@ class RouterBudgetLimiting(CustomLogger):
|
||||
response_cost=response_cost,
|
||||
)
|
||||
|
||||
request_tags = _get_tags_from_request_kwargs(kwargs)
|
||||
request_tags = _get_tags_from_request_kwargs(
|
||||
kwargs,
|
||||
metadata_variable_name=get_metadata_variable_name_from_kwargs(kwargs or {}),
|
||||
)
|
||||
if len(request_tags) > 0:
|
||||
for _tag in request_tags:
|
||||
_tag_budget_config = self._get_budget_config_for_tag(_tag)
|
||||
|
||||
@@ -106,9 +106,7 @@ def _match_deployment(
|
||||
# the strict tag check has already failed (step 1 returned None). Allow
|
||||
# the regex to fire only when the deployment has NO plain tags, so we never
|
||||
# use regex as a backdoor around the operator's strict-tag policy.
|
||||
strict_tag_check_failed = (
|
||||
not match_any and bool(deployment_tags) and bool(request_tags)
|
||||
)
|
||||
strict_tag_check_failed = not match_any and bool(deployment_tags)
|
||||
if deployment_tag_regex and header_strings and not strict_tag_check_failed:
|
||||
regex_match = _is_valid_deployment_tag_regex(
|
||||
deployment_tag_regex, header_strings
|
||||
|
||||
Reference in New Issue
Block a user