Configure guardrails
For: tenant administrators and plugin users.
Look up settings that control the guardrail checks.
Settings
Everything is a tenant setting of the plugin. Without any, every pack is on, personal data and secrets are redacted, injections escalate, the command guard is on and the intent check is off.
oz0 setting set GUARDRAILS_CONFIG '{"packs": {"pii.dk": false}, "pii": "redact", "injection": "escalate",
"agents": {"support-triage": {"pii": "escalate", "intent": "on"}}}' --reason "Danish numbers are order numbers here"
Key in GUARDRAILS_CONFIG | Values | Default |
|---|---|---|
packs | {"<pack id>": true or false} | every pack on |
pii | redact, escalate (a person decides whether the value goes on), refuse (the model gets a refusal instead of the result; the job fails at job_end), off | redact |
secrets | redact, refuse or off: never escalate, since the person asked would read the secret | redact |
injection | escalate, refuse, off | escalate |
intent | on, off | off |
commands | escalate (the command guard sends a catastrophic or unreadable shell command to a person, on a node whose runtime does not decide commands itself) or off; what the platform's command rules ask about is changed with command rules, not here | escalate |
agents | the same keys per agent name; an agent's entry overrides the tenant's key by key, and its packs merge pack by pack | none |
When several findings disagree, refuse wins over escalate, and escalate over redact; but when a value holds a
secret too, an escalate for personal data becomes a redaction, so that nobody is shown the secret. A setting the plugin cannot
read makes its checks fail closed: each result and output goes to a person with the reason, and none passes
unchecked; the command guard stays on then, since it only ever asks a person. Changing a setting restarts the plugin's
server on the nodes.
GUARDRAILS_CONFIG is a governance setting: it turns
safety checks off, so changing it needs the governance-write permission besides settings-write and a reason (--reason,
or $OZ0_REASON), and the audit log keeps the reason. A change that loosens a check (one turned off or to a weaker
action, or a pack turned off, for the tenant or an agent) waits for a second admin where the tenant has four eyes on;
one that only tightens applies at once. A value the plugin would not read is refused when it is set.
| Setting | What it holds |
|---|---|
GUARDRAILS_CONFIG | The JSON above. A governance setting: changing it takes a reason |
GUARDRAILS_PACKS, GUARDRAILS_PACKS_2, _3, _4 | Extra packs: a JSON array of packs, or gz: and the base64 of its gzip, since a setting holds at most 4 KiB. A pack with a baseline pack's ID replaces it, except a classified pack, which an extra pack may add to but never replace |
GUARDRAILS_CLASSIFIED_LOCAL | The classification markings allowed into agents whose data stays on the node, by ID, such as ["se_begransat_hemlig"] (see Classified text). A governance setting: changing it takes a reason |
GUARDRAILS_INTENT_MODEL | The model the intent check asks, by the provider's name, such as claude-haiku-4-5. Another model than the agents it judges run on: it never judges a call on the model that made it (see The intent check) |
GUARDRAILS_INTENT_PROVIDER | anthropic (the default) or openai: which API the check speaks to the gateway |
Your own packs
A pack is JSON in the format oz0.guardrails.pack/v1:
{
"format": "oz0.guardrails.pack/v1",
"id": "acme.customers",
"title": "Acme customer numbers",
"version": "1",
"kind": "pii",
"description": "Customer numbers of the form AC-123456.",
"patterns": [
{
"id": "customer_number",
"label": "customer number",
"regex": "\\bAC-[0-9]{6}\\b",
"false_positives": "Order numbers of the old shop used the same prefix.",
"examples": {"match": ["customer AC-123456"], "no_match": ["AC-12345", "BAC-123456"]}
}
]
}
kind is pii, secrets or injection. A pattern's regex must be valid in both Go's and Python's syntax (no
back-references, look-arounds or inline flags), and it must not be one of the shapes that take minutes in a
backtracking engine on a long result someone wrote to stall it. The plugin refuses a pack whose pattern has:
- a repetition without an upper bound (
+,*,{2,}) anywhere but at the pattern's end: write[A-Za-z0-9._-]{1,64}@rather than[A-Za-z0-9._-]+@; - a count over 1000;
- a group repeated more than once that holds a repetition of a varying count, such as
(a+)+or(?:a{1,8}){2,}; - a repeated group whose alternatives can start with the same character, such as
(?:A|AA){1,40};(?:[0-9]|-[a-z])*is fine; - runs that can match the same text side by side, whose ways to split a text multiply past 4096 at one position, such
as
[A-Z0-9]{1,1000}[A-Z0-9]{1,1000}. Put a character between them that neither matches, or lower their bounds:[A-Z0-9]{1,64}[A-Z0-9]{1,64}is at the limit.
These rules are an estimate, not a promise that a pattern is quick: a pattern at the limit still takes seconds on 1 MiB of text. So every check also runs under a deadline of 3 seconds. A check that runs past it is stopped, and the hook fails closed: the result goes to a person, or is refused when it holds one of the tenant's secrets. Keep your patterns well inside the limits, and anchor them with a word or a fixed prefix where you can (\bAC-[0-9]{6}\b).
flags: ["i"] ignores case; validate names one of the checks above (luhn, mod97, se_pnr, se_samordning, no_fnr, dk_cpr, fi_hetu, is_kt, de_idnr, de_idcard, and isin and lei for securities and legal-entity identifiers by their check digits); context lists words of which one must be near a match; redact_as is the text a match is replaced with (at most 64 characters), instead of [PII:<label>]. The plugin checks every pack when it starts (the format, the validator's name, that the regex compiles and that every example holds) and fails closed when one does not. Packs are data, never code.
A pack may also set action: the least its findings get, whatever its check's action says. redact, escalate or refuse for a pii pack, redact or refuse for a secrets pack (secrets never go to a person), escalate or refuse for an injection pack. A pack of secrecy references (such as "sekretess enligt 21 kap. 7 § OSL") sets "action": "escalate", so such a document goes to a person while personal data elsewhere is still redacted: the person reads the value with the personal data redacted and the marking left in, and a yes passes on that redacted value.
When the same value also holds a secret, it is redacted instead and nobody is asked. A stricter action of the check still wins (refuse stays refuse), and a check or pack you turn off stays off.