This commit is contained in:
Ninad Phalak 2026-10-03 23:09:51 +00:00 • committed by GitHub
commit 798bb0d3bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 3291 additions and 2 deletions

View file

@ -0,0 +1,33 @@
from typing import TYPE_CHECKING, Final
from litellm.types.guardrails import SupportedGuardrailIntegrations
from .llm_shield_proxy import LLMShieldProxyGuardrail
if TYPE_CHECKING:
from litellm.types.guardrails import Guardrail, LitellmParams
def initialize_guardrail(litellm_params: "LitellmParams", guardrail: "Guardrail") -> LLMShieldProxyGuardrail:
import litellm
_llm_shield_guardrail_callback: Final = LLMShieldProxyGuardrail(
api_key=litellm_params.api_key,
api_base=litellm_params.api_base,
guardrail_name=guardrail.get("guardrail_name", ""),
event_hook=litellm_params.mode,
default_on=litellm_params.default_on,
)
litellm.logging_callback_manager.add_litellm_callback(_llm_shield_guardrail_callback)
return _llm_shield_guardrail_callback
guardrail_initializer_registry: Final = {
SupportedGuardrailIntegrations.LLM_SHIELD_PROXY.value: initialize_guardrail,
}
guardrail_class_registry: Final = {
SupportedGuardrailIntegrations.LLM_SHIELD_PROXY.value: LLMShieldProxyGuardrail,
}

View file

@ -0,0 +1,57 @@
# Example LiteLLM Proxy configuration for LLM Shield Proxy
# LLM Shield Proxy is a self-hosted PII gateway: https://github.com/ninadphalak/LLM-Shield-Proxy
#
# Unlike a masking guardrail, LLM Shield Proxy's substitution is reversible. Personal data is
# replaced with placeholders before the request goes to the provider, and the original
# values are put back into the model's reply, so the end user still sees real data while
# the provider never received it.
model_list:
- model_name: gpt-4o
litellm_params:
model: openai/gpt-4o
api_key: os.environ/OPENAI_API_KEY
guardrails:
# Both modes belong on ONE entry. pre_call redacts the outbound request and post_call
# restores the reply; listing only pre_call would send placeholders back to the user.
- guardrail_name: "llm_shield_proxy"
litellm_params:
guardrail: llm_shield_proxy
mode: ["pre_call", "post_call"]
default_on: true
# Your own LLM Shield Proxy deployment. Defaults to http://localhost:8000, and also reads
# LLM_SHIELD_PROXY_API_BASE from the environment.
api_base: "http://localhost:8000"
# A virtual key configured on that deployment. Also reads LLM_SHIELD_PROXY_API_KEY.
api_key: os.environ/LLM_SHIELD_PROXY_API_KEY
# Usage:
#
# 1. Run LLM Shield Proxy somewhere the proxy can reach:
# pip install llm-shield-proxy
# llm-shield-proxy --port 8000
#
# 2. Point this config at it and start the proxy:
# export LLM_SHIELD_PROXY_API_KEY="your-virtual-key"
# litellm --config example_config.yaml
#
# 3. Send a request containing personal data:
# curl http://localhost:4000/v1/chat/completions \
# -H "Authorization: Bearer sk-1234" \
# -H "Content-Type: application/json" \
# -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Email jane.doe@example.com the invoice"}]}'
#
# The provider receives a stand-in value in place of the address. The reply you get
# back carries the real address again.
#
# Notes:
#
# - Requests are refused if LLM Shield Proxy is unreachable or returns an error, rather than
# being forwarded. Sending them on would hand the provider exactly the data this
# guardrail exists to withhold.
# - Restoring a value requires the request and the reply to share a session. LiteLLM's
# session id is used when present; otherwise one is generated per request.
# - Streaming replies are restored as chunks arrive. A placeholder split across two
# chunks is held back until it is complete, so partial values are never emitted.
# - Only text is redacted; images and audio pass through untouched.

File diff suppressed because it is too large Load diff

View file

@ -145,6 +145,7 @@ class SupportedGuardrailIntegrations(Enum):
STRAIKER = "straiker"
ALICE = "alice"
AGENT_365 = "agent_365"
LLM_SHIELD_PROXY = "llm_shield_proxy"
CONDUCT = "conduct"

View file

@ -0,0 +1,24 @@
from pydantic import Field
from .base import GuardrailConfigModel
class LLMShieldProxyGuardrailConfigModel(GuardrailConfigModel):
api_key: str | None = Field(
default=None,
description=(
"The virtual key for the LLM Shield Proxy instance. If not provided, the "
"`LLM_SHIELD_PROXY_API_KEY` environment variable is checked."
),
)
api_base: str | None = Field(
default=None,
description=(
"The base URL of the LLM Shield Proxy instance. If not provided, the `LLM_SHIELD_PROXY_API_BASE` "
"environment variable is checked, then `http://localhost:8000`."
),
)
@staticmethod
def ui_friendly_name() -> str:
return "LLM Shield Proxy"

View file

@ -30,6 +30,10 @@ external = [
# grows over time; typing it concretely (`object`) broke that forwarding call outright —
# basedpyright turned every named param into a reportArgumentType error. Any is correct here.
"litellm/proxy/guardrails/guardrail_hooks/alice/alice.py" = ["ANN401"]
# Same reason: `**kwargs` forwards verbatim to CustomGuardrail.__init__, and the lifecycle
# hook signatures inherit `Any` for `response` from CustomLogger, so narrowing them here
# would break the override rather than describe it.
"litellm/proxy/guardrails/guardrail_hooks/llm_shield_proxy/llm_shield_proxy.py" = ["ANN401"]
[lint.mccabe]
max-complexity = 15

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24" rx="8" fill="#0B1F33"/>
<path d="M12 4L18 6.35V11.5C18 15.28 15.62 18.2 12 19.4C8.38 18.2 6 15.28 6 11.5V6.35L12 4Z" fill="#BDEFF7"/>
<path d="M12 4L18 6.35V11.5C18 15.28 15.62 18.2 12 19.4V4Z" fill="#3FC5DE"/>
<path d="M12 4L18 6.35V11.5C18 15.28 15.62 18.2 12 19.4C8.38 18.2 6 15.28 6 11.5V6.35L12 4Z" stroke="#6FE3F2" stroke-width="0.9" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 498 B

View file

@ -73,7 +73,9 @@ interface GuardrailPreset {
provider: string;
categoryName?: string;
guardrailNameSuggestion: string;
mode: string;
// A guardrail that both rewrites the request and repairs the response needs two
// modes seeded, not one; the form already normalises either shape.
mode: string | string[];
defaultOn: boolean;
}

View file

@ -2,7 +2,9 @@ export interface GuardrailPreset {
provider: string;
categoryName?: string;
guardrailNameSuggestion: string;
mode: string;
// A guardrail that both rewrites the request and repairs the response needs two
// modes seeded, not one; the form already normalises either shape.
mode: string | string[];
defaultOn: boolean;
}
@ -325,6 +327,14 @@ export const GUARDRAIL_PRESETS: Record<string, GuardrailPreset> = {
// MCP-only: default_on is the only activation path on the MCP hook
defaultOn: true,
},
llm_shield_proxy: {
provider: "LLM Shield Proxy",
guardrailNameSuggestion: "LLM Shield Proxy",
// Both halves are required. With only pre_call the request is redacted and the
// placeholders are handed straight back to the caller.
mode: ["pre_call", "post_call"],
defaultOn: false,
},
conduct: {
provider: "Conduct",
guardrailNameSuggestion: "Conduct Guard",

View file

@ -29,6 +29,7 @@ const EXPECTED_PARTNER_LOGO_FILES: Record<string, string> = {
straiker: "straiker.svg",
alice: "alice.svg",
agent_365: "microsoft_azure.svg",
llm_shield_proxy: "llm_shield_proxy.svg",
conduct: "conduct.png",
};

View file

@ -484,6 +484,16 @@ export const PARTNER_GUARDRAIL_CARDS: GuardrailCardInfo[] = [
tags: ["Agentic", "MCP", "Tool Misuse", "Observability"],
providerKey: "Agent365",
},
{
id: "llm_shield_proxy",
name: "LLM Shield Proxy",
description:
"Self-hosted PII redaction that puts the original values back into the model's response, so the provider never receives personal data while the end user still sees it.",
category: "partner",
logo: guardrailLogoMap["LLM Shield Proxy"],
tags: ["PII", "Data Privacy", "Compliance", "Streaming"],
providerKey: "LLM Shield Proxy",
},
{
id: "conduct",
name: "Conduct Guard",

View file

@ -1,6 +1,7 @@
import aimSecurityLogo from "../../../../../public/assets/logos/aim_security.jpeg";
import aktoLogo from "../../../../../public/assets/logos/akto.svg";
import aliceLogo from "../../../../../public/assets/logos/alice.svg";
import llmShieldProxyLogo from "../../../../../public/assets/logos/llm_shield_proxy.svg";
import conductLogo from "../../../../../public/assets/logos/conduct.png";
import aporiaLogo from "../../../../../public/assets/logos/aporia.png";
import bedrockLogo from "../../../../../public/assets/logos/bedrock.svg";
@ -86,6 +87,7 @@ export const guardrail_provider_map: Record<string, string> = {
QostodianNexus: "qostodian_nexus",
Repelloai: "repelloai",
Alice: "alice",
"LLM Shield Proxy": "llm_shield_proxy",
Conduct: "conduct",
};
@ -211,6 +213,7 @@ export const guardrailLogoMap = {
Straiker: straikerLogo.src,
Alice: aliceLogo.src,
"Microsoft Agent 365": microsoftAzureLogo.src,
"LLM Shield Proxy": llmShieldProxyLogo.src,
"Conduct Guard": conductLogo.src,
} satisfies Record<string, string>;