Regex reference
Guardrail patterns are compiled with RE2, the regular-expression engine used by Go. If you have written a regex before, almost everything works exactly as you expect. Three things do not, and the reason is worth understanding.
What is not supported
| Not supported | Example |
|---|---|
| Lookahead | foo(?=bar), foo(?!bar) |
| Lookbehind | (?<=foo)bar, (?<!foo)bar |
| Backreferences | (\w+)\s+\1 |
These are not missing features. RE2 excludes them deliberately, because they are what make a regex engine capable of catastrophic backtracking — the failure mode where a short, innocuous-looking pattern takes exponential time on a crafted input.
Every guardrail pattern runs on every request your organisation makes, against text an attacker can choose. A backtracking engine in that position is a denial-of-service vector aimed at your entire AI capability. RE2 matches in time linear in the length of the input, always, regardless of the pattern. You cannot write a rule that hangs the firewall, and nobody can send a prompt that does.
That trade is the right one, and it is not negotiable.
What is supported
Everything else you are likely to reach for:
| Feature | Example |
|---|---|
| Character classes | [a-z], [^0-9], \d, \w, \s |
| Quantifiers | *, +, ?, {3}, {2,5}, {13,16} |
| Lazy quantifiers | *?, +?, {2,5}? |
| Alternation | `foo |
| Grouping | (abc)+, non-capturing (?:abc)+ |
| Named groups | (?P<name>\w+) |
| Anchors | ^, $, \b, \B |
| Case-insensitive flag | (?i)secret |
| Multiline / dot-all flags | (?m), (?s) |
| Unicode classes | \p{L}, \p{Han} |
Porting from PCRE
If you are bringing patterns over from a secret scanner, a SIEM rule, or
grep -P, the lookarounds are what will break. Common rewrites:
A negative lookahead used to exclude a known-safe prefix:
(?<!example\.)api\.internal ✗ not supported
Instead, match what you do want and accept the broader match, then narrow it with a more specific pattern — or split into two rules where one flags and a higher-priority one allows by simply not matching. There is no “exclude” primitive; see tuning false positives.
A backreference used to find repeated tokens:
(\b\w+\b)\s+\1 ✗ not supported
RE2 cannot express this at all. Rewrite around the specific literal you care about.
The failure mode you must know about
A rule whose pattern does not compile is silently skipped.
It stays in your policy. It shows as enabled. It matches nothing, forever. The
gateway does not error, does not warn the caller, and does not fail the request — it
simply evaluates the remaining rules as though that one did not exist.
This means a typo in a regex can silently disable a control you believe is protecting you.
This is why the CLI compiles every pattern locally and tells you:
vulnetix ai-firewall status
A bad pattern shows up as a guardrail_pattern_invalid warning, with a hint if the
cause is a lookaround. Run it after every rule change. In CI:
vulnetix ai-firewall status --strict
The policy-as-code path validates patterns too: apply
rejects the whole file if any pattern will not compile, so a bad regex never reaches
your organisation’s policy in the first place. That is the safest way to manage
rules.
The same applies to max_messages
A max_messages rule whose pattern is not an integer is skipped by exactly the
same mechanism. --pattern 40 is a rule; --pattern forty is a rule that does
nothing.
Testing a pattern
RE2’s syntax is documented in the
Go regexp/syntax package. Any Go regex
playground will tell you whether a pattern compiles and what it matches — that is
exactly the engine the firewall runs.
Then deploy it at --action flag and watch what it does to real traffic before you
let it block or rewrite anything. See rollout.