Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion litellm/integrations/custom_guardrail.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import secrets
from datetime import datetime
from typing import (
Expand All @@ -17,6 +18,7 @@
from litellm.litellm_core_utils.core_helpers import redact_nested_match_and_regex_keys
from litellm.caching import DualCache
from litellm.integrations.custom_logger import CustomLogger
from litellm.secret_managers.main import str_to_bool
from litellm.types.guardrails import (
DynamicGuardrailParams,
GuardrailEventHooks,
Expand Down Expand Up @@ -59,6 +61,20 @@
_PRE_CALL_EXECUTED_TOKEN = secrets.token_hex(16)


def _strict_guardrail_modes_enabled() -> bool:
"""Whether guardrail-mode validation raises (default) or logs a warning.

Set `LITELLM_STRICT_GUARDRAIL_MODES=false` to keep the pre-LIT-4226 behavior
for guardrails whose supported_event_hooks list newly includes their
configured mode: log the mismatch and continue instead of raising at boot.
"""
raw = os.environ.get("LITELLM_STRICT_GUARDRAIL_MODES")
if raw is None:
return True
parsed = str_to_bool(raw)
return True if parsed is None else parsed


def get_session_id_from_request_data(request_data: Dict[str, Any]) -> Optional[str]:
"""Extract session_id from request data (litellm_session_id or metadata)."""
session_id = request_data.get("litellm_session_id")
Expand Down Expand Up @@ -132,7 +148,17 @@ def __init__(

if supported_event_hooks:
## validate event_hook is in supported_event_hooks
self._validate_event_hook(event_hook, supported_event_hooks)
try:
self._validate_event_hook(event_hook, supported_event_hooks)
except ValueError as validation_error:
if _strict_guardrail_modes_enabled():
raise
verbose_logger.warning(
"%s. LITELLM_STRICT_GUARDRAIL_MODES=false; continuing "
"with unsupported event_hook. Set the env var to true "
"(default) to enforce validation and fail at startup.",
validation_error,
)
super().__init__(**kwargs)

def render_violation_message(self, default: str, context: Optional[Dict[str, Any]] = None) -> str:
Expand Down Expand Up @@ -303,6 +329,18 @@ def get_config_model() -> Optional[Type["GuardrailConfigModel"]]:
"""
return None

@classmethod
def get_supported_event_hooks(cls) -> Optional[List[GuardrailEventHooks]]:
"""
Returns the event hooks this guardrail supports, for the UI to render.

Subclasses should override to return their supported hooks list. When a
subclass returns None, the endpoint omits it from the per-provider map
and the UI is expected to fall back to the global `supported_modes`
list client-side.
"""
return None

def _validate_event_hook(
self,
event_hook: Optional[Union[GuardrailEventHooks, List[GuardrailEventHooks], Mode]],
Expand Down
7 changes: 6 additions & 1 deletion litellm/integrations/rubrik.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import urllib.parse
import uuid
from collections import Counter
from typing import TYPE_CHECKING, Any, Literal, Optional
from typing import TYPE_CHECKING, Any, List, Literal, Optional

import httpx
from litellm._logging import verbose_logger
Expand Down Expand Up @@ -52,6 +52,10 @@ class _MalformedToolBlockingResponseError(Exception):


class RubrikLogger(CustomGuardrail, CustomBatchLogger):
@classmethod
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
return [GuardrailEventHooks.pre_call, GuardrailEventHooks.post_call]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rubrik pre_call mode no-op

Medium Severity

get_supported_event_hooks for Rubrik now includes pre_call, so the Admin UI and strict mode validation treat that mode as supported. apply_guardrail returns immediately without checking tools when input_type is request, which is what runs for pre_call. A Rubrik guardrail saved as pre_call passes startup validation but never runs tool blocking.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e2e3767. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

residual gap


def __init__(
self,
api_key: str | None = None,
Expand All @@ -69,6 +73,7 @@ def __init__(
kwargs["event_hook"] = kwargs.get("event_hook") or GuardrailEventHooks.post_call
if kwargs.get("default_on") is None:
kwargs["default_on"] = True
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
super().__init__(
flush_lock=self.flush_lock,
**kwargs,
Expand Down
24 changes: 15 additions & 9 deletions litellm/proxy/guardrails/guardrail_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -1296,21 +1296,27 @@ async def get_guardrail_ui_settings():
get_available_content_categories,
get_pattern_metadata,
)
from litellm.proxy.guardrails.guardrail_registry import guardrail_class_registry

# Convert the PII_ENTITY_CATEGORIES_MAP to the format expected by the UI
category_maps = []
for category, entities in PII_ENTITY_CATEGORIES_MAP.items():
category_maps.append(
{
"category": category.value,
"entities": [entity.value for entity in entities],
}
)
category_maps = [
{
"category": category.value,
"entities": [entity.value for entity in entities],
}
for category, entities in PII_ENTITY_CATEGORIES_MAP.items()
]

supported_modes_by_provider = {
provider: [hook.value for hook in hooks]
for provider, guardrail_class in guardrail_class_registry.items()
if (hooks := guardrail_class.get_supported_event_hooks()) is not None
}

return GuardrailUIAddGuardrailSettings(
supported_entities=[entity.value for entity in PiiEntityType],
supported_actions=[action.value for action in PiiAction],
supported_modes=[mode.value for mode in GuardrailEventHooks],
supported_modes_by_provider=supported_modes_by_provider,
pii_entity_categories=category_maps,
content_filter_settings={
"prebuilt_patterns": get_pattern_metadata(),
Expand Down
12 changes: 11 additions & 1 deletion litellm/proxy/guardrails/guardrail_hooks/aim/aim.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import asyncio
import json
import os
from typing import TYPE_CHECKING, Any, AsyncGenerator, Optional, Type, Union
from typing import TYPE_CHECKING, Any, AsyncGenerator, List, Optional, Type, Union

from pydantic import BaseModel
from websockets.asyncio.client import ClientConnection, connect
Expand All @@ -26,6 +26,7 @@
build_inspection_messages,
has_non_string_content,
)
from litellm.types.guardrails import GuardrailEventHooks
from litellm.types.utils import (
CallTypesLiteral,
Choices,
Expand All @@ -44,7 +45,16 @@ class AimGuardrailMissingSecrets(Exception):


class AimGuardrail(CustomGuardrail):
@classmethod
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
return [
GuardrailEventHooks.pre_call,
GuardrailEventHooks.during_call,
GuardrailEventHooks.post_call,
]

def __init__(self, api_key: Optional[str] = None, api_base: Optional[str] = None, **kwargs):
Comment thread
greptile-apps[bot] marked this conversation as resolved.
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
ssl_verify = kwargs.pop("ssl_verify", None)
self.async_handler = get_async_httpx_client(
llm_provider=httpxSpecialProvider.GuardrailCallback,
Expand Down
14 changes: 9 additions & 5 deletions litellm/proxy/guardrails/guardrail_hooks/akto/akto.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import json
import os
from datetime import datetime
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Tuple, Type
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Type

from fastapi import HTTPException

Expand Down Expand Up @@ -52,6 +52,13 @@ def get_config_model() -> Type["GuardrailConfigModel"]:

return AktoConfigModel

@classmethod
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
return [
GuardrailEventHooks.pre_call,
GuardrailEventHooks.post_call,
]

def __init__(
self,
akto_base_url: Optional[str] = None,
Expand Down Expand Up @@ -90,10 +97,7 @@ def __init__(
self.akto_account_id = akto_account_id or os.environ.get("AKTO_ACCOUNT_ID", "1000000")
self.akto_vxlan_id = akto_vxlan_id or os.environ.get("AKTO_VXLAN_ID", "0")

kwargs["supported_event_hooks"] = [
GuardrailEventHooks.pre_call,
GuardrailEventHooks.post_call,
]
kwargs["supported_event_hooks"] = list(self.get_supported_event_hooks())
super().__init__(**kwargs)

verbose_proxy_logger.debug(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,15 @@


class AporiaGuardrail(CustomGuardrail):
@classmethod
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
return [
GuardrailEventHooks.during_call,
GuardrailEventHooks.post_call,
]

def __init__(self, api_key: Optional[str] = None, api_base: Optional[str] = None, **kwargs):
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
self.async_handler = get_async_httpx_client(llm_provider=httpxSpecialProvider.GuardrailCallback)
self.aporia_api_key = api_key or os.environ["APORIO_API_KEY"]
self.aporia_api_base = api_base or os.environ["APORIO_API_BASE"]
Expand Down
16 changes: 9 additions & 7 deletions litellm/proxy/guardrails/guardrail_hooks/azure/prompt_shield.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CustomGuardrail,
log_guardrail_information,
)
from litellm.types.guardrails import GuardrailEventHooks
from litellm.types.utils import CallTypesLiteral

from .base import AzureGuardrailBase
Expand Down Expand Up @@ -47,19 +48,13 @@ def __init__(
**kwargs,
):
"""Initialize Azure Prompt Shield guardrail handler."""
from litellm.types.guardrails import GuardrailEventHooks

supported_event_hooks = [
GuardrailEventHooks.pre_call,
GuardrailEventHooks.during_call,
]
# AzureGuardrailBase.__init__ stores api_key, api_base, api_version,
# async_handler and forwards the rest to CustomGuardrail.
super().__init__(
api_key=api_key,
api_base=api_base,
guardrail_name=guardrail_name,
supported_event_hooks=supported_event_hooks,
supported_event_hooks=list(self.get_supported_event_hooks()),
**kwargs,
)

Expand Down Expand Up @@ -149,3 +144,10 @@ def get_config_model() -> Optional[Type["GuardrailConfigModel"]]:
)

return AzurePromptShieldGuardrailConfigModel

@classmethod
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
return [
GuardrailEventHooks.pre_call,
GuardrailEventHooks.during_call,
]
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
log_guardrail_information,
)
from litellm.proxy._types import UserAPIKeyAuth
from litellm.types.guardrails import GuardrailEventHooks
from litellm.types.utils import CallTypesLiteral

from .base import AzureGuardrailBase
Expand Down Expand Up @@ -42,6 +43,13 @@ class AzureContentSafetyTextModerationGuardrail(AzureGuardrailBase, CustomGuardr

default_severity_threshold: int = 2

@classmethod
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
return [
GuardrailEventHooks.pre_call,
GuardrailEventHooks.post_call,
]

def __init__(
self,
guardrail_name: str,
Expand All @@ -56,6 +64,7 @@ def __init__(
AzureTextModerationRequestBodyOptionalParams,
)

kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
# AzureGuardrailBase.__init__ stores api_key, api_base, api_version,
# async_handler and forwards the rest to CustomGuardrail.
super().__init__(
Expand Down
19 changes: 11 additions & 8 deletions litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,7 @@ def __init__(
"""

# Set supported event hooks to include MCP hooks
if "supported_event_hooks" not in kwargs:
kwargs["supported_event_hooks"] = [
GuardrailEventHooks.pre_call,
GuardrailEventHooks.post_call,
GuardrailEventHooks.during_call,
GuardrailEventHooks.pre_mcp_call,
GuardrailEventHooks.during_mcp_call,
]
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))

super().__init__(**kwargs)
BaseAWSLLM.__init__(self)
Expand All @@ -184,6 +177,16 @@ def __init__(
self.guardrailVersion,
)

@classmethod
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
return [
GuardrailEventHooks.pre_call,
GuardrailEventHooks.post_call,
GuardrailEventHooks.during_call,
GuardrailEventHooks.pre_mcp_call,
GuardrailEventHooks.during_mcp_call,
]

def _create_bedrock_input_content_request(self, messages: Optional[List[AllMessageValues]]) -> BedrockRequest:
"""
Create a bedrock request for the input content - the LLM request.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,11 +351,7 @@ def __init__(
_event_hook = GuardrailEventHooks(event_hook)
super().__init__(
guardrail_name=guardrail_name or "block_code_execution",
supported_event_hooks=[
GuardrailEventHooks.pre_call,
GuardrailEventHooks.post_call,
GuardrailEventHooks.during_call,
],
supported_event_hooks=list(self.get_supported_event_hooks()),
event_hook=_event_hook
or [
GuardrailEventHooks.pre_call,
Expand All @@ -378,6 +374,14 @@ def get_config_model() -> Optional[type[GuardrailConfigModel]]:

return BlockCodeExecutionGuardrailConfigModel

@classmethod
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
return [
GuardrailEventHooks.pre_call,
GuardrailEventHooks.post_call,
GuardrailEventHooks.during_call,
]

def _find_blocks(self, text: str) -> List[Tuple[int, int, str, str, float, CodeBlockActionTaken]]:
"""
Find all fenced code blocks in text. Returns list of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import json
import os
import ssl
from typing import TYPE_CHECKING, Any, AsyncGenerator, Optional, Type, Union
from typing import TYPE_CHECKING, Any, AsyncGenerator, List, Optional, Type, Union

from fastapi import HTTPException
from pydantic import BaseModel
Expand All @@ -30,6 +30,7 @@
apply_redacted_messages_back,
build_inspection_messages,
)
from litellm.types.guardrails import GuardrailEventHooks
from litellm.types.utils import (
CallTypesLiteral,
Choices,
Expand All @@ -49,7 +50,16 @@ class CatoNetworksGuardrailMissingSecrets(Exception):


class CatoNetworksGuardrail(CustomGuardrail):
@classmethod
def get_supported_event_hooks(cls) -> List[GuardrailEventHooks]:
return [
GuardrailEventHooks.pre_call,
GuardrailEventHooks.during_call,
GuardrailEventHooks.post_call,
]

def __init__(self, api_key: Optional[str] = None, api_base: Optional[str] = None, **kwargs):
kwargs.setdefault("supported_event_hooks", list(self.get_supported_event_hooks()))
ssl_verify = kwargs.pop("ssl_verify", None)
self.async_handler = get_async_httpx_client(
llm_provider=httpxSpecialProvider.GuardrailCallback,
Expand Down
Loading
Loading