Guardrail checks#
| Task | Code |
|---|
| Find injection patterns | detect_injection(text) |
|---|
| Find PII in input | detect_pii(text) |
|---|
| Cap prompt size | check_length(text, max_chars=5000, max_words=1000) |
|---|
| Block toxic output | filter_toxicity(text) |
|---|
| Redact PII in output | scrubbed, result = scrub_pii_from_output(text) |
|---|
| Check that output answers input | check_relevance(input_text, output_text, threshold=0.15) |
|---|
| Catch system prompt echo | check_system_prompt_leak(output_text, system_prompt, threshold=0.4) |
|---|
| Run all checks around a model | pipeline.process(attack) |
|---|
The filter stack#
Moderation API call#
from openai import OpenAI
client = OpenAI()
response = client.moderations.create(
model="omni-moderation-latest",text and images in one call
input="<text>",
)
result = response.results[0]
print(result.flagged)True when a category fires
Thresholds in the code#
| Setting | Default | Note |
|---|
check_length max_chars | 5000 | Also max_words 1000 |
|---|
detect_injection block at | 0.75 | Max pattern confidence |
|---|
filter_toxicity block at | 0.80 | Max category confidence |
|---|
check_relevance threshold | 0.15 | Pass at or above |
|---|
check_system_prompt_leak threshold | 0.4 | Pass below |
|---|
input_moderator threshold | 0.5 | Same for output_moderator |
|---|
InstructionLeakageClassifier threshold | 0.55 | Trigram cosine |
|---|
_NEGATION_WINDOW | 20 | Chars checked for not, never, no |
|---|
Moderation systems side by side#
| Factor | OpenAI Moderation | Llama Guard | Perspective API |
|---|
| Model | omni-moderation-latest, GPT-4o | Llama-3.1-8B base (v3) | Pre-LLM toxicity scoring |
|---|
| Categories | 13 booleans | S1 to S14 in v4 | TOXICITY and 5 variants |
|---|
| Input | Text and images | v4: image and text | Text |
|---|
| Languages | 42% better multilingual | 8 languages (v3) | Not stated |
|---|
| Cost | Free for most developers | Open weights | Research baseline |
|---|
| Use it when | You want a hosted API | You self-host, S14 code abuse | You compare to research |
|---|
Severity rules#
- high
- block. Drop the output and return a policy refusal.
- medium
- redact. Apply each classifier redactor.
- low
- warn. Log and append a soft notice.
- none
- log. Ship the output as is.
- Max wins
- redact plus warn gives redact.
- pii
- ssn or card is high. Three findings or an email is medium.
- instruction-leakage
- 0.85 high, 0.70 medium, 0.55 low.
- toxicity
- 3 hits high, 2 medium, 1 low.
Output router#
Troubleshooting#
| Symptom | Cause | Fix |
|---|
| Emoji smuggled request passes | Tokenizer coalesces emoji differently | Add a normalization step |
|---|
| Cyrillic lookalike letters pass | Classifier trained on English | normalize(text): NFKC, homoglyph map |
|---|
| Encoding attack gets through | Input moderation only | Add output moderation |
|---|
| Every input reaches the model | Output moderation only | Add input moderation |
|---|
| "you are not a slur" trips the rule | No negation check | Negation window before the match |
|---|
| Rest of a large span shows | Small nested match removed | Merge intersecting spans to one union |
|---|
Redaction gate API#
| Task | Code |
|---|
| Find EMAIL, IPV4, LITERAL spans | detect(text, literals=()) |
|---|
| Join intersecting spans | merge_spans(spans, text_length) |
|---|
Replace with [REDACTED] | redact(text, literals=()) |
|---|
| Check up to 10000 records | process(records, literals=()) |
|---|
| Render the offset ledger | render_html(report) |
|---|
Run the projects#
| Command | What it does |
|---|
python3 scripts/project_test.py <project> --init learning-artifacts/<project> | Makes a learner workspace |
|---|
python3 scripts/project_test.py <project> --all --solution --strict | Tests the reference |
|---|
python3 cli.py samples/input.jsonl --literals samples/literals.json --output demo-output | Writes redacted.jsonl, receipt.json and review.html |
|---|
rustc --edition=2021 <dir>/cli.rs -o <dir>/firewall | Builds the firewall CLI |
|---|
<dir>/firewall <root> reader r1 read notes.md | Reads a file through the gate |
|---|
Q&A#
- Why check input and output?
- Attackers find ways around each layer alone.
- Is a guard classifier enough?
- No. Emoji Smuggling hit 100% ASR on six guard systems.
- Who supplies the role?
- The caller application. The model supplies only the operation.
- Output checks add latency?
- Run them in parallel with token streaming. Buffer the final chunk.