feat: add claude_code guardrails + provider-agnostic block_hosted_tools

Adds three new guardrail hooks for Claude Code proxy deployments:

**block_hosted_tools** (provider-agnostic, new top-level hook)
- Blocks platform-executed hosted tools from Anthropic, OpenAI, and Gemini
- Per-provider YAML configs (anthropic.yaml, openai.yaml, gemini.yaml) define
  tool type prefixes (Anthropic versioned types), exact type strings (OpenAI),
  and native top-level keys (Gemini googleSearch, codeExecution, etc.)
- Registered as guardrail type: "block_hosted_tools"

**claude_code_prompt_cache**
- Auto-injects cache_control: {type: ephemeral} into system messages
- Only applies to Anthropic API models (detected via get_llm_provider)
- Reuses AnthropicCacheControlHook._safe_insert_cache_control_in_message

**claude_code_block_expensive_flags**
- Blocks speed=fast (~6x pricing), inference_geo, thinking.type=enabled
- Config-driven via expensive_api_flags.yaml; inherits Anthropic hosted tool
  prefixes from block_hosted_tools/anthropic.yaml via inherit_from

Policy templates updated:
- claude-code-pii-protection: uses block_hosted_tools + litellm_content_filter
- claude-code-cost-optimization: uses prompt_cache + block_expensive_flags

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krrish Dholakia 2026-02-17 17:21:40 -08:00
parent 371cabfebd
commit a05b7a7747
12 changed files with 928 additions and 18 deletions

View file

@ -769,6 +769,81 @@
"guardrails_remove": []
}
},
{
"id": "claude-code-pii-protection",
"title": "Claude Code PII Protection",
"description": "Prevents data leakage in Claude Code sessions by blocking all Anthropic-hosted tools (bash, web_search, web_fetch, code_execution, memory, text_editor) and masking PII and credentials found in request messages.",
"icon": "ShieldCheckIcon",
"iconColor": "text-indigo-500",
"iconBg": "bg-indigo-50",
"guardrails": [
"claude-code-block-hosted-tools",
"claude-code-pii-credentials",
"claude-code-pii-personal-data"
],
"complexity": "Medium",
"guardrailDefinitions": [
{
"guardrail_name": "claude-code-block-hosted-tools",
"litellm_params": {
"guardrail": "block_hosted_tools",
"mode": "pre_call"
},
"guardrail_info": {
"description": "Blocks platform-hosted tools from Anthropic (bash, web_search, web_fetch, code_execution, memory, text_editor, tool_search), OpenAI (code_interpreter, file_search, web_search_preview), and Gemini (googleSearch, code_execution, url_context). Only custom tool schemas are permitted."
}
},
{
"guardrail_name": "claude-code-pii-credentials",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "aws_access_key", "action": "BLOCK"},
{"pattern_type": "prebuilt", "pattern_name": "aws_secret_key", "action": "BLOCK"},
{"pattern_type": "prebuilt", "pattern_name": "github_token", "action": "BLOCK"},
{"pattern_type": "prebuilt", "pattern_name": "slack_token", "action": "BLOCK"},
{"pattern_type": "prebuilt", "pattern_name": "generic_api_key", "action": "BLOCK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "Blocks requests containing credentials or API keys (AWS, GitHub, Slack) that could be exfiltrated through the LLM context."
}
},
{
"guardrail_name": "claude-code-pii-personal-data",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "us_ssn", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "email", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "us_phone", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "credit_card", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "visa", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "mastercard", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "iban", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "ipv4", "action": "MASK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "Masks SSNs, email addresses, phone numbers, credit card numbers, IBANs, and IP addresses before they enter the LLM context."
}
}
],
"templateData": {
"policy_name": "claude-code-pii-protection",
"description": "Prevents data leakage in Claude Code sessions. Blocks Anthropic-hosted tools and masks PII and credentials in request messages.",
"guardrails_add": [
"claude-code-block-hosted-tools",
"claude-code-pii-credentials",
"claude-code-pii-personal-data"
],
"guardrails_remove": []
}
},
{
"id": "eu-ai-act-article5",
"title": "EU AI Act Article 5 — Prohibited Practices",
@ -1000,6 +1075,50 @@
"guardrails_remove": []
}
},
{
"id": "claude-code-cost-optimization",
"title": "Claude Code Cost Optimization",
"description": "Reduces Claude Code API spend by blocking expensive inference modes (fast/turbo, inference_geo, and extended thinking) and automatically injecting prompt caching headers into system messages to maximize cache hit rates.",
"icon": "CurrencyDollarIcon",
"iconColor": "text-green-500",
"iconBg": "bg-green-50",
"guardrails": [
"claude-code-inject-prompt-cache",
"claude-code-block-expensive-flags"
],
"complexity": "Low",
"guardrailDefinitions": [
{
"guardrail_name": "claude-code-inject-prompt-cache",
"litellm_params": {
"guardrail": "claude_code_prompt_cache",
"mode": "pre_call"
},
"guardrail_info": {
"description": "Automatically adds cache_control: {type: ephemeral} to system messages so Anthropic can cache the system prompt prefix. Only applies to Anthropic API models. Reduces cost on repeated calls that share the same system prompt."
}
},
{
"guardrail_name": "claude-code-block-expensive-flags",
"litellm_params": {
"guardrail": "claude_code_block_expensive_flags",
"mode": "pre_call"
},
"guardrail_info": {
"description": "Blocks expensive Anthropic API flags: fast/turbo inference (speed=fast, ~6x pricing), inference geo-routing (inference_geo, 1.1x pricing), and extended thinking (thinking.type=enabled). Also blocks Anthropic-hosted tools inherited from the hosted tools policy."
}
}
],
"templateData": {
"policy_name": "claude-code-cost-optimization",
"description": "Reduces Claude Code API spend by blocking fast mode, inference_geo, extended thinking, and Anthropic-hosted tools, while auto-injecting prompt caching into system messages.",
"guardrails_add": [
"claude-code-inject-prompt-cache",
"claude-code-block-expensive-flags"
],
"guardrails_remove": []
}
},
{
"id": "prompt-injection-detection",
"title": "Prompt Injection Detection",

View file

@ -0,0 +1,43 @@
"""
Block Hosted Tools guardrail for LiteLLM.
Blocks platform-executed server-side tools from Anthropic, OpenAI, and Gemini.
Provider tool lists are maintained in per-provider YAML files (anthropic.yaml,
openai.yaml, gemini.yaml) alongside this module.
"""
from typing import TYPE_CHECKING
import litellm
from litellm.types.guardrails import SupportedGuardrailIntegrations
from .guardrail import BlockHostedToolsGuardrail
if TYPE_CHECKING:
from litellm.types.guardrails import Guardrail, LitellmParams
def initialize_guardrail(
litellm_params: "LitellmParams", guardrail: "Guardrail"
) -> BlockHostedToolsGuardrail:
guardrail_name = guardrail.get("guardrail_name")
if not guardrail_name:
raise ValueError("BlockHostedToolsGuardrail requires a guardrail_name")
instance = BlockHostedToolsGuardrail(
guardrail_name=guardrail_name,
event_hook=litellm_params.mode,
default_on=litellm_params.default_on,
)
litellm.logging_callback_manager.add_litellm_callback(instance)
return instance
guardrail_initializer_registry = {
SupportedGuardrailIntegrations.BLOCK_HOSTED_TOOLS.value: initialize_guardrail,
}
guardrail_class_registry = {
SupportedGuardrailIntegrations.BLOCK_HOSTED_TOOLS.value: BlockHostedToolsGuardrail,
}
__all__ = ["BlockHostedToolsGuardrail", "initialize_guardrail"]

View file

@ -0,0 +1,24 @@
# Anthropic server-side hosted tools.
# Tools are matched by prefix against the tool 'type' field, because Anthropic
# appends a version date suffix (e.g. bash_20250124, web_search_20250305).
#
# Reference: https://docs.anthropic.com/en/docs/agents-and-tools/computer-use
provider: "anthropic"
description: "Anthropic server-side hosted tools executed on Anthropic infrastructure"
tool_type_prefixes:
- prefix: "bash_"
description: "Bash shell execution tool"
- prefix: "web_search_"
description: "Web search tool"
- prefix: "web_fetch_"
description: "Web fetch / URL retrieval tool"
- prefix: "code_execution_"
description: "Code execution sandbox tool"
- prefix: "memory_"
description: "Memory read/write tool"
- prefix: "text_editor_"
description: "Text editor tool"
- prefix: "tool_search_"
description: "Tool search / discovery tool"

View file

@ -0,0 +1,35 @@
# Google Gemini / Vertex AI platform-hosted tools executed on Google infrastructure.
# Gemini tools appear in two formats in the API:
# - OpenAI-compat: tool dict has 'type' set to the value below
# - Gemini native: tool dict has a top-level key matching the tool name
# Both formats are checked.
#
# Reference: https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/function-calling
provider: "gemini"
description: "Google Gemini / Vertex AI platform-hosted tools"
# Matched against the 'type' field (OpenAI-compat format)
tool_type_exact:
- type: "code_execution"
description: "Code execution sandbox (OpenAI-compat format)"
- type: "url_context"
description: "URL fetching and context grounding (OpenAI-compat format)"
# Matched as top-level dict keys (Gemini native format)
# e.g. {"googleSearch": {}}, {"codeExecution": {}}
tool_top_level_keys:
- key: "googleSearch"
description: "Google Search grounding"
- key: "googleSearchRetrieval"
description: "Google Search retrieval with data store integration"
- key: "enterpriseWebSearch"
description: "Enterprise web search"
- key: "url_context"
description: "URL context grounding"
- key: "code_execution"
description: "Code execution sandbox"
- key: "googleMaps"
description: "Google Maps integration"
- key: "computerUse"
description: "Computer use / automated screen interaction"

View file

@ -0,0 +1,177 @@
"""
Block Hosted Tools Guardrail
Provider-agnostic guardrail that blocks platform-executed hosted tools from
Anthropic, OpenAI, and Google Gemini. Tool definitions are loaded from
per-provider YAML files in this directory so the list can be extended without
touching Python code.
Detection strategy per provider:
Anthropic — prefix match on tool 'type' field (versioned: bash_20250124, etc.)
OpenAI — exact match on tool 'type' field (code_interpreter, file_search, …)
Gemini — exact match on tool 'type' field OR presence of a top-level key
(googleSearch, codeExecution, …) in the tool dict
"""
import os
from typing import (TYPE_CHECKING, Dict, FrozenSet, List, Literal, Optional,
Tuple)
import yaml
from fastapi import HTTPException
from litellm._logging import verbose_proxy_logger
from litellm.integrations.custom_guardrail import (CustomGuardrail,
log_guardrail_information)
from litellm.types.guardrails import GuardrailEventHooks
from litellm.types.utils import GenericGuardrailAPIInputs
if TYPE_CHECKING:
from litellm.litellm_core_utils.litellm_logging import \
Logging as LiteLLMLoggingObj
_DIR = os.path.dirname(__file__)
# ---------------------------------------------------------------------------
# Config loading — runs once at import time
# ---------------------------------------------------------------------------
def _load_provider_configs() -> Dict[str, dict]:
"""Load all *.yaml files in this directory, keyed by provider name."""
configs: Dict[str, dict] = {}
for fname in os.listdir(_DIR):
if not fname.endswith(".yaml"):
continue
with open(os.path.join(_DIR, fname)) as f:
cfg = yaml.safe_load(f)
provider = cfg.get("provider")
if provider:
configs[provider] = cfg
return configs
def _build_match_sets(
configs: Dict[str, dict],
) -> Tuple[tuple, FrozenSet[str], FrozenSet[str]]:
"""
Derive three matching structures from all provider configs:
Returns:
type_prefixes — tuple of prefix strings (Anthropic-style versioned types)
exact_types — frozenset of exact type strings (OpenAI / Gemini-compat)
top_level_keys — frozenset of Gemini native top-level dict keys
"""
prefixes: List[str] = []
exact: List[str] = []
keys: List[str] = []
for cfg in configs.values():
for entry in cfg.get("tool_type_prefixes", []):
if isinstance(entry, dict) and "prefix" in entry:
prefixes.append(entry["prefix"])
for entry in cfg.get("tool_type_exact", []):
if isinstance(entry, dict) and "type" in entry:
exact.append(entry["type"])
for entry in cfg.get("tool_top_level_keys", []):
if isinstance(entry, dict) and "key" in entry:
keys.append(entry["key"])
return tuple(prefixes), frozenset(exact), frozenset(keys)
_PROVIDER_CONFIGS = _load_provider_configs()
_TYPE_PREFIXES, _EXACT_TYPES, _TOP_LEVEL_KEYS = _build_match_sets(_PROVIDER_CONFIGS)
# ---------------------------------------------------------------------------
# Tool-matching helpers
# ---------------------------------------------------------------------------
def _match_tool(tool: dict) -> Optional[str]:
"""
Return a human-readable description if the tool is a known hosted tool,
or None if it is a user-defined tool (pass through).
"""
if not isinstance(tool, dict):
return None
raw_type: Optional[str] = tool.get("type")
# Skip generic OpenAI function wrapper — never a hosted tool
if raw_type == "function":
return None
if raw_type:
# Prefix match (Anthropic versioned types)
if raw_type.startswith(_TYPE_PREFIXES):
name = tool.get("name") or raw_type
return f"{name} ({raw_type})"
# Exact match (OpenAI / Gemini-compat)
if raw_type in _EXACT_TYPES:
name = tool.get("name") or raw_type
return f"{name} ({raw_type})"
# Gemini native top-level keys ({"googleSearch": {}, ...})
for key in _TOP_LEVEL_KEYS:
if key in tool:
return f"{key}"
return None
# ---------------------------------------------------------------------------
# Guardrail class
# ---------------------------------------------------------------------------
class BlockHostedToolsGuardrail(CustomGuardrail):
"""
Guardrail that blocks platform-hosted tools from Anthropic, OpenAI, and Gemini.
Raises HTTP 403 if any tool in the request matches a known hosted tool
from any supported provider. Provider tool lists are defined in YAML
files alongside this module and loaded at import time.
"""
def __init__(self, **kwargs):
if "supported_event_hooks" not in kwargs:
kwargs["supported_event_hooks"] = [GuardrailEventHooks.pre_call]
super().__init__(**kwargs)
verbose_proxy_logger.debug(
f"BlockHostedToolsGuardrail initialized "
f"(providers: {list(_PROVIDER_CONFIGS.keys())})"
)
@log_guardrail_information
async def apply_guardrail(
self,
inputs: GenericGuardrailAPIInputs,
request_data: dict,
input_type: Literal["request", "response"],
logging_obj: Optional["LiteLLMLoggingObj"] = None,
) -> GenericGuardrailAPIInputs:
if input_type != "request":
return inputs
tools: List[dict] = list(inputs.get("tools") or []) # type: ignore[assignment]
blocked: List[str] = []
for tool in tools:
desc = _match_tool(tool)
if desc:
blocked.append(desc)
if blocked:
raise HTTPException(
status_code=403,
detail={
"error": (
"the following platform-hosted tools are disabled by "
"your organization's policy: " + ", ".join(blocked)
),
"guardrail": self.guardrail_name,
"blocked_tools": blocked,
},
)
return inputs

View file

@ -0,0 +1,19 @@
# OpenAI platform-hosted tools executed on OpenAI's infrastructure.
# These are matched by exact 'type' field value (no version suffix).
#
# Reference: https://platform.openai.com/docs/guides/tools
provider: "openai"
description: "OpenAI platform-hosted tools executed on OpenAI infrastructure"
tool_type_exact:
- type: "code_interpreter"
description: "Python code execution and file output sandbox"
- type: "file_search"
description: "Semantic file search across vector stores"
- type: "web_search_preview"
description: "Bing-powered live web search"
- type: "computer_use_preview"
description: "Computer use / automated screen interaction"
- type: "shell"
description: "Shell command execution in containers"

View file

@ -0,0 +1,77 @@
"""
Claude Code guardrail integrations for LiteLLM.
Two focused policy-enforcement guardrails for Claude Code deployments:
1. claude_code_prompt_cache — auto-inject Anthropic prompt-caching headers
2. claude_code_block_expensive_flags — block expensive API flags (fast mode, etc.)
Hosted tool blocking is handled by the provider-agnostic 'block_hosted_tools'
guardrail (see guardrail_hooks/block_hosted_tools/).
"""
from typing import TYPE_CHECKING
import litellm
from litellm.types.guardrails import SupportedGuardrailIntegrations
from .block_expensive_flags import ClaudeCodeBlockExpensiveFlagsGuardrail
from .prompt_cache import ClaudeCodePromptCacheGuardrail
if TYPE_CHECKING:
from litellm.types.guardrails import Guardrail, LitellmParams
# ------------------------------------------------------------------ #
# Per-guardrail initializer functions #
# ------------------------------------------------------------------ #
def _init_prompt_cache(
litellm_params: "LitellmParams", guardrail: "Guardrail"
) -> ClaudeCodePromptCacheGuardrail:
guardrail_name = guardrail.get("guardrail_name")
if not guardrail_name:
raise ValueError("ClaudeCodePromptCacheGuardrail requires a guardrail_name")
instance = ClaudeCodePromptCacheGuardrail(
guardrail_name=guardrail_name,
event_hook=litellm_params.mode,
default_on=litellm_params.default_on,
)
litellm.logging_callback_manager.add_litellm_callback(instance)
return instance
def _init_block_expensive_flags(
litellm_params: "LitellmParams", guardrail: "Guardrail"
) -> ClaudeCodeBlockExpensiveFlagsGuardrail:
guardrail_name = guardrail.get("guardrail_name")
if not guardrail_name:
raise ValueError("ClaudeCodeBlockExpensiveFlagsGuardrail requires a guardrail_name")
instance = ClaudeCodeBlockExpensiveFlagsGuardrail(
guardrail_name=guardrail_name,
event_hook=litellm_params.mode,
default_on=litellm_params.default_on,
)
litellm.logging_callback_manager.add_litellm_callback(instance)
return instance
# ------------------------------------------------------------------ #
# Registries consumed by the guardrail loader #
# ------------------------------------------------------------------ #
guardrail_initializer_registry = {
SupportedGuardrailIntegrations.CLAUDE_CODE_PROMPT_CACHE.value: _init_prompt_cache,
SupportedGuardrailIntegrations.CLAUDE_CODE_BLOCK_EXPENSIVE_FLAGS.value: _init_block_expensive_flags,
}
guardrail_class_registry = {
SupportedGuardrailIntegrations.CLAUDE_CODE_PROMPT_CACHE.value: ClaudeCodePromptCacheGuardrail,
SupportedGuardrailIntegrations.CLAUDE_CODE_BLOCK_EXPENSIVE_FLAGS.value: ClaudeCodeBlockExpensiveFlagsGuardrail,
}
__all__ = [
"ClaudeCodePromptCacheGuardrail",
"ClaudeCodeBlockExpensiveFlagsGuardrail",
]

View file

@ -0,0 +1,174 @@
"""
Claude Code - Block Expensive API Flags Guardrail
Blocks Anthropic API parameters that trigger feature-specific pricing surcharges
(fast mode, inference_geo, extended thinking). Also inherits the hosted tool
type prefixes from hosted_tool_types.yaml so hosted tools are blocked here too.
Blocked params are driven by expensive_api_flags.yaml which references
hosted_tool_types.yaml via `inherit_from`, following the same pattern as
harmful_child_safety.yaml inherits from harm_toxic_abuse.json.
"""
import os
from typing import TYPE_CHECKING, Any, List, Literal, Optional
import yaml
from fastapi import HTTPException
from litellm._logging import verbose_proxy_logger
from litellm.integrations.custom_guardrail import (CustomGuardrail,
log_guardrail_information)
from litellm.types.guardrails import GuardrailEventHooks
from litellm.types.utils import GenericGuardrailAPIInputs
if TYPE_CHECKING:
from litellm.litellm_core_utils.litellm_logging import \
Logging as LiteLLMLoggingObj
_DIR = os.path.dirname(__file__)
_FLAGS_YAML = os.path.join(_DIR, "expensive_api_flags.yaml")
_TOOLS_YAML = os.path.join(_DIR, "hosted_tool_types.yaml")
def _load_config() -> dict:
"""Load expensive_api_flags.yaml, merging any inherited config."""
with open(_FLAGS_YAML) as f:
config: dict = yaml.safe_load(f)
inherited_prefixes: List[str] = []
inherit_from = config.get("inherit_from")
if inherit_from:
inherit_path = os.path.join(_DIR, inherit_from)
with open(inherit_path) as f:
inherited: dict = yaml.safe_load(f)
inherited_prefixes = [
entry["prefix"]
for entry in inherited.get("tool_type_prefixes", [])
if isinstance(entry, dict) and "prefix" in entry
]
config["_inherited_tool_type_prefixes"] = inherited_prefixes
return config
_CONFIG: dict = _load_config()
_BLOCKED_PARAMS: List[dict] = _CONFIG.get("blocked_params", [])
_INHERITED_TOOL_TYPE_PREFIXES: tuple = tuple(_CONFIG.get("_inherited_tool_type_prefixes", []))
def _tool_type(tool: dict) -> Optional[str]:
t = tool.get("type")
if t and t != "function":
return t
return None
def _is_hosted_tool(tool: dict) -> bool:
if not _INHERITED_TOOL_TYPE_PREFIXES:
return False
t = _tool_type(tool)
if not t:
return False
return t.startswith(_INHERITED_TOOL_TYPE_PREFIXES)
def _check_param(
request_data: dict, param_cfg: dict
) -> Optional[str]:
"""
Return an error message if the param in request_data matches a blocked value.
Returns None when the param is not blocked.
"""
param = param_cfg.get("param")
if not param:
return None
value: Any = request_data.get(param)
if value is None:
return None
nested_key = param_cfg.get("nested_key")
if nested_key:
# e.g. thinking.type — value must be a dict
if not isinstance(value, dict):
return None
value = value.get(nested_key)
if value is None:
return None
blocked_values: List[str] = param_cfg.get("blocked_values", [])
if "*" in blocked_values or str(value) in blocked_values:
return param_cfg.get("error_message", f"{param} is disabled by your organization's policy")
return None
class ClaudeCodeBlockExpensiveFlagsGuardrail(CustomGuardrail):
"""
Guardrail that blocks expensive Anthropic API flags.
Checks request_data for feature-specific pricing flags (fast mode,
inference_geo, extended thinking) and Anthropic-hosted tools inherited
from hosted_tool_types.yaml. Raises HTTP 403 on the first violation.
"""
def __init__(self, **kwargs):
if "supported_event_hooks" not in kwargs:
kwargs["supported_event_hooks"] = [GuardrailEventHooks.pre_call]
super().__init__(**kwargs)
verbose_proxy_logger.debug("ClaudeCodeBlockExpensiveFlagsGuardrail initialized")
@log_guardrail_information
async def apply_guardrail(
self,
inputs: GenericGuardrailAPIInputs,
request_data: dict,
input_type: Literal["request", "response"],
logging_obj: Optional["LiteLLMLoggingObj"] = None,
) -> GenericGuardrailAPIInputs:
if input_type != "request":
return inputs
# ------------------------------------------------------------------ #
# 1. Check request-level blocked params (speed, inference_geo, etc.) #
# ------------------------------------------------------------------ #
for param_cfg in _BLOCKED_PARAMS:
error_msg = _check_param(request_data, param_cfg)
if error_msg:
param = param_cfg.get("param", "unknown")
raise HTTPException(
status_code=403,
detail={
"error": error_msg,
"guardrail": self.guardrail_name,
"blocked_param": param,
},
)
# ------------------------------------------------------------------ #
# 2. Check for inherited hosted tools (from hosted_tool_types.yaml) #
# ------------------------------------------------------------------ #
if _INHERITED_TOOL_TYPE_PREFIXES:
tools: List[dict] = list(inputs.get("tools") or []) # type: ignore[assignment]
blocked_tools: List[str] = []
for tool in tools:
if _is_hosted_tool(tool):
tool_type = _tool_type(tool) or "unknown"
tool_name = tool.get("name") or tool_type
blocked_tools.append(f"{tool_name} ({tool_type})")
if blocked_tools:
raise HTTPException(
status_code=403,
detail={
"error": (
"the following Anthropic-hosted tools are disabled by "
"your organization's policy: " + ", ".join(blocked_tools)
),
"guardrail": self.guardrail_name,
"blocked_tools": blocked_tools,
},
)
return inputs

View file

@ -0,0 +1,35 @@
# Expensive Anthropic API flags.
# Blocks request parameters that trigger feature-specific pricing surcharges.
# Also inherits hosted tool type prefixes so hosted tools are blocked as well.
#
# Pricing reference: https://platform.claude.com/docs/en/about-claude/pricing#feature-specific-pricing
category_name: "claude_code_expensive_flags"
description: "Blocks expensive Anthropic API features that incur pricing surcharges"
default_action: "BLOCK"
# Inherit hosted tool type prefixes — hosted tools are also expensive.
inherit_from: "../block_hosted_tools/anthropic.yaml"
# Request-level parameters to block.
# Each entry maps a top-level request_data key to one or more blocked values.
# Use "*" as the value to block the flag entirely regardless of its value.
blocked_params:
- param: "speed"
blocked_values:
- "fast"
description: "Fast/turbo inference mode (~6x pricing surcharge)"
error_message: "fast inference mode (speed=fast) is disabled by your organization's policy"
- param: "inference_geo"
blocked_values:
- "*"
description: "Inference geo-routing (1.1x pricing surcharge)"
error_message: "inference geo-routing (inference_geo) is disabled by your organization's policy"
- param: "thinking"
blocked_values:
- "enabled"
nested_key: "type"
description: "Extended thinking / budget-token thinking mode"
error_message: "extended thinking mode (thinking.type=enabled) is disabled by your organization's policy"

View file

@ -0,0 +1,91 @@
"""
Claude Code - Prompt Cache Injection Guardrail
Automatically injects cache_control: {type: ephemeral} into system messages
so Anthropic can cache the prompt prefix, reducing costs on repeated calls.
Only runs when the request targets an Anthropic API model. Uses the existing
AnthropicCacheControlHook._safe_insert_cache_control_in_message utility so
the injection logic is not duplicated.
"""
from typing import TYPE_CHECKING, Literal, Optional
from litellm._logging import verbose_proxy_logger
from litellm.integrations.anthropic_cache_control_hook import \
AnthropicCacheControlHook
from litellm.integrations.custom_guardrail import (CustomGuardrail,
log_guardrail_information)
from litellm.types.guardrails import GuardrailEventHooks
from litellm.types.llms.openai import ChatCompletionCachedContent
from litellm.types.utils import GenericGuardrailAPIInputs
from litellm.utils import get_llm_provider
if TYPE_CHECKING:
from litellm.litellm_core_utils.litellm_logging import \
Logging as LiteLLMLoggingObj
def _is_anthropic_model(model: Optional[str]) -> bool:
"""Return True when the model resolves to the Anthropic provider."""
if not model:
return False
try:
_, custom_llm_provider, _, _ = get_llm_provider(model=model)
return custom_llm_provider == "anthropic"
except Exception:
return False
class ClaudeCodePromptCacheGuardrail(CustomGuardrail):
"""
Guardrail that injects Anthropic prompt-caching headers into system messages.
Targets only Anthropic API models — the provider is detected from the
model string so this guardrail is safe to apply globally without
restricting other providers.
"""
def __init__(self, **kwargs):
if "supported_event_hooks" not in kwargs:
kwargs["supported_event_hooks"] = [GuardrailEventHooks.pre_call]
super().__init__(**kwargs)
verbose_proxy_logger.debug("ClaudeCodePromptCacheGuardrail initialized")
@log_guardrail_information
async def apply_guardrail(
self,
inputs: GenericGuardrailAPIInputs,
request_data: dict,
input_type: Literal["request", "response"],
logging_obj: Optional["LiteLLMLoggingObj"] = None,
) -> GenericGuardrailAPIInputs:
if input_type != "request":
return inputs
model: Optional[str] = request_data.get("model") or inputs.get("model") # type: ignore[assignment]
if not _is_anthropic_model(model):
verbose_proxy_logger.debug(
f"ClaudeCodePromptCacheGuardrail: skipping non-Anthropic model '{model}'"
)
return inputs
messages = request_data.get("messages") or []
if not messages:
return inputs
control = ChatCompletionCachedContent(type="ephemeral")
modified = []
for msg in messages:
if msg.get("role") == "system":
msg = AnthropicCacheControlHook._safe_insert_cache_control_in_message(
message=msg, # type: ignore[arg-type]
control=control,
)
modified.append(msg)
request_data["messages"] = modified
verbose_proxy_logger.debug(
"ClaudeCodePromptCacheGuardrail: injected cache_control into system messages"
)
return inputs

View file

@ -5,24 +5,18 @@ from typing import Any, Dict, List, Literal, Optional, Union
from pydantic import BaseModel, ConfigDict, Field, field_validator
from typing_extensions import Required, TypedDict
from litellm.types.proxy.guardrails.guardrail_hooks.enkryptai import (
EnkryptAIGuardrailConfigs,
)
from litellm.types.proxy.guardrails.guardrail_hooks.grayswan import (
GraySwanGuardrailConfigModel,
)
from litellm.types.proxy.guardrails.guardrail_hooks.ibm import (
IBMGuardrailsBaseConfigModel,
)
from litellm.types.proxy.guardrails.guardrail_hooks.litellm_content_filter import (
ContentFilterCategoryConfig,
)
from litellm.types.proxy.guardrails.guardrail_hooks.qualifire import (
QualifireGuardrailConfigModel,
)
from litellm.types.proxy.guardrails.guardrail_hooks.tool_permission import (
ToolPermissionGuardrailConfigModel,
)
from litellm.types.proxy.guardrails.guardrail_hooks.enkryptai import \
EnkryptAIGuardrailConfigs
from litellm.types.proxy.guardrails.guardrail_hooks.grayswan import \
GraySwanGuardrailConfigModel
from litellm.types.proxy.guardrails.guardrail_hooks.ibm import \
IBMGuardrailsBaseConfigModel
from litellm.types.proxy.guardrails.guardrail_hooks.litellm_content_filter import \
ContentFilterCategoryConfig
from litellm.types.proxy.guardrails.guardrail_hooks.qualifire import \
QualifireGuardrailConfigModel
from litellm.types.proxy.guardrails.guardrail_hooks.tool_permission import \
ToolPermissionGuardrailConfigModel
"""
Pydantic object defining how to set guardrails on litellm proxy
@ -70,6 +64,9 @@ class SupportedGuardrailIntegrations(Enum):
GENERIC_GUARDRAIL_API = "generic_guardrail_api"
QUALIFIRE = "qualifire"
CUSTOM_CODE = "custom_code"
BLOCK_HOSTED_TOOLS = "block_hosted_tools"
CLAUDE_CODE_PROMPT_CACHE = "claude_code_prompt_cache"
CLAUDE_CODE_BLOCK_EXPENSIVE_FLAGS = "claude_code_block_expensive_flags"
class Role(Enum):

View file

@ -769,6 +769,81 @@
"guardrails_remove": []
}
},
{
"id": "claude-code-pii-protection",
"title": "Claude Code PII Protection",
"description": "Prevents data leakage in Claude Code sessions by blocking all Anthropic-hosted tools (bash, web_search, web_fetch, code_execution, memory, text_editor) and masking PII and credentials found in request messages.",
"icon": "ShieldCheckIcon",
"iconColor": "text-indigo-500",
"iconBg": "bg-indigo-50",
"guardrails": [
"claude-code-block-hosted-tools",
"claude-code-pii-credentials",
"claude-code-pii-personal-data"
],
"complexity": "Medium",
"guardrailDefinitions": [
{
"guardrail_name": "claude-code-block-hosted-tools",
"litellm_params": {
"guardrail": "block_hosted_tools",
"mode": "pre_call"
},
"guardrail_info": {
"description": "Blocks platform-hosted tools from Anthropic (bash, web_search, web_fetch, code_execution, memory, text_editor, tool_search), OpenAI (code_interpreter, file_search, web_search_preview), and Gemini (googleSearch, code_execution, url_context). Only custom tool schemas are permitted."
}
},
{
"guardrail_name": "claude-code-pii-credentials",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "aws_access_key", "action": "BLOCK"},
{"pattern_type": "prebuilt", "pattern_name": "aws_secret_key", "action": "BLOCK"},
{"pattern_type": "prebuilt", "pattern_name": "github_token", "action": "BLOCK"},
{"pattern_type": "prebuilt", "pattern_name": "slack_token", "action": "BLOCK"},
{"pattern_type": "prebuilt", "pattern_name": "generic_api_key", "action": "BLOCK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "Blocks requests containing credentials or API keys (AWS, GitHub, Slack) that could be exfiltrated through the LLM context."
}
},
{
"guardrail_name": "claude-code-pii-personal-data",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "us_ssn", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "email", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "us_phone", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "credit_card", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "visa", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "mastercard", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "iban", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "ipv4", "action": "MASK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "Masks SSNs, email addresses, phone numbers, credit card numbers, IBANs, and IP addresses before they enter the LLM context."
}
}
],
"templateData": {
"policy_name": "claude-code-pii-protection",
"description": "Prevents data leakage in Claude Code sessions. Blocks Anthropic-hosted tools and masks PII and credentials in request messages.",
"guardrails_add": [
"claude-code-block-hosted-tools",
"claude-code-pii-credentials",
"claude-code-pii-personal-data"
],
"guardrails_remove": []
}
},
{
"id": "eu-ai-act-article5",
"title": "EU AI Act Article 5 — Prohibited Practices",
@ -1031,5 +1106,49 @@
"guardrails_add": ["mcp-security-block"],
"guardrails_remove": []
}
},
{
"id": "claude-code-cost-optimization",
"title": "Claude Code Cost Optimization",
"description": "Reduces Claude Code API spend by blocking expensive inference modes (fast/turbo, inference_geo, and extended thinking) and automatically injecting prompt caching headers into system messages to maximize cache hit rates.",
"icon": "CurrencyDollarIcon",
"iconColor": "text-green-500",
"iconBg": "bg-green-50",
"guardrails": [
"claude-code-inject-prompt-cache",
"claude-code-block-expensive-flags"
],
"complexity": "Low",
"guardrailDefinitions": [
{
"guardrail_name": "claude-code-inject-prompt-cache",
"litellm_params": {
"guardrail": "claude_code_prompt_cache",
"mode": "pre_call"
},
"guardrail_info": {
"description": "Automatically adds cache_control: {type: ephemeral} to system messages so Anthropic can cache the system prompt prefix. Only applies to Anthropic API models. Reduces cost on repeated calls that share the same system prompt."
}
},
{
"guardrail_name": "claude-code-block-expensive-flags",
"litellm_params": {
"guardrail": "claude_code_block_expensive_flags",
"mode": "pre_call"
},
"guardrail_info": {
"description": "Blocks expensive Anthropic API flags: fast/turbo inference (speed=fast, ~6x pricing), inference geo-routing (inference_geo, 1.1x pricing), and extended thinking (thinking.type=enabled). Also blocks Anthropic-hosted tools inherited from the hosted tools policy."
}
}
],
"templateData": {
"policy_name": "claude-code-cost-optimization",
"description": "Reduces Claude Code API spend by blocking fast mode, inference_geo, extended thinking, and Anthropic-hosted tools, while auto-injecting prompt caching into system messages.",
"guardrails_add": [
"claude-code-inject-prompt-cache",
"claude-code-block-expensive-flags"
],
"guardrails_remove": []
}
}
]