Invisible prompt injection with Unicode Tags
· 8 min read
The Unicode Tags block encodes printable ASCII as code points that render as nothing. Text containing them looks innocuous to a human reviewer while a language model consuming the raw string reads the hidden instructions as ordinary input. The defense is to strip the block — and decode it first, so you can see what was attempted.
The mechanism
Each printable ASCII character has a counterpart in the Tags block, offset by U+E0000. The letter "A" (U+0041) becomes U+E0041. A sequence of them spells out arbitrary text that no mainstream renderer displays.
Nothing about this is a bug in Unicode. The block was assigned for language tagging, that use was deprecated, and the code points remain in the standard. What makes it dangerous is the gap between two consumers of the same string: a person sees the visible text, and a model sees every code point.
Visible to a person: Please summarize this document. Actual code points: Please summarize this document. U+E0069 U+E0067 U+E006E U+E006F U+E0072 U+E0065 ... → decodes to: "ignore all previous instructions"
Where it shows up
- Documents and email forwarded into a summarization or triage assistant.
- Support tickets and form submissions processed by an automated agent.
- Web pages retrieved by a browsing model.
- Résumés, contracts and reports reviewed with model assistance.
- Code review comments and commit messages read by a coding agent.
Why filtering the visible text is not enough
Guardrails that inspect what a human would read miss this entirely, because the hidden characters are not part of what a human reads. A moderation pass over the rendered string sees nothing wrong.
Normalization does not remove it either. Unicode normalization forms — NFC, NFD, NFKC, NFKD — leave tag characters intact, because they are valid assigned code points with no decomposition.
The one legitimate exception
Three emoji are built from this block: the flags of England, Scotland and Wales. Each is a black flag (U+1F3F4) followed by tag letters spelling a subdivision code, terminated by U+E007F.
A sanitizer that strips the whole block indiscriminately turns those flags into a plain black flag, and a detector that reports the whole block indiscriminately will tell a user their Welsh flag contains a prompt-injection payload. Both are avoidable: keep a tag run only when it is a well-formed, terminated subdivision sequence following a black flag.
Frequently asked
- Do all language models read Unicode Tags characters?
- It depends on tokenization, and behavior differs between models and versions. That variability is exactly why it should not be relied on as a defense: sanitize the input rather than assuming a particular model ignores it.
- Is this the same as jailbreaking?
- No. Jailbreaking is a user trying to make a model bypass its own guidelines. This is a third party hiding instructions inside content that someone else feeds to a model, so the victim is the person or system doing the processing.
Keep reading
- Invisible Unicode characters: a complete reference
Every category of invisible, zero-width and blank-rendering Unicode character, what each one is legitimately for, and when its presence is a signal that something is hidden in your text.
- Do AI watermarks use invisible characters? No — and here is the evidence
SynthID-Text and Anthropic's Claude watermark are statistical, living in the model's word choices rather than in any added character. Stripping invisible characters has no effect on them whatsoever.
- Zero-width steganography: how text hides inside text
StegCloak, base-N zero-width encodings and variation-selector channels — the practical schemes for hiding data in a string, their fingerprints, and how detection works.