Guardrails, and why they leak
Filters, classifiers and safety prompts are worth having. Just be honest about what they are: speed bumps, not walls.
In 60 seconds
Guardrails, and why they leak
Filters, classifiers and safety prompts are worth having. Just be honest about what they are: speed bumps, not walls.
- 1
Input guardrails
Scan what arrives before the model sees it. Known attack phrases, hidden characters, suspicious formatting, off-topic requests. - 2
Output guardrails
Scan what the model produced before anyone acts on it. Leaked secrets, disallowed content, URLs pointing somewhere strange. - 3
Action guardrails
Check the proposed tool call before it runs. Amount limits, recipient allow-lists, forbidden operations.
Why language guardrails leak
| Trick | Example | Why the filter misses it |
|---|---|---|
| Another language | The instruction in Hungarian | The filter was tuned on English |
| Encoding | Base64, ROT13, hex | The filter sees noise; the model decodes it |
| Splitting | Instruction spread over table cells | No single chunk matches a pattern |
| Roleplay | "In this story, the assistant explains..." | The literal words are innocent |
| In an image | Text rendered as a picture | Text filters do not read pictures |
| Slow build-up | Ten harmless turns, then the ask | Each turn passes on its own |
So why use them?
- They stop the lazy 90% for almost no cost, which meaningfully reduces noise.
- They create signal: a blocked attempt is a log line telling you someone is probing.
- They enforce non-security policy well — off-topic, tone, compliance language — where a determined adversary is not the threat model.
- They buy time while you build the controls that actually hold.
Build the stack in the right order
Watch and read more
Lab
A guardrail you beat five ways, and one you cannot.
The problem
BLOCKLIST = ["ignore previous", "system prompt", "reveal your instructions"]
def input_guard(text: str) -> bool:
low = text.lower()
return not any(b in low for b in BLOCKLIST)
# Now the other kind:
def action_guard(tool: str, args: dict) -> str | None:
if tool == "refund" and args["amount"] > 5000:
return "refunds over 5000 require a human"
return NoneYou are done when
Hard questions
Try to answer before you reveal. If you can answer these, you understood the lesson.
Q1Why is the action guard unbeatable in a way the input filter is not? Be precise about the mechanism.Reveal
amount > 5000 is either true or false, and no phrasing changes the number. Guarding meaning is a losing search problem; guarding a value is arithmetic.Q2Give a case where a language guardrail is the correct primary control.Reveal
Questions people ask
Are commercial guardrail products worth it?
They save you building and maintaining pattern lists, and they come with useful telemetry. They do not change the fundamentals. Buy one if it saves time; do not let purchasing it substitute for layers 1 to 3.
Can a model check its own output?
It catches obvious slips and is cheap to add. It fails when the same injection that steered the first pass steers the check, which is exactly the case you needed it for. Never make self-check your only gate on an irreversible action.
What should I actually block on the way out?
Anything resembling a credential, any URL not on your allow-list, and any content that would auto-execute or auto-load where it is rendered. Those three cover the large majority of real leaks.
How do I measure whether my guardrails work?
Red-team them — Module 23. Run a fixed set of attacks, record the pass rate, and re-run it on every change. A guardrail with no measured bypass rate is a guardrail with an unknown bypass rate.
Lesson test
5 questions. Get 3 right (60%) to pass and complete this lesson.
Sign in with your phone number to take the test and save your progress