Skip to content

Guardrails

Guardrails inspect the content of requests and responses in real time — before a prompt reaches a provider and before a response reaches the caller. A guardrail config bundles a set of built-in checks, the surfaces they run on, and the resource groups they apply to (group_ids). Multiple configs can apply to the same request; the strictest outcome wins.

Guardrail configs list

Check What it detects Providers Key settings
Prompt injection Attempts to override instructions built_in, lakera_guard prompt_injection_sensitivity (0.0–1.0)
Jailbreak Role-play / policy-evasion attacks built_in, lakera_guard
PII Personal data in traffic built_in, presidio pii_categories, pii_threshold (Presidio score threshold)
Toxic content Hate, harassment, violence, sexual content, self-harm openai_moderation, built_in toxic_content_threshold, per-category thresholds
Secrets Credentials leaking through prompts/outputs built-in secrets_categories, secrets_threshold
Banned words Literal word/phrase list built-in banned_words, banned_words_match_mode (substring / word_boundary)
Banned patterns Regex list built-in banned_patterns

Category values you can select:

  • PII (pii_categories): email, phone, ssn, credit_card, iban, ipv4, ipv6, openai_api_key, anthropic_api_key, aws_access_key, jwt, private_key
  • Secrets (secrets_categories): aws_access_key, aws_secret_key, github_token, openai_key, gcp_service_account, private_key_pem, jwt, generic_high_entropy
  • Toxicity per-category thresholds (toxic_content_category_thresholds) use the OpenAI Moderation taxonomy: hate, hate/threatening, harassment, harassment/threatening, self-harm, self-harm/intent, self-harm/instructions, sexual, sexual/minors, violence, violence/graphic

Each check has an action: block (reject the request/response), redact (mask the offending spans and let the traffic continue — PII and secrets are the typical redact candidates), warn (log a violation, let it pass), or allow.

This is the most common source of “my guardrail isn’t firing” confusion, so read this twice.

Surfaces tab on a guardrail config

A check runs on a surface only where BOTH layers enable it:

  1. Config-level surfaces map — turns each surface on/off for the whole config.
  2. Per-check *_surfaces list (e.g. pii_surfaces) — the surfaces that specific check participates in. If unset, the check uses its built-in default surface set.
check fires on surface S ⇔ surfaces[S] == true AND S ∈ <check>_surfaces

Example. This config enables the mcp_input surface, but the PII check lists only chat surfaces — so PII will not scan MCP tool arguments, even though the surface is on:

{
"surfaces": { "chat_input": true, "chat_output": true, "mcp_input": true },
"builtin": {
"pii_enabled": true,
"pii_action": "redact",
"pii_surfaces": ["chat_input", "chat_output"]
}
}

To scan MCP arguments too, add "mcp_input" to pii_surfaces. Conversely, a long per-check surface list is inert on any surface the config-level map leaves off.

LLM surfaces: chat_input, chat_output, embeddings_input, image_gen_input, image_gen_output, audio_tts_input, audio_tts_output, audio_stt_input, audio_stt_output, video_gen_input, video_gen_output, batch_input, batch_output, moderation_input

Non-LLM surfaces: mcp_input, mcp_output, mcp_registration, skill_input, skill_output, a2a_inbound, a2a_outbound

Per-surface overrides exist for most checks (pii_per_surface, toxic_content_per_surface, secrets_per_surface, banned_words_per_surface, …) so you can, for example, run PII with redact on chat but block on MCP output. For banned words/patterns, a per-surface words/patterns field replaces the global list on that surface.

Output checks on SSE streams need a strategy: you can’t redact a token that has already left the building. streaming_output_mode on the config controls this:

Mode Behavior
auto (default) Uses sync whenever any active output check can block or redact; otherwise async
sync Buffer-and-release — the stream is buffered, checked, then released. Supports redaction and guarantees nothing violating is emitted, at the cost of time-to-first-token
async Async-interrupt — tokens flow immediately; the check runs concurrently and terminates the stream on a violation. Block-only (no redaction), minimal latency, but some content may reach the client before the interrupt

On an async-mode block, the client receives an error event followed by [DONE]:

data: {"error":{"type":"guardrail_block","message":"Output blocked by guardrail policy"}}
data: [DONE]

The providers array configures external engines used by the checks:

{
"providers": [
{ "provider": "presidio", "enabled": true, "endpoint": "http://presidio-analyzer:3030" },
{ "provider": "lakera", "enabled": true, "api_key": "..." },
{ "provider": "openai_moderation", "enabled": true, "api_key": "..." }
]
}

Supported provider values: lakera, openai_moderation, presidio, bedrock (requires config.guardrail_id), custom (requires endpoint). Presidio requires an endpoint — the trial bundle ships a presidio-analyzer service behind the presidio compose profile. Provider api_key values are encrypted at rest and never returned by GET.

PII check backed by Microsoft Presidio

Per-check fail_open_overrides (keys: pii, prompt_injection, jailbreak, toxic_content, banned_words, banned_patterns, secrets) control what happens when an external provider is unreachable.

Guardrail config editor — checks tab

In the Admin UI: Guardrails → New config, pick surfaces, enable checks, assign resource groups. Via the API:

Terminal window
curl -X POST http://localhost:5050/v1/admin/guardrails/configs \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "default-safety",
"description": "Baseline safety for all chat traffic",
"enabled": true,
"group_ids": ["9f2c..."],
"surfaces": {
"chat_input": true,
"chat_output": true,
"mcp_input": true
},
"builtin": {
"prompt_injection_enabled": true,
"prompt_injection_action": "block",
"jailbreak_enabled": true,
"jailbreak_action": "block",
"pii_enabled": true,
"pii_action": "redact",
"pii_provider": "presidio",
"pii_threshold": 0.6,
"pii_categories": ["email", "phone", "credit_card", "ssn"],
"pii_surfaces": ["chat_input", "chat_output", "mcp_input"],
"secrets_enabled": true,
"secrets_action": "block",
"secrets_categories": ["aws_access_key", "github_token", "openai_key", "private_key_pem", "jwt"]
},
"providers": [
{ "provider": "presidio", "enabled": true, "endpoint": "http://presidio-analyzer:3030" }
]
}'

Test a config against sample content without sending real traffic — in the Admin UI (Guardrails → Test) or via the API:

Guardrail test panel

Terminal window
curl -X POST http://localhost:5050/v1/admin/guardrails/test \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"content": "My card number is 4111 1111 1111 1111, please remember it",
"surface": "chat_input",
"config_id": "grc-..."
}'
{
"action": "redact",
"blocked": false,
"violations": [
{
"type": "pii",
"provider": "presidio",
"confidence": 0.95,
"action": "redact",
"detail": "credit_card detected",
"config_id": "grc-...",
"config_name": "default-safety"
}
],
"duration_ms": 41.7,
"surface": "chat_input",
"direction": "input",
"configs_evaluated": 1
}

Omit config_id to test what the tenant’s applicable configs would collectively do.

Non-streaming chat completions return 403 in the caller’s native error shape:

{
"error": {
"code": "guardrail_blocked",
"message": "Prompt injection detected",
"guardrail": "default-safety",
"check": "prompt_injection",
"direction": "input"
}
}

(Anthropic-native callers get the same fields wrapped in Anthropic’s {"type": "error", ...} envelope.)

Every block writes exactly one audit row, visible in the proxy logs with guardrail_blocked = true, and increments the proxy_guardrail_blocks_total Prometheus metric labeled by surface and check.