A pattern can prove text is unsafe. No pattern can prove text is safe. That asymmetry is the whole design of a guardrail, and getting it backwards quietly turns a safety product into a regular expression.
The tempting optimisation
Checking text with a language model costs 200 to 2,000 milliseconds. A regular expression costs under a microsecond. When your product sits between a chatbot and its user, that difference is the product.
So you write a fast path: scan for the obvious things first, and only pay for the model when the scan finds nothing. It looks like a pure win. It is half a win, and the other half is a hole.
Absence of a match is not evidence of safety. A scan that finds nothing has learned nothing — it has only established that the text does not look like the things you thought to write down.
Consider what a pattern cannot see: “Our supplement is FDA approved and cures anxiety in two weeks.” No credit card number. No SSN. No injection string. Nothing to match. It is also a hallucinated medical and regulatory claim that could end a company.
If your fast path approves that because it matched no pattern, you have not built a guardrail. You have built a regex, and your customers cannot tell which verdicts were real.
Three outcomes, not two
The correct structure has a third state, and it is the important one.
Certain — decide it
Some matches cannot reasonably be innocent: a card number that passes a Luhn check, a live-looking API key, an explicit instruction to disregard previous instructions. Escalating those to a model means paying 200ms to confirm something already known.
Uncertain — escalate it, and say what you saw
This is the category people collapse, in one direction or the other. An email address in “write to us at help@example.com” is a support bot doing its job. The same address in a dump of customer records is a breach. A line beginning System: might be an injected turn or an order update.
Treat those as violations and you reject ordinary support traffic. Ignore them and you miss real leaks. Neither is acceptable, so the pattern does not decide — it escalates, and carries what it noticed to the model as an observation to weigh in context:
A deterministic pre-scan flagged the following as worth attention.
They are observations, not conclusions — judge them in context and
disregard any that are innocent here:
- Email address detectedYou get the speed of the scan without the scan acquiring a verdict it cannot justify.
Nothing found — escalate it
The default is the model. Not because the scan is bad, but because “I did not recognise anything” and “this is safe” are different statements and only one of them is true.
What this costs, honestly
It costs the headline. You cannot claim sub-millisecond latency for the general case, because the general case reaches a model. What you can claim is that definite violations are caught for free, that repeated text is answered from cache, and that everything else gets a real judgment.
Publish which stage answered. We return X-LLMFence-Source: fastpath | cache | model on every response, so a customer can measure the mix themselves rather than trusting a number in our marketing. A guardrail that will not tell you how it decided is asking for more trust than it has earned.
The failure mode to design against
The dangerous failure is not a guardrail that blocks too much. It is one that silently stops checking, because it keeps returning approved and everything looks fine.
Which is also why analysis should fail closed. If the model call fails, times out, or returns something that does not satisfy the schema, the honest answer is a rejection — not an approval with a shrug. An approval you cannot justify is worse than an outage, because an outage is visible.
This is how LLMFence is built. The latency documentation sets out each stage and what it will and will not decide.