Prompt-injection detection

Rendered from docs/prompt-injection.md. Documents are rendered as written in the repository.

The gateway checks every message it scans against a corpus of known prompt-injection and jailbreak phrases before it redacts personal data. This page describes exactly what the code does: what is detected, what your client receives on a block, how it shows up in the console, and where the limits are.

What is detected

core/guardrails.py's check_injection runs an Aho-Corasick automaton built from core/data/jailbreak_signatures.txt against the text of every message slot in the request (not just the top-level content field - tool-call arguments and other text-bearing slots are checked too). Text is normalised first: lowercased, and every run of whitespace (including newlines, tabs and non-breaking spaces) collapsed to a single space. This closes the simplest evasion - splitting an attack phrase across a line break - without joining text across punctuation, which would invent false matches.

The signature list is deduplicated and reduced to shortest-stem form: if one signature is a substring of another, only the shorter one is kept, since the automaton already matches substrings.

Two modes: flag and block

Detection ships in flag mode by default. A match is logged (a PROMPT_INJECTION_DETECTED SIEM event, severity 9) and the request is still forwarded, masked as usual. Personal-data redaction is not affected either way - it is a separate pipeline stage that runs regardless of whether an injection signature matched.

An operator can opt a gateway into block mode (ZER0PII_INJECTION_MODE=block). In block mode, a match returns immediately:

  • HTTP 403
  • Problem type PROMPT_INJECTION_DETECTED
  • Title: "Prompt Injection Intercepted"
  • Detail: "The request matched known prompt-injection signatures."

The request never reaches the upstream model and is never scanned for personal data, because the block happens before that stage of the pipeline runs. The event is logged as PROMPT_INJECTION_BLOCK instead of PROMPT_INJECTION_DETECTED.

Flag mode is the default because this is a substring matcher over roughly a hundred known phrases, not a semantic classifier - a rephrased attack the corpus has not seen can evade it, and OWASP's own guidance treats input filtering as one weak layer of defence in depth rather than a gate to rely on alone. Blocking also does not change what happens to personal data in the request either way, since redaction is independent of this check; it protects the customer's upstream model from manipulation, not the data subject.

What the console shows

The console tracks two count-only counters, scoped to the org that sent the request, never the matched signature or the request content:

  • Detected: every request that matched, in either mode (flag or block).
  • Blocked: the subset that was actually rejected (block mode only). Every blocked request is also a detected one, so blocked can never exceed detected.

A gateway running the default flag mode will show a growing detected count with blocked staying at zero - that is expected, not a malfunction, since flag mode never rejects a request. The detected counter is incremented without adding latency to the forwarded request in flag mode; the blocked counter is incremented as part of the same request that returns the 403, since that request is not going anywhere else anyway.

Flag-mode detections are also visible in the audit log as PROMPT_INJECTION_DETECTED SIEM events; block-mode rejections as PROMPT_INJECTION_BLOCK events.

Limits

  • This is signature matching against a known-phrase corpus, not a live classifier. It changes when the repository is updated, not adaptively per request.
  • It is one layer of defence in depth. A prompt-injection attempt using wording the corpus has not seen can pass through undetected.
  • It has no bearing on personal-data redaction, which is a separate, always-on pipeline stage.