Trojan Source attacks use invisible Unicode bidirectional control characters to make code display one way and run another. To detect them in JavaScript, scan every changed file in CI for those characters and for mixed-script identifiers, fail the build on unexpected hits, and make reviewers' tools show the hidden characters.
What is a Trojan Source attack?
The researchers who published it describe the trick as using Unicode control characters to reorder tokens in source code at the encoding level. Compilers and interpreters follow the logical order of the characters, while a human reviewer sees the visual order. The two can disagree without any visible sign.
The attack is tracked as CVE-2021-42574. The CVE text notes the Unicode Consortium's view too: the visual order of tokens may differ from their logical order when text mixes left-to-right and right-to-left characters, and unless mitigated, an adversary could craft source code whose perceived order does not match what a compiler or interpreter processes.
The site lists three patterns: early returns that make a return statement appear inside a comment, commenting-out that makes a comment appear to be code, and stretched strings that make part of a string literal appear to be code. A related variant uses homoglyphs, near-identical characters from different scripts, and is tracked as CVE-2021-42694.
Why does it matter for supply chains?
The researchers point out that the risk is highest where code is accepted from others. If someone commits a targeted vulnerability into open source code by deceiving human reviewers, downstream software will likely inherit it. A reviewer who reads a pull request carefully still reads what the screen shows.
Constructed example: a contributor sends a small fix to a JavaScript package. A string comparison in an authorization helper looks correct on screen. Hidden control characters change which characters end up inside the quotes, so the check that runs differs from the one that appears. Review passes, tests that do not target the case pass, and the package ships.
Can ESLint catch it?
ESLint's no-irregular-whitespace rule targets whitespace characters that cause parser and readability problems, and its documentation lists issues with characters such as the Ogham space mark. We did not find bidirectional control characters named on that page, so do not assume the rule covers them. Check the current rule documentation and your ESLint version before relying on it.
A direct approach is a small check that looks for the specific code points. The bidirectional controls are U+202A through U+202E and U+2066 through U+2069.
const fs = require("fs");
const BIDI = /[--]/;
for (const file of process.argv.slice(2)) {
const lines = fs.readFileSync(file, "utf8").split("\n");
lines.forEach((line, i) => {
if (BIDI.test(line)) {
console.log(file + ":" + (i + 1) + " contains a bidirectional control character");
process.exitCode = 1;
}
});
}
Run it over changed files, including JSON, Markdown, configuration and generated code. You can wire the same pattern into a custom ESLint rule if you prefer reporting in the editor.
Legitimate use exists. Strings in right-to-left languages may need these controls. Allow-list those files, such as translation resources, and review them separately.
What does the researchers' defense advice say?
Compilers, interpreters and build pipelines that support Unicode should error or warn on unterminated bidirectional control characters in comments or string literals, and on identifiers with mixed-script confusable characters. Language specifications should disallow unterminated controls in comments and strings. Editors and repository front ends should make these characters perceptible with symbols or warnings.
You control only some of that. Choose editors and review tools that reveal hidden characters, and keep toolchains current, since language projects added checks after the disclosure.
How to check or apply this
- Add a CI step that fails on bidirectional control characters in source files, with a reviewed allow-list for translation files.
- Add a check for identifiers that mix scripts, such as Latin letters beside Cyrillic ones.
- Turn on hidden-character warnings in your editor and in your code review tool where available.
- Review dependency updates by diffing the published package contents, not only the source repository.
- Run the check on vendored and generated code as well.
- Add a test case with a known sample so you know the check still works.
Where does Safeguard fit?
Safeguard's code and dependency scanning looks at what is in your repositories and your lockfiles, and policy gates can block merges that fail a check. Use it alongside the character-level scan above.
Quick check
Do Trojan Source attacks need a vulnerable compiler?
No. The attack abuses legitimate Unicode behavior, so the compiler does what it should. The gap is between what the reviewer sees and what is parsed.
Is every bidirectional control character malicious?
No. Right-to-left text may use them legitimately. The concern is unexpected ones in source code, comments and strings that change visible order.
Sources
- Trojan Source, Invisible Source Code Vulnerabilities (Boucher and Anderson)
- NVD, CVE-2021-42574
- ESLint, no-irregular-whitespace
- Unicode Standard Annex 9, Bidirectional Algorithm
Last reviewed: 18 October 2026.