Documentation
Content rules applied to every request the firewall proxies — block secrets, redact PII, cap runaway agent loops. Three rule types, three actions, evaluated in priority order.
A guardrail is a content rule applied to every request the firewall proxies, on
every surface, from every client. A rule you write once covers a curl, a Codex
turn, and a Claude Code tool result alike.
That is the whole system. It is small on purpose — everything below is built out of these nine combinations.
| Rule type | What pattern means |
|---|---|
blocked_pattern | An RE2 regular expression, matched against the request’s text |
pii_redact | An optional regex. Leave it empty to use the four built-in detectors (email, credit-card-like number, US SSN, phone number) |
max_messages | An integer — the maximum number of messages in a conversation |
| Action | What happens |
|---|---|
flag | The request proceeds. The match is recorded. Nothing is blocked. |
redact | Matched spans are rewritten to the literal [REDACTED] and the request proceeds. |
block | The request is rejected with a 403, naming the rule that fired. |
flag. You get the signal without the risk, you find
out what it actually matches in your traffic, and only then do you decide whether
it should redact or block. This ladder is the single most useful habit with this
feature — see rollout.priority — lowest number first.block short-circuits: evaluation stops there and nothing after it runs.redact mutates the request in place, and later rules see the already-redacted
text.Priority is therefore how you order a redaction before a block that would otherwise have fired on the same text.
HTTP 403, naming the rule, in the dialect of whatever client asked:
{
"error": {
"message": "request blocked by AI firewall policy: content matched pattern for policy \"no-connection-strings\"",
"type": "policy_violation",
"code": "request_blocked",
"blocked_by": "no-connection-strings",
"violations": [
{
"policy_name": "no-connection-strings",
"rule_type": "blocked_pattern",
"action": "block",
"detail": "content matched pattern for policy \"no-connection-strings\""
}
]
}
}
The violations array names the rules that fired and why. It never contains the
content that triggered them — not the matched substring, not the surrounding
text. That holds even for flag, which records only that a rule matched.
Everything you send: the system prompt, every message, multi-modal text parts, and — this is the one that matters for coding agents — tool results. When an agent reads a file, runs a command, or pastes a diff back into the conversation, that content is a tool result, and it goes through your guardrails.
Two things are not inspected, and you should know both:
Both are covered honestly in limitations.
Every rule can be created three ways, and they are equivalent:
vulnetix ai-firewall policy guardrail no-connection-strings \
--rule-type blocked_pattern \
--action block \
--pattern '(?i)postgres://\S+' \
--priority 10 \
--enable
# .vulnetix/ai-firewall.yaml
apiVersion: vulnetix.com/v1
kind: AiFirewallPolicy
spec:
guardrails:
- name: no-connection-strings
ruleType: blocked_pattern
action: block
pattern: '(?i)postgres://\S+'
priority: 10
enabled: true
vulnetix ai-firewall apply --dry-run # review
vulnetix ai-firewall apply # commit
See policy as code.
If the guardrail backend is unreachable, the gateway fails closed by default:
the request is refused with a 503 rather than forwarded unscreened. This is the
right default for a firewall and you should leave it alone.
Note that this switch applies only to guardrails. Authentication and provider/model policy are always fail-closed and cannot be configured otherwise — a backend outage can never turn into an unauthenticated request reaching a provider.