Trojan Source: when code does not do what it looks like it does
· 6 min read
Unicode's bidirectional algorithm lets embedded control characters reorder how a line is displayed. Applied to source code, that means a reviewer can read a line as one thing while the compiler reads it as another. The controls are invisible, so the discrepancy is not apparent from the rendered text.
How the reordering works
Unicode has to display text that mixes right-to-left and left-to-right scripts, so it defines control characters that override the natural direction of a run: embeddings (U+202A, U+202B), overrides (U+202D, U+202E), a terminator (U+202C), and a newer set of isolates (U+2066–U+2069).
These characters are invisible and they affect display only. A compiler, interpreter or parser reads the underlying code point order. An editor shows the reordered result. When those two differ, a reviewer approves one thing and the machine runs another.
Detection
- Directional overrides (U+202D, U+202E) have essentially no legitimate use in source code and should be treated as findings on sight.
- Embeddings and isolates are legitimate in text that genuinely mixes scripts, so the signal there is imbalance — an opener without its matching terminator.
- Counting has to be right: U+202C terminates embeddings and overrides alike, so a checker that counts overrides as non-openers while counting their terminators will report imbalances that do not exist.
- Homoglyphs are the sibling problem: a Cyrillic "а" is a different identifier from a Latin "a" while looking identical. Flag mixed-script identifiers rather than silently substituting characters.
Practical defense
Scan for bidirectional controls in continuous integration and fail the build on directional overrides in source files. Most compilers now warn about them, but a check that runs before code review is better than one that runs after approval.
For text rather than code the same controls are worth reporting, though the judgment differs: a document legitimately mixing Arabic and English may use them correctly, so removal should be a considered choice rather than a default.
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.
- Invisible prompt injection with Unicode Tags
How the U+E0000 block hides instructions that people cannot see but language models read, why it works, and how to strip it before untrusted text reaches a model.