Guardrails - new Policy Templates (pre-configured guardrail combinations for specific use-cases) (#21025)

* feat(patterns.json): add australia specific pii patterns - tax file number, abn, medicare number

Improve PII detection for australian contexts

* feat(patterns.json): add iban + street address pattern detection

* feat: support policy templates on ui

allows admin to enable pre-configured guardrails

helps cover specific use-cases well

* feat: create missing guardrails, working policy templates

* feat: policy_templates.json

support hosted policy templates

allows others to contribute to the policy templates

* docs: document new policy templates

* fix: address greptile feedback

* fix: fix linting error
This commit is contained in:
Krish Dholakia 2026-02-13 11:53:02 -08:00 committed by GitHub
parent aab8edde67
commit 24b56a14eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 1559 additions and 16 deletions

View File

@ -767,6 +767,7 @@ router_settings:
| LITELLM_KEY_ROTATION_CHECK_INTERVAL_SECONDS | Interval in seconds for how often to run job that auto-rotates keys. Default is 86400 (24 hours).
| LITELLM_LICENSE | License key for LiteLLM usage
| LITELLM_LOCAL_MODEL_COST_MAP | Local configuration for model cost mapping in LiteLLM
| LITELLM_LOCAL_POLICY_TEMPLATES | When set to "true", uses local backup policy templates instead of fetching from GitHub. Policy templates are fetched from https://raw.githubusercontent.com/BerriAI/litellm/main/policy_templates.json by default, with automatic fallback to local backup on failure
| LITELLM_LOG | Enable detailed logging for LiteLLM
| LITELLM_MODEL_COST_MAP_URL | URL for fetching model cost map data. Default is https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json
| LITELLM_LOG_FILE | File path to write LiteLLM logs to. When set, logs will be written to both console and the specified file

View File

@ -0,0 +1,298 @@
# Policy Templates
Policy templates provide pre-configured guardrail policies that you can use as a starting point for your organization. Instead of manually creating policies and guardrails, you can select a template that matches your use case and deploy it with one click.
## Using Policy Templates
### In the UI
1. Navigate to **Policies → Templates** tab in the LiteLLM Admin UI
2. Browse available templates (e.g., "PII Protection", "Cost Control", "HR Compliance")
3. Click **"Use Template"** on any template
4. Review the guardrails that will be created:
- Existing guardrails are marked with a green checkmark
- New guardrails can be selected/deselected
5. Click **"Create X Guardrails & Use Template"**
6. Review and customize the pre-filled policy form
7. Click **"Create Policy"** to save
![Policy Templates UI](/img/policy_templates_ui.png)
### Workflow
```
Select Template → Review Guardrails → Create Selected → Edit Policy → Save
```
The system automatically:
- ✅ Detects which guardrails already exist
- ✅ Creates only the missing guardrails you select
- ✅ Pre-fills the policy form with template data
- ✅ Lets you customize before saving
## Available Templates
Templates are fetched from [GitHub](https://raw.githubusercontent.com/BerriAI/litellm/main/policy_templates.json) with automatic fallback to local backup.
### Current Templates
#### 1. Advanced PII Protection (Australia)
- **Complexity:** High
- **Use Case:** Comprehensive PII detection for Australian organizations
- **Guardrails:**
- Australian tax identifiers (TFN, ABN, Medicare)
- Australian passports
- International PII (SSN, passports, national IDs)
- Contact information (email, phone, address)
- Financial data (credit cards, IBAN)
- API credentials (AWS, GitHub, Slack) - **BLOCKS** requests
- Network infrastructure (IP addresses)
- Protected class information (gender, race, religion, disability, etc.)
#### 2. Baseline PII Protection
- **Complexity:** Low
- **Use Case:** Basic protection for internal tools and testing
- **Guardrails:**
- Australian tax identifiers
- API credentials
- Financial data
## Creating Your Own Policy Templates
You can contribute policy templates for the entire LiteLLM community to use.
### Template Structure
Templates are defined in JSON format with the following structure:
```json
{
"id": "unique-template-id",
"title": "Display Title",
"description": "Detailed description of what this template protects",
"icon": "ShieldCheckIcon",
"iconColor": "text-purple-500",
"iconBg": "bg-purple-50",
"guardrails": [
"guardrail-name-1",
"guardrail-name-2"
],
"complexity": "Low|Medium|High",
"guardrailDefinitions": [
{
"guardrail_name": "example-guardrail",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{
"pattern_type": "prebuilt",
"pattern_name": "email",
"action": "MASK"
}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "What this guardrail does"
}
}
],
"templateData": {
"policy_name": "policy-name",
"description": "Policy description",
"guardrails_add": ["guardrail-name-1", "guardrail-name-2"],
"guardrails_remove": []
}
}
```
### Field Descriptions
#### Display Fields
- **id**: Unique identifier (lowercase with hyphens)
- **title**: User-facing name shown in UI
- **description**: Detailed explanation of what the template protects
- **icon**: Icon name (must be available in UI icon map)
- **iconColor**: Tailwind CSS text color class
- **iconBg**: Tailwind CSS background color class
- **guardrails**: Array of guardrail names (for display only)
- **complexity**: Badge showing difficulty ("Low", "Medium", or "High")
#### Guardrail Definitions
- **guardrailDefinitions**: Array of complete guardrail configurations
- Each must be a valid guardrail object that can be sent to `/guardrails` POST endpoint
- If a guardrail already exists, it will be skipped
- Can be empty `[]` if template uses only existing guardrails
#### Policy Configuration
- **templateData**: Object that pre-fills the policy form
- **policy_name**: Suggested name (user can edit)
- **description**: Policy description
- **guardrails_add**: Array of guardrail names to include
- **guardrails_remove**: Array to remove (usually `[]` for templates)
- **inherit**: (Optional) Parent policy name for inheritance
### Example Template
Here's a complete example for a HIPAA compliance template:
```json
{
"id": "hipaa-compliance",
"title": "HIPAA Compliance Policy",
"description": "Healthcare compliance policy that masks PHI and enforces HIPAA regulations for healthcare applications.",
"icon": "ShieldCheckIcon",
"iconColor": "text-red-500",
"iconBg": "bg-red-50",
"guardrails": [
"phi-detector",
"medical-record-blocker",
"patient-id-masker"
],
"complexity": "High",
"guardrailDefinitions": [
{
"guardrail_name": "phi-detector",
"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_redaction_format": "[PHI_REDACTED]"
},
"guardrail_info": {
"description": "Detects and masks Protected Health Information (PHI)"
}
}
],
"templateData": {
"policy_name": "hipaa-compliance-policy",
"description": "HIPAA compliance policy for healthcare applications",
"guardrails_add": [
"phi-detector",
"medical-record-blocker",
"patient-id-masker"
],
"guardrails_remove": []
}
}
```
## Contributing Templates
To contribute a policy template for everyone to use:
### Step 1: Create Your Template JSON
1. Create a JSON file following the structure above
2. Test it locally by adding it to your local `policy_templates.json`
3. Verify all guardrails work correctly
4. Ensure descriptions are clear and helpful
### Step 2: Submit a Pull Request
1. Fork the [LiteLLM repository](https://github.com/BerriAI/litellm)
2. Add your template to `policy_templates.json` at the root
3. Add your template to `litellm/policy_templates_backup.json` (keep both in sync)
4. Create a pull request with:
- Clear description of what the template protects
- Use case examples
- Any relevant compliance frameworks (HIPAA, GDPR, SOC 2, etc.)
### Guidelines
**DO:**
- ✅ Use clear, descriptive names
- ✅ Include comprehensive descriptions
- ✅ Test all guardrails thoroughly
- ✅ Document pattern sources (e.g., "Based on NIST guidelines")
- ✅ Group related guardrails logically
- ✅ Consider different complexity levels
**DON'T:**
- ❌ Include credentials or secrets
- ❌ Use overly broad patterns that may have false positives
- ❌ Duplicate existing templates
- ❌ Use custom code without thorough testing
## Using Templates Offline
For air-gapped or offline deployments, set the environment variable:
```bash
export LITELLM_LOCAL_POLICY_TEMPLATES=true
```
This forces the system to use the local backup (`litellm/policy_templates_backup.json`) instead of fetching from GitHub.
## Template Sources
- **GitHub (default):** https://raw.githubusercontent.com/BerriAI/litellm/main/policy_templates.json
- **Local backup:** `litellm/policy_templates_backup.json`
Templates are automatically fetched from GitHub on each request, with fallback to local backup on any failure.
## Available Pattern Types
When creating guardrails for templates, you can use these prebuilt patterns:
### Identity Documents
- `passport_australia`, `passport_us`, `passport_uk`, `passport_germany`, etc.
- `us_ssn`, `us_ssn_no_dash`
- `au_tfn`, `au_abn`, `au_medicare`
- `nl_bsn_contextual`
- `br_cpf`, `br_rg`, `br_cnpj`
### Financial
- `visa`, `mastercard`, `amex`, `discover`, `credit_card`
- `iban`
### Contact Information
- `email`
- `us_phone`, `br_phone_landline`, `br_phone_mobile`
- `street_address`
- `br_cep` (Brazilian postal code)
### Credentials
- `aws_access_key`, `aws_secret_key`
- `github_token`
- `slack_token`
- `generic_api_key`
### Network
- `ipv4`, `ipv6`
### Protected Class
- `gender_sexual_orientation`
- `race_ethnicity_national_origin`
- `religion`
- `age_discrimination`
- `disability`
- `marital_family_status`
- `military_status`
- `public_assistance`
See the [full patterns list](https://github.com/BerriAI/litellm/blob/main/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/patterns.json) for all available patterns.
## Related Docs
- [Guardrail Policies](./guardrail_policies)
- [Policy Tags](./policy_tags)
- [Content Filter Patterns](../hooks/content_filter)
- [Custom Code Guardrails](../hooks/custom_code)

View File

@ -97,6 +97,7 @@ const sidebars = {
label: "Policies",
items: [
"proxy/guardrails/guardrail_policies",
"proxy/guardrails/policy_templates",
"proxy/guardrails/policy_tags",
],
},

View File

@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "LiteLLM_PolicyAttachmentTable" ADD COLUMN "tags" TEXT[] DEFAULT ARRAY[]::TEXT[];

View File

@ -29,7 +29,11 @@ from litellm.types.utils import (
LLMResponseTypes,
StandardLoggingGuardrailInformation,
)
from fastapi.exceptions import HTTPException
try:
from fastapi.exceptions import HTTPException
except ImportError:
HTTPException = None # type: ignore
if TYPE_CHECKING:
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
@ -664,7 +668,11 @@ class CustomGuardrail(CustomLogger):
if isinstance(e, ModifyResponseException):
return True
if isinstance(e, HTTPException) and e.status_code == 400:
if (
HTTPException is not None
and isinstance(e, HTTPException)
and e.status_code == 400
):
return True
return False

View File

@ -0,0 +1,278 @@
[
{
"id": "advanced-au-pii-protection",
"title": "Advanced PII Protection (Australia)",
"description": "Comprehensive PII detection and masking for Australia. Protects Australian-specific identifiers, international employee data, financial information, credentials, protected class information, and industry-specific sensitive data.",
"icon": "ShieldCheckIcon",
"iconColor": "text-purple-500",
"iconBg": "bg-purple-50",
"guardrails": [
"au-pii-tax-identifiers",
"au-pii-passports",
"international-pii-identifiers",
"contact-information-pii",
"financial-pii",
"credentials-api-keys",
"network-infrastructure-pii",
"protected-class-information"
],
"complexity": "High",
"guardrailDefinitions": [
{
"guardrail_name": "au-pii-tax-identifiers",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{
"pattern_type": "prebuilt",
"pattern_name": "au_tfn",
"action": "MASK"
},
{
"pattern_type": "prebuilt",
"pattern_name": "au_abn",
"action": "MASK"
},
{
"pattern_type": "prebuilt",
"pattern_name": "au_medicare",
"action": "MASK"
}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "Masks Australian Tax File Numbers, Business Numbers, and Medicare Numbers"
}
},
{
"guardrail_name": "au-pii-passports",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{
"pattern_type": "prebuilt",
"pattern_name": "passport_australia",
"action": "MASK"
}
],
"pattern_redaction_format": "[PASSPORT_REDACTED]"
},
"guardrail_info": {
"description": "Masks Australian passport numbers"
}
},
{
"guardrail_name": "international-pii-identifiers",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "us_ssn", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "us_ssn_no_dash", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_us", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_uk", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_germany", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_france", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_netherlands", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "nl_bsn_contextual", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_china", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_india", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_japan", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_canada", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_cpf", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_cpf_unformatted", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_rg", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_cnpj", "action": "MASK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "Masks international PII identifiers including passports and national IDs"
}
},
{
"guardrail_name": "contact-information-pii",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "email", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "us_phone", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_phone_landline", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_phone_mobile", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "street_address", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_cep", "action": "MASK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "Masks contact information including emails, phone numbers, and addresses"
}
},
{
"guardrail_name": "financial-pii",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "visa", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "mastercard", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "amex", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "discover", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "credit_card", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "iban", "action": "MASK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "Masks financial information including credit cards and bank account numbers"
}
},
{
"guardrail_name": "credentials-api-keys",
"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 API keys and credentials (AWS, GitHub, Slack)"
}
},
{
"guardrail_name": "network-infrastructure-pii",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "ipv4", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "ipv6", "action": "MASK"}
],
"pattern_redaction_format": "[INTERNAL_IP_REDACTED]"
},
"guardrail_info": {
"description": "Masks IP addresses in requests"
}
},
{
"guardrail_name": "protected-class-information",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "gender_sexual_orientation", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "race_ethnicity_national_origin", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "religion", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "age_discrimination", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "disability", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "marital_family_status", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "military_status", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "public_assistance", "action": "MASK"}
],
"pattern_redaction_format": "[PROTECTED_CLASS_INFO_REDACTED]"
},
"guardrail_info": {
"description": "Masks protected class information for HR compliance and anti-discrimination"
}
}
],
"templateData": {
"policy_name": "advanced-pii-protection-australia",
"description": "Comprehensive PII detection and masking policy for Australia. Protects Australian-specific identifiers, international employee data, financial information, credentials, protected class information, and industry-specific sensitive data.",
"guardrails_add": [
"au-pii-tax-identifiers",
"au-pii-passports",
"international-pii-identifiers",
"contact-information-pii",
"financial-pii",
"credentials-api-keys",
"network-infrastructure-pii",
"protected-class-information"
],
"guardrails_remove": []
}
},
{
"id": "baseline-pii-protection",
"title": "Baseline PII Protection",
"description": "Baseline PII protection for internal tools and testing. Focuses on credentials and high-risk identifiers only. Suitable for non-sensitive internal use.",
"icon": "ShieldCheckIcon",
"iconColor": "text-blue-500",
"iconBg": "bg-blue-50",
"guardrails": [
"au-pii-tax-identifiers",
"credentials-api-keys",
"financial-pii"
],
"complexity": "Low",
"guardrailDefinitions": [
{
"guardrail_name": "au-pii-tax-identifiers",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "au_tfn", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "au_abn", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "au_medicare", "action": "MASK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {"description": "Masks Australian Tax File Numbers, Business Numbers, and Medicare Numbers"}
},
{
"guardrail_name": "credentials-api-keys",
"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 API keys and credentials (AWS, GitHub, Slack)"}
},
{
"guardrail_name": "financial-pii",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "visa", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "mastercard", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "amex", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "discover", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "credit_card", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "iban", "action": "MASK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {"description": "Masks financial information including credit cards and bank account numbers"}
}
],
"templateData": {
"policy_name": "baseline-pii-protection",
"description": "Baseline PII protection for internal tools and testing. Focuses on credentials and high-risk identifiers only.",
"guardrails_add": [
"au-pii-tax-identifiers",
"credentials-api-keys",
"financial-pii"
],
"guardrails_remove": []
}
}
]

View File

@ -13,15 +13,3 @@ model_list:
- model_name: gpt-4.1-mini
litellm_params:
model: openai/gpt-4.1-mini
guardrails:
- guardrail_name: redact-ssn
litellm_params:
guardrail: custom_code
mode: pre_call
custom_code: |
def apply_guardrail(inputs, request_data, input_type):
for text in inputs["texts"]:
if regex_match(text, r"\d{3}-\d{2}-\d{4}"):
return block("SSN detected in message")
return allow()

View File

@ -367,6 +367,43 @@
"pattern": "\\b\\d{1,2}\\.\\d{3}\\.\\d{3}-[\\dXx]\\b",
"category": "Brazilian PII Patterns",
"description": "Detects Brazilian RG identity card numbers (common pattern for SP, RJ, MG states)"
},
{
"name": "au_tfn",
"display_name": "TFN (Australian Tax File Number)",
"pattern": "\\b\\d{8,9}\\b",
"category": "PII Patterns",
"description": "Detects Australian Tax File Numbers (8-9 digits) only when near TFN/tax file context to avoid false positives on arbitrary numbers",
"keyword_pattern": "\\b(?:TFN|T\\.F\\.N\\.|tax\\s*file\\s*(?:number|no\\.?)|tax\\s*file\\s*#?|ATO\\s*number)\\b",
"allow_word_numbers": false
},
{
"name": "au_abn",
"display_name": "ABN (Australian Business Number)",
"pattern": "\\b\\d{2}\\s?\\d{3}\\s?\\d{3}\\s?\\d{3}\\b",
"category": "PII Patterns",
"description": "Detects Australian Business Numbers (11 digits, optional spaces: XX XXX XXX XXX)"
},
{
"name": "au_medicare",
"display_name": "Medicare Number (Australia)",
"pattern": "\\b(?:\\d{4}\\s?\\d{5}\\s?\\d{1}|\\d{11})\\b",
"category": "PII Patterns",
"description": "Detects Australian Medicare numbers (formatted XXXX XXXXX X or 11 consecutive digits)"
},
{
"name": "iban",
"display_name": "IBAN (International Bank Account Number)",
"pattern": "\\b[A-Z]{2}\\d{2}[A-Z0-9]{11,30}\\b",
"category": "Payment Card Patterns",
"description": "Detects IBANs (2 letter country code + 2 check digits + 4 char bank code + 7 digit base + optional 0-16 alphanumeric)"
},
{
"name": "street_address",
"display_name": "Street Address",
"pattern": "\\b\\d{1,6}\\s+[A-Za-z0-9][A-Za-z0-9\\s.'-]*\\s+(?:Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Drive|Dr|Lane|Ln|Way|Court|Ct|Place|Pl|Circle|Cir)\\b",
"category": "PII Patterns",
"description": "Detects street addresses (number + street name + street type)"
}
]
}

View File

@ -6,8 +6,12 @@ All /policy management endpoints
/policy/validate - Validate a policy configuration
/policy/list - List all loaded policies
/policy/info - Get information about a specific policy
/policy/templates - Get policy templates (GitHub with local fallback)
"""
import json
import os
from fastapi import APIRouter, Depends, HTTPException, Request
from litellm._logging import verbose_proxy_logger
@ -257,3 +261,64 @@ async def test_policy_matching(
matching_policies=matching_policy_names,
resolved_guardrails=resolved_guardrails,
)
POLICY_TEMPLATES_GITHUB_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/policy_templates.json"
def _load_policy_templates_from_local_backup() -> list:
"""Load policy templates from local backup file (litellm/policy_templates_backup.json)."""
backup_path = os.path.join(
os.path.dirname(__file__),
"..",
"..",
"policy_templates_backup.json",
)
path = os.path.abspath(backup_path)
if not os.path.exists(path):
return []
with open(path, "r") as f:
return json.load(f)
@router.get(
"/policy/templates",
tags=["policy management"],
dependencies=[Depends(user_api_key_auth)],
)
@management_endpoint_wrapper
async def get_policy_templates(
request: Request,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
) -> list:
"""
Get policy templates for the UI (pre-configured guardrail combinations).
Fetches from GitHub with automatic fallback to local backup on failure.
Set LITELLM_LOCAL_POLICY_TEMPLATES=true to skip GitHub and use local backup only.
"""
use_local = os.getenv("LITELLM_LOCAL_POLICY_TEMPLATES", "").strip().lower() in (
"true",
"1",
"yes",
)
if use_local:
return _load_policy_templates_from_local_backup()
try:
from litellm.llms.custom_httpx.http_handler import get_async_httpx_client
from litellm.types.llms.custom_http import httpxSpecialProvider
async_client = get_async_httpx_client(
llm_provider=httpxSpecialProvider.UI,
params={"timeout": 10.0},
)
response = await async_client.get(POLICY_TEMPLATES_GITHUB_URL)
if response.status_code == 200:
return response.json()
except Exception as e:
verbose_proxy_logger.debug(
"Failed to fetch policy templates from GitHub, using local backup: %s", e
)
return _load_policy_templates_from_local_backup()

278
policy_templates.json Normal file
View File

@ -0,0 +1,278 @@
[
{
"id": "advanced-au-pii-protection",
"title": "Advanced PII Protection (Australia)",
"description": "Comprehensive PII detection and masking for Australia. Protects Australian-specific identifiers, international employee data, financial information, credentials, protected class information, and industry-specific sensitive data.",
"icon": "ShieldCheckIcon",
"iconColor": "text-purple-500",
"iconBg": "bg-purple-50",
"guardrails": [
"au-pii-tax-identifiers",
"au-pii-passports",
"international-pii-identifiers",
"contact-information-pii",
"financial-pii",
"credentials-api-keys",
"network-infrastructure-pii",
"protected-class-information"
],
"complexity": "High",
"guardrailDefinitions": [
{
"guardrail_name": "au-pii-tax-identifiers",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{
"pattern_type": "prebuilt",
"pattern_name": "au_tfn",
"action": "MASK"
},
{
"pattern_type": "prebuilt",
"pattern_name": "au_abn",
"action": "MASK"
},
{
"pattern_type": "prebuilt",
"pattern_name": "au_medicare",
"action": "MASK"
}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "Masks Australian Tax File Numbers, Business Numbers, and Medicare Numbers"
}
},
{
"guardrail_name": "au-pii-passports",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{
"pattern_type": "prebuilt",
"pattern_name": "passport_australia",
"action": "MASK"
}
],
"pattern_redaction_format": "[PASSPORT_REDACTED]"
},
"guardrail_info": {
"description": "Masks Australian passport numbers"
}
},
{
"guardrail_name": "international-pii-identifiers",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "us_ssn", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "us_ssn_no_dash", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_us", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_uk", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_germany", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_france", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_netherlands", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "nl_bsn_contextual", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_china", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_india", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_japan", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "passport_canada", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_cpf", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_cpf_unformatted", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_rg", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_cnpj", "action": "MASK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "Masks international PII identifiers including passports and national IDs"
}
},
{
"guardrail_name": "contact-information-pii",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "email", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "us_phone", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_phone_landline", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_phone_mobile", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "street_address", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "br_cep", "action": "MASK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "Masks contact information including emails, phone numbers, and addresses"
}
},
{
"guardrail_name": "financial-pii",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "visa", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "mastercard", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "amex", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "discover", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "credit_card", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "iban", "action": "MASK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {
"description": "Masks financial information including credit cards and bank account numbers"
}
},
{
"guardrail_name": "credentials-api-keys",
"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 API keys and credentials (AWS, GitHub, Slack)"
}
},
{
"guardrail_name": "network-infrastructure-pii",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "ipv4", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "ipv6", "action": "MASK"}
],
"pattern_redaction_format": "[INTERNAL_IP_REDACTED]"
},
"guardrail_info": {
"description": "Masks IP addresses in requests"
}
},
{
"guardrail_name": "protected-class-information",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "gender_sexual_orientation", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "race_ethnicity_national_origin", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "religion", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "age_discrimination", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "disability", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "marital_family_status", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "military_status", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "public_assistance", "action": "MASK"}
],
"pattern_redaction_format": "[PROTECTED_CLASS_INFO_REDACTED]"
},
"guardrail_info": {
"description": "Masks protected class information for HR compliance and anti-discrimination"
}
}
],
"templateData": {
"policy_name": "advanced-pii-protection-australia",
"description": "Comprehensive PII detection and masking policy for Australia. Protects Australian-specific identifiers, international employee data, financial information, credentials, protected class information, and industry-specific sensitive data.",
"guardrails_add": [
"au-pii-tax-identifiers",
"au-pii-passports",
"international-pii-identifiers",
"contact-information-pii",
"financial-pii",
"credentials-api-keys",
"network-infrastructure-pii",
"protected-class-information"
],
"guardrails_remove": []
}
},
{
"id": "baseline-pii-protection",
"title": "Baseline PII Protection",
"description": "Baseline PII protection for internal tools and testing. Focuses on credentials and high-risk identifiers only. Suitable for non-sensitive internal use.",
"icon": "ShieldCheckIcon",
"iconColor": "text-blue-500",
"iconBg": "bg-blue-50",
"guardrails": [
"au-pii-tax-identifiers",
"credentials-api-keys",
"financial-pii"
],
"complexity": "Low",
"guardrailDefinitions": [
{
"guardrail_name": "au-pii-tax-identifiers",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "au_tfn", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "au_abn", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "au_medicare", "action": "MASK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {"description": "Masks Australian Tax File Numbers, Business Numbers, and Medicare Numbers"}
},
{
"guardrail_name": "credentials-api-keys",
"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 API keys and credentials (AWS, GitHub, Slack)"}
},
{
"guardrail_name": "financial-pii",
"litellm_params": {
"guardrail": "litellm_content_filter",
"mode": "pre_call",
"patterns": [
{"pattern_type": "prebuilt", "pattern_name": "visa", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "mastercard", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "amex", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "discover", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "credit_card", "action": "MASK"},
{"pattern_type": "prebuilt", "pattern_name": "iban", "action": "MASK"}
],
"pattern_redaction_format": "[{pattern_name}_REDACTED]"
},
"guardrail_info": {"description": "Masks financial information including credit cards and bank account numbers"}
}
],
"templateData": {
"policy_name": "baseline-pii-protection",
"description": "Baseline PII protection for internal tools and testing. Focuses on credentials and high-risk identifiers only.",
"guardrails_add": [
"au-pii-tax-identifiers",
"credentials-api-keys",
"financial-pii"
],
"guardrails_remove": []
}
}
]

View File

@ -5438,6 +5438,32 @@ export const getPolicyInfoWithGuardrails = async (accessToken: string, policyNam
}
};
export const getPolicyTemplates = async (accessToken: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/policy/templates` : `/policy/templates`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to get policy templates:", error);
throw error;
}
};
export const createPolicyCall = async (accessToken: string, policyData: any) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies` : `/policies`;

View File

@ -41,7 +41,9 @@ const AddPolicyForm: React.FC<AddPolicyFormProps> = ({
const [availableModels, setAvailableModels] = useState<string[]>([]);
const { userId, userRole } = useAuthorized();
const isEditing = !!editingPolicy;
// Only consider it "editing" if editingPolicy has a policy_id (real existing policy)
// If editingPolicy is set but has no policy_id, it's just pre-filled data for a new policy (e.g., from a template)
const isEditing = !!editingPolicy?.policy_id;
useEffect(() => {
if (visible && editingPolicy) {

View File

@ -0,0 +1,244 @@
import React, { useState, useEffect } from "react";
import { Modal, Checkbox, Button, Divider, Tag, Spin } from "antd";
import { CheckCircleOutlined, InfoCircleOutlined } from "@ant-design/icons";
interface GuardrailInfo {
guardrail_name: string;
description: string;
alreadyExists: boolean;
definition: any;
}
interface GuardrailSelectionModalProps {
visible: boolean;
template: any;
existingGuardrails: Set<string>;
onConfirm: (selectedGuardrails: any[]) => void;
onCancel: () => void;
isLoading?: boolean;
}
const GuardrailSelectionModal: React.FC<GuardrailSelectionModalProps> = ({
visible,
template,
existingGuardrails,
onConfirm,
onCancel,
isLoading = false,
}) => {
const [selectedGuardrails, setSelectedGuardrails] = useState<Set<string>>(
new Set()
);
// Prepare guardrail info with existence status
const guardrailsInfo: GuardrailInfo[] = (
template?.guardrailDefinitions || []
).map((def: any) => ({
guardrail_name: def.guardrail_name,
description: def.guardrail_info?.description || "No description available",
alreadyExists: existingGuardrails.has(def.guardrail_name),
definition: def,
}));
// Initialize selection: select only new guardrails by default
useEffect(() => {
if (visible && template) {
const newGuardrails = guardrailsInfo
.filter((g) => !g.alreadyExists)
.map((g) => g.guardrail_name);
setSelectedGuardrails(new Set(newGuardrails));
}
}, [visible, template]);
const handleToggle = (guardrailName: string) => {
setSelectedGuardrails((prev) => {
const newSet = new Set(prev);
if (newSet.has(guardrailName)) {
newSet.delete(guardrailName);
} else {
newSet.add(guardrailName);
}
return newSet;
});
};
const handleSelectAll = () => {
const allNew = guardrailsInfo
.filter((g) => !g.alreadyExists)
.map((g) => g.guardrail_name);
setSelectedGuardrails(new Set(allNew));
};
const handleDeselectAll = () => {
setSelectedGuardrails(new Set());
};
const handleConfirm = () => {
const selectedDefinitions = guardrailsInfo
.filter((g) => selectedGuardrails.has(g.guardrail_name))
.map((g) => g.definition);
onConfirm(selectedDefinitions);
};
const newGuardrailsCount = guardrailsInfo.filter(
(g) => !g.alreadyExists
).length;
const existingCount = guardrailsInfo.filter((g) => g.alreadyExists).length;
const selectedCount = selectedGuardrails.size;
return (
<Modal
title={
<div>
<h3 className="text-lg font-semibold mb-1">{template?.title}</h3>
<p className="text-sm text-gray-500 font-normal">
Review and select guardrails to create for this template
</p>
</div>
}
open={visible}
onCancel={onCancel}
width={700}
footer={[
<Button key="cancel" onClick={onCancel} disabled={isLoading}>
Cancel
</Button>,
<Button
key="confirm"
type="primary"
onClick={handleConfirm}
loading={isLoading}
disabled={selectedCount === 0 && existingCount === 0}
>
{selectedCount > 0
? `Create ${selectedCount} Guardrail${selectedCount > 1 ? "s" : ""} & Use Template`
: "Use Template"}
</Button>,
]}
>
<div className="py-4">
{/* Summary Stats */}
<div className="flex items-center gap-4 mb-4 p-3 bg-blue-50 rounded-lg border border-blue-100">
<InfoCircleOutlined className="text-blue-600 text-lg" />
<div className="flex-1">
<div className="text-sm">
<span className="font-medium text-gray-900">
{guardrailsInfo.length} total guardrails
</span>
<span className="text-gray-600 mx-2"></span>
<span className="text-green-600 font-medium">
{newGuardrailsCount} new
</span>
{existingCount > 0 && (
<>
<span className="text-gray-600 mx-2"></span>
<span className="text-gray-600">
{existingCount} already exist
</span>
</>
)}
</div>
</div>
{newGuardrailsCount > 0 && (
<div className="flex gap-2">
<Button size="small" onClick={handleSelectAll}>
Select All New
</Button>
<Button size="small" onClick={handleDeselectAll}>
Deselect All
</Button>
</div>
)}
</div>
{/* Guardrails List */}
<div className="space-y-3 max-h-96 overflow-y-auto">
{guardrailsInfo.map((guardrail) => (
<div
key={guardrail.guardrail_name}
className={`border rounded-lg p-4 ${
guardrail.alreadyExists
? "bg-gray-50 border-gray-200"
: "bg-white border-gray-300 hover:border-blue-400"
} transition-colors`}
>
<div className="flex items-start gap-3">
<div className="flex-shrink-0 pt-0.5">
{guardrail.alreadyExists ? (
<CheckCircleOutlined className="text-green-600 text-lg" />
) : (
<Checkbox
checked={selectedGuardrails.has(guardrail.guardrail_name)}
onChange={() => handleToggle(guardrail.guardrail_name)}
/>
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<span className="font-mono text-sm font-medium text-gray-900">
{guardrail.guardrail_name}
</span>
{guardrail.alreadyExists && (
<Tag color="green" className="text-xs">
Already exists
</Tag>
)}
</div>
<p className="text-sm text-gray-600">
{guardrail.description}
</p>
{/* Show guardrail type and mode */}
<div className="flex gap-2 mt-2">
<Tag className="text-xs">
{guardrail.definition?.litellm_params?.guardrail || "unknown"}
</Tag>
<Tag className="text-xs" color="blue">
{guardrail.definition?.litellm_params?.mode || "unknown"}
</Tag>
{guardrail.definition?.litellm_params?.patterns && (
<Tag className="text-xs" color="purple">
{guardrail.definition.litellm_params.patterns.length} pattern(s)
</Tag>
)}
</div>
</div>
</div>
</div>
))}
</div>
{guardrailsInfo.length === 0 && (
<div className="text-center py-8 text-gray-500">
<p>No guardrails defined for this template.</p>
<p className="text-sm mt-2">
This template will use existing guardrails in your system.
</p>
</div>
)}
<Divider />
{/* Selected Summary */}
<div className="text-sm text-gray-600">
{selectedCount > 0 ? (
<p>
<span className="font-medium text-gray-900">{selectedCount}</span>{" "}
guardrail{selectedCount > 1 ? "s" : ""} will be created
</p>
) : existingCount > 0 ? (
<p className="text-green-600">
All guardrails already exist. You can proceed to use this template.
</p>
) : (
<p className="text-orange-600">
Select at least one guardrail to create, or click "Use Template" to proceed without creating new guardrails.
</p>
)}
</div>
</div>
</Modal>
);
};
export default GuardrailSelectionModal;

View File

@ -9,6 +9,8 @@ import AddPolicyForm from "./add_policy_form";
import AttachmentTable from "./attachment_table";
import AddAttachmentForm from "./add_attachment_form";
import PolicyTestPanel from "./policy_test_panel";
import PolicyTemplates from "./policy_templates";
import GuardrailSelectionModal from "./guardrail_selection_modal";
import {
getPoliciesList,
deletePolicyCall,
@ -19,6 +21,7 @@ import {
createPolicyCall,
updatePolicyCall,
createPolicyAttachmentCall,
createGuardrailCall,
} from "../networking";
import {
Policy,
@ -49,6 +52,10 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({
const [isDeleting, setIsDeleting] = useState(false);
const [policyToDelete, setPolicyToDelete] = useState<Policy | null>(null);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isGuardrailSelectionModalOpen, setIsGuardrailSelectionModalOpen] = useState(false);
const [selectedTemplate, setSelectedTemplate] = useState<any>(null);
const [existingGuardrailNames, setExistingGuardrailNames] = useState<Set<string>>(new Set());
const [isCreatingGuardrails, setIsCreatingGuardrails] = useState(false);
const isAdmin = userRole ? isAdminRole(userRole) : false;
@ -172,16 +179,133 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({
fetchAttachments();
};
const handleUseTemplate = async (template: any) => {
if (!accessToken) {
message.error("Authentication required");
return;
}
try {
// Fetch existing guardrails to show in the modal
const existingGuardrailsResponse = await getGuardrailsList(accessToken);
const existingNames = new Set<string>(
existingGuardrailsResponse.guardrails?.map((g: any) => g.guardrail_name as string) || []
);
setExistingGuardrailNames(existingNames);
setSelectedTemplate(template);
setIsGuardrailSelectionModalOpen(true);
} catch (error) {
console.error("Error fetching guardrails:", error);
message.error("Failed to load guardrails. Please try again.");
}
};
const handleGuardrailSelectionConfirm = async (selectedGuardrailDefinitions: any[]) => {
if (!accessToken || !selectedTemplate) return;
setIsCreatingGuardrails(true);
try {
const createdGuardrails: string[] = [];
const failedGuardrails: string[] = [];
// Create selected guardrails
for (const guardrailDef of selectedGuardrailDefinitions) {
const guardrailName = guardrailDef.guardrail_name;
try {
await createGuardrailCall(accessToken, guardrailDef);
createdGuardrails.push(guardrailName);
console.log(`Successfully created guardrail: ${guardrailName}`);
} catch (error) {
console.error(`Failed to create guardrail "${guardrailName}":`, error);
failedGuardrails.push(guardrailName);
}
}
// Refresh guardrails list
await fetchGuardrails();
// Close modal
setIsGuardrailSelectionModalOpen(false);
setIsCreatingGuardrails(false);
// Pre-fill the add policy form with template data
setEditingPolicy(selectedTemplate.templateData as Policy);
setIsAddPolicyModalVisible(true);
setActiveTab(1); // Switch to Policies tab (now at index 1)
// Show success message
if (createdGuardrails.length > 0) {
message.success(
`Created ${createdGuardrails.length} guardrail${createdGuardrails.length > 1 ? "s" : ""}! Complete the policy form to save.`
);
} else {
message.success("Template ready! Complete the policy form to save.");
}
if (failedGuardrails.length > 0) {
message.warning(
`Failed to create ${failedGuardrails.length} guardrail(s): ${failedGuardrails.join(", ")}. You may need to create them manually.`
);
}
} catch (error) {
setIsCreatingGuardrails(false);
console.error("Error creating guardrails:", error);
message.error("Failed to create guardrails. Please try again.");
}
};
const handleGuardrailSelectionCancel = () => {
setIsGuardrailSelectionModalOpen(false);
setSelectedTemplate(null);
};
return (
<div className="w-full mx-auto flex-auto overflow-y-auto m-8 p-2">
<TabGroup index={activeTab} onIndexChange={setActiveTab}>
<TabList className="mb-4">
<Tab>Templates</Tab>
<Tab>Policies</Tab>
<Tab>Attachments</Tab>
<Tab>Policy Simulator</Tab>
</TabList>
<TabPanels>
<TabPanel>
<Alert
message="About Policies"
description={
<div>
<p className="mb-3">
Use policies to group guardrails and control which ones run for specific teams, keys, or models.
</p>
<p className="mb-2 font-semibold">Why use policies?</p>
<ul className="list-disc list-inside mb-3 space-y-1 ml-2">
<li>Enable/disable specific guardrails for teams, keys, or models</li>
<li>Group guardrails into a single policy</li>
<li>Inherit from existing policies and override what you need</li>
</ul>
<a
href="https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 underline inline-block mt-1"
>
Learn more in the documentation
</a>
</div>
}
type="info"
icon={<InfoCircleOutlined />}
showIcon
closable
className="mb-6"
/>
<PolicyTemplates onUseTemplate={handleUseTemplate} accessToken={accessToken} />
</TabPanel>
<TabPanel>
<Alert
message="About Policies"
@ -273,6 +397,15 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({
onOk={handleDeleteConfirm}
confirmLoading={isDeleting}
/>
<GuardrailSelectionModal
visible={isGuardrailSelectionModalOpen}
template={selectedTemplate}
existingGuardrails={existingGuardrailNames}
onConfirm={handleGuardrailSelectionConfirm}
onCancel={handleGuardrailSelectionCancel}
isLoading={isCreatingGuardrails}
/>
</TabPanel>
<TabPanel>

View File

@ -0,0 +1,181 @@
import React, { useState, useEffect } from "react";
import { Card, Button, Spin, message } from "antd";
import {
ShieldCheckIcon,
ShieldExclamationIcon,
BeakerIcon,
CurrencyDollarIcon,
CheckCircleIcon,
} from "@heroicons/react/outline";
import { getPolicyTemplates } from "../networking";
interface PolicyTemplateCardProps {
title: string;
description: string;
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
iconColor: string;
iconBg: string;
guardrails: string[];
inherits?: string;
complexity: "Low" | "Medium" | "High";
onUseTemplate: () => void;
}
const PolicyTemplateCard: React.FC<PolicyTemplateCardProps> = ({
title,
description,
icon: Icon,
iconColor,
iconBg,
guardrails,
inherits,
complexity,
onUseTemplate,
}) => {
const getComplexityStyle = () => {
switch (complexity) {
case "Low":
return "bg-gray-50 text-gray-600 border-gray-200";
case "Medium":
return "bg-blue-50 text-blue-600 border-blue-100";
case "High":
return "bg-purple-50 text-purple-600 border-purple-100";
}
};
return (
<Card
className="h-full hover:shadow-md transition-shadow"
bodyStyle={{ display: "flex", flexDirection: "column", height: "100%" }}
>
<div className="flex items-start justify-between mb-4">
<div className={`p-2 rounded-lg ${iconBg}`}>
<Icon className={`h-6 w-6 ${iconColor}`} />
</div>
<span
className={`px-2.5 py-0.5 rounded-full text-xs font-medium border ${getComplexityStyle()}`}
>
{complexity} Complexity
</span>
</div>
<h3 className="text-base font-semibold text-gray-900 mb-2">{title}</h3>
<p className="text-sm text-gray-500 mb-6 flex-grow">{description}</p>
{inherits && (
<div className="mb-4 text-xs">
<span className="text-gray-500">Inherits from: </span>
<span className="font-medium text-gray-700 bg-gray-100 px-2 py-0.5 rounded">
{inherits}
</span>
</div>
)}
<div className="mb-6">
<span className="text-xs font-medium text-gray-500 uppercase tracking-wider block mb-2">
Included Guardrails
</span>
<div className="flex flex-wrap gap-2">
{guardrails.map((g) => (
<span
key={g}
className="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-gray-50 text-gray-700 border border-gray-200"
>
{g}
</span>
))}
</div>
</div>
<Button
type="primary"
block
className="mt-auto"
onClick={onUseTemplate}
>
Use Template
</Button>
</Card>
);
};
interface PolicyTemplatesProps {
onUseTemplate: (templateData: any) => void;
accessToken: string | null;
}
// Map icon names from JSON to actual icon components
const iconMap: Record<string, React.ComponentType<React.SVGProps<SVGSVGElement>>> = {
ShieldCheckIcon: ShieldCheckIcon,
ShieldExclamationIcon: ShieldExclamationIcon,
BeakerIcon: BeakerIcon,
CurrencyDollarIcon: CurrencyDollarIcon,
CheckCircleIcon: CheckCircleIcon,
};
const PolicyTemplates: React.FC<PolicyTemplatesProps> = ({ onUseTemplate, accessToken }) => {
const [templates, setTemplates] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchTemplates = async () => {
if (!accessToken) return;
setIsLoading(true);
try {
const data = await getPolicyTemplates(accessToken);
setTemplates(data);
} catch (error) {
console.error("Error fetching policy templates:", error);
message.error("Failed to fetch policy templates");
} finally {
setIsLoading(false);
}
};
fetchTemplates();
}, [accessToken]);
if (isLoading) {
return (
<div className="flex justify-center items-center py-20">
<Spin size="large" tip="Loading policy templates..." />
</div>
);
}
return (
<div className="space-y-6">
<div className="flex justify-between items-end">
<div>
<h2 className="text-lg font-medium text-gray-900">
Policy Templates
</h2>
<p className="text-sm text-gray-500 mt-1">
Start with a pre-configured policy template to quickly set up
guardrails for your organization.
</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
{templates.map((template, index) => (
<PolicyTemplateCard
key={template.id || index}
title={template.title}
description={template.description}
icon={iconMap[template.icon] || ShieldCheckIcon}
iconColor={template.iconColor}
iconBg={template.iconBg}
guardrails={template.guardrails}
inherits={template.inherits}
complexity={template.complexity}
onUseTemplate={() => onUseTemplate(template)}
/>
))}
</div>
</div>
);
};
export default PolicyTemplates;

View File

@ -14,7 +14,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"jsx": "preserve",
"incremental": true,
"plugins": [
{