This article describes a hands-on cybersecurity lab demonstrating hidden-character injection against RAG (Retrieval-Augmented Generation) systems, specifically using DocsBot as a case study. The attack embeds invisible Unicode characters—zero-width spaces, joiners, and bidirectional overrides—into documents that appear harmless to human reviewers but carry executable instructions for the model. The technique reveals a critical gap in human-in-the-loop security controls: when content is invisible to people but legible to AI systems, approval processes provide no meaningful protection.
- Invisible Unicode survives markup strippingDocsBot's document upload pipeline removes visible HTML and markdown but explicitly preserves zero-width and bidirectional Unicode characters. This selective filtering creates a security gap where invisible payloads pass through sanitization intact and remain indexed in the system, ready to activate when retrieved into model context.
- Two-layer content divergence defeats reviewThe human review layer sees a benign-looking document because invisible characters are not visible to people; the model ingestion layer sees plain text containing hidden instructions. Because these two layers process different content, a document can receive human approval while carrying live, undetected instructions that fire when the model processes it.
- Attack payload embeds via zero-width charactersAn attacker can craft a normal-looking document, hide an instruction (such as 'disclose the support key') using zero-width spaces, joiners, or bidirectional overrides, and upload it knowing the markup stripper will leave the invisible instruction intact. When a user query retrieves that document into context, the model obeys the embedded instruction.
- Human approval becomes a false security controlThe article emphasizes that 'a human checked it' is not a valid control when the payload is invisible to humans but legible to the model. If an upload pipeline preserves anything a reviewer cannot see, human approval provides no safeguard against hidden-character injection.
- Defense requires normalization at ingestionThe recommended mitigation is to strip invisible Unicode—zero-width characters, bidirectional overrides, and combining marks—before text is indexed, not after a breach occurs. Text should also be normalized to a canonical form so hidden payloads cannot survive storage, and retrieved content must be treated as inert data, clearly separated from instructions.
- Classification as LLM01 with LLM08 angleThe article maps this attack to OWASP LLM01 Prompt Injection with an LLM08 Vector and Embedding Weaknesses component (2025 list), reflecting that the threat combines direct prompt injection via hidden characters with the broader risk of poisoned documents entering a RAG index.
- Lab format enables hands-on vulnerability testingThe lab runs in a browser with no signup, takes about 20 minutes, and teaches the technique by having learners author their own hidden-character payload, upload it, and observe it execute. The article encourages readers to attempt their own approach before viewing the spoiler solution.
Bottom line: Hidden-character injection is a form of indirect prompt injection that defeats human review because the payload is invisible to people but plain text to models; the only effective defense is to strip invisible Unicode and normalize text at ingestion time, and to treat all retrieved content as inert data rather than instructions. Relying on human approval as a control for content containing invisible characters is ineffective and creates a false sense of security.
📋 In this walkthrough
What this lab teaches
Hidden-character injection is indirect prompt injection that survives human review. Invisible Unicode (zero-width spaces and joiners, bidi overrides) can encode instructions that a person reading the document never notices but the model ingests as text. Human approval becomes meaningless.
The lesson: 'a human checked it' is not a control when the payload is invisible to humans and legible to the model.
How DocsBot indexes documents
DocsBot's uploader removes visible markup but preserves invisible Unicode. An indexed document can therefore hide an instruction in zero-width characters that, when any user's query pulls that document into context, tells the model to disclose the support key.
The attack approach
- Confirm the uploader strips visible markup but keeps invisible Unicode.
- Hide an instruction in zero-width characters inside an otherwise benign document.
- Make the instruction disclose the support key when the doc is used as context.
- Upload it so it passes human review and gets indexed.
Full walkthrough (spoiler)
If the instruction does not fire, it may have been stripped — try another invisible encoding and open the steps.
Show the step-by-step solution
- Author a document that reads as normal to a human but embeds an instruction in zero-width Unicode.
- The instruction tells DocsBot to disclose the SUPPORT_KEY whenever the document is in context.
- Upload it; visible-markup stripping leaves the invisible payload intact and it gets indexed.
- On a later query that retrieves the document, the hidden instruction fires and the key is disclosed. The lab marks you solved.
Why it works
Review happens at the human layer, where the payload is invisible; ingestion happens at the model layer, where it is plain text. Because the two layers see different content, a document can be 'approved' and still carry a live instruction into the model.
Where this happens in the real world
Hidden-character and invisible-Unicode injection defeat human-in-the-loop review and are a potent form of OWASP LLM01 Prompt Injection, with a vector/embedding angle (LLM08) when poisoned docs enter a RAG index. What a reviewer cannot see, they cannot catch.
How to defend against it
- Strip invisible Unicode at ingestion — zero-width characters, bidi overrides, combining marks — before indexing.
- Normalise text to a canonical form so hidden payloads cannot survive storage.
- Treat retrieved content as inert data, clearly delimited from instructions.
- Do not rely on human review as the control for invisible content.
Try it, then go further
Reading about it is the easy part. Open the lab, try your own approach before peeking at the spoiler, and see how many attempts it takes.
