# Guardrails

Guardrails inspect request and response content. Use this page to choose a rule, understand its failure behavior and verify how it affects accounting. For a complete setup procedure, follow [write and enforce a guardrail](how-to/write-and-enforce-a-guardrail.md); for the request flow, see the [guardrail design](design/guardrails.md).

## The model: a chain with modes

Input rules run before dispatch; output rules run on the response. Each stage evaluates an ordered chain.

| Mode | Content behavior | Default behavior if evaluation errors |
|---|---|---|
| `off` | Rule is inactive | No evaluation |
| `monitor` | Records a `flag` for a would-be block; leaves the request unchanged | Fails open |
| `enforce` | Blocks, redacts or transforms; a block stops the stage | Fails closed with a distinct retryable error |

An evaluation error means the check could not run. It is different from a content violation. Per-rule `fail_open_on_error: true` overrides the default failure policy; the default is `false`.

Observe `failed_open` and `failed_closed` by rule at `GET /admin/ops/guardrails/health`.

> **Regex exceptions:** a pattern exceeding its ReDoS budget is skipped as no-match with a warning log. An invalid pattern is dropped at boot with a warning log. Neither path appears as an evaluation-error counter on the health page.

## The rules

The shipped catalog contains 26 rules: 21 rule-based rules and five external-service rules. `GET /admin/control/effective` supplies the running chain's parameters, types, bounds and live-editability.

### Input content safety

| Rule | Purpose |
|---|---|
| `secrets` | Detect credential-shaped text |
| `pii` | Redact or block email, SSN, credit card and phone patterns; regex by default, Presidio pluggable |
| `prompt-injection-heuristic` | Detect configured injection patterns heuristically |
| `blocked-pattern` | Apply an operator regex list |

### Input shape and hygiene

Use the following rules for structural checks and transformations:

- **Counts:** `max-messages`, `char-count`, `word-count`, `sentence-count`.
- **Validation:** `case-check`, `contains`, `ends-with`.
- **Transformation:** `add-prefix`, `regex-replace`.

`contains` is a blocklist by default; `not` inverts its condition. `add-prefix` injects a system or user prefix.

### Output checks

| Rule family | Rules and behavior |
|---|---|
| Sensitive content | `response-pii`, `response-blocked-pattern` |
| Size and presence | `max-response-tokens`, `not-null` |
| Code and structure | `contains-code` (require or forbid a code block), `json-keys`, `json-schema` |
| Links | `valid-urls`, optionally resolving URLs within a budget |

`max-response-tokens` compares `usage.completion_tokens`, including reasoning tokens normalized into that count by an adapter. It defaults to 32,768 and allows a completion exactly at the limit. Prompt tokens are governed by context admission.

### External services

External rules can receive request or response content. Review their data handling before enabling them.

| Rule | External dependency |
|---|---|
| `openai-moderation` | OpenAI moderation |
| `mistral-moderation` | Mistral moderation |
| `azure-content-safety` | Azure Content Safety |
| `webhook` | Your service; timeout follows the configured failure policy |
| `log` | Payload collector; monitor behavior |

## Configuring a rule

Update live fields through `PUT /admin/control/guardrails/{rule}`. For example:

```json
{"enabled": true, "mode": "monitor", "params": {"words": ["codename"], "operator": "any"}}
```

Follow these configuration rules:

1. Nest rule parameters under `params`. A flat body can return 204 while ignoring the parameters.
2. Read `GET /admin/control/effective` after writing to confirm the applied value.
3. Change regex patterns through deployment configuration and restart. Patterns for `blocked-pattern`, `response-blocked-pattern` and `regex-replace` are compiled and ReDoS-screened at boot; stored live changes are refused.

External-rule secrets are write-only. Readback reports presence, never the value. Omitting a secret preserves it; explicit `null` clears it.

## Test before traffic

Use `POST /admin/guardrails/dry-run` with a prompt and, optionally, a response.

The dry run:

- Returns per-rule verdicts: `would pass`, `would block`, `would redact: N` or `not reached`.
- Shadow-evaluates disabled rules so you can inspect their effect before enabling them.
- Writes no audit rows and changes no metrics.
- Makes no external-service calls; those rows report `external - not evaluated`.

## Scopes: tightening only

Tenant admins configure organization overrides through `PUT /admin/control/guardrails/{rule}/tenant?org=...`. The operator controls how each override composes with the global rule.

| `tenant_policy` | Composition | Default? |
|---|---|---|
| `locked` | Ignore the tenant row | No |
| `tighten_only` | Enabled composes by OR; enforce wins over monitor; caps take the minimum | **Yes** |
| `tenant_may_relax` | Tenant value wins for this rule at tenant level | No |

Inspect the workspace-wide result at `GET /admin/control/guardrails/workspace-effective`.

## What a fire leaves behind

A rule fire records its rule, stage, action and mode in `guardrail_events`. An enforcing block appears in the request ledger as `guardrail_violation`.

| Block stage | Provider work | Accounting result |
|---|---|---|
| Input | No provider call | Zero provider tokens and cost |
| Output | Provider already answered | Settle its tokens and cost against the requesting key, even though the caller receives 400 |

An output-blocked request still counts against the key's request rate. Refusal messages identify the rule without echoing matched content.

## What this cannot do

Rules can have false positives and false negatives. A word list or structural validator does not establish intent, factual correctness or protection against every injection or disclosure technique. Review the [security posture](security.md) and validate rules against representative application traffic.
