AI security
Hardening a pipeline against prompt injection
Content you did not write can hijack your model. Here is the layered sanitization I run, and exactly what it does not catch.
the problem
Any pipeline that reads the open web reads content nobody on the team wrote. It can carry a fake closing tag followed by 'ignore previous instructions'.
A single tag stripper feels safe and is not. Zero-width characters and unicode variants hide inside it, so the defense has to be layered and honest about its limits.
architecture
raw external content
|
strip zero-width + control chars <- NFKC alone misses these
|
normalize unicode (NFKC)
|
drop script/style, then strip tags
|
best-effort phrase net (not a boundary)
|
v
inert text -> caller wraps it as DATAThe robust fix is structural, not clever. Sanitize to inert text, then have the caller wrap it in a delimiter the content cannot contain and tell the model the block is data. Scrubbing phrases is a backstop, never the boundary.
Order matters, and zero-width chars come off first.
import re, unicodedata
ZERO_WIDTH = re.compile(r"[\u200b\u200c\u200d\ufeff]")
def sanitize(text: str) -> str:
text = ZERO_WIDTH.sub("", text) # hidden chars first
text = unicodedata.normalize("NFKC", text) # fold compatibility forms
text = re.sub(r"<(script|style)\b[^>]*>.*?</\1>", " ", text, flags=re.I | re.S)
text = re.sub(r"<[^>]*>", " ", text) # then the generic strip
return text.strip()what is still broken
- ▸It reduces a real attack surface; it does not guarantee an injection-proof input.
- ▸The phrase net is English-only and best-effort. The actual boundary is the structural wrap, not the regex.