Consecutive Unicode Variation Selectors can be used to hide executable code inside source files, making it invisible to human reviewers.
Unicode Variation Selectors (U+FE00–U+FE0F and U+E0100–U+E01EF) are invisible characters that
normally select an alternate rendering for a preceding base character — for example, choosing between a text and emoji presentation of a symbol.
When multiple variation selectors appear consecutively after a single character, they can encode hidden data. Each selector’s offset from the start
of its Unicode range maps to a numeric value, and the resulting sequence of values can be decoded as UTF-8 bytes to reveal an arbitrary payload. If an
attacker is able to e.g. inject this data into eval or similar methods (e.g. by using an existing data flow, or a dependency), this leads
to arbitrary code execution.
This technique was used in the GlassWorm supply chain attack, where malicious actors embedded invisible payloads in Visual Studio Code extensions and npm packages. Since variation selectors render as nothing in virtually all code editors, terminals, version control diff views, and code review interfaces, the hidden content escapes detection during manual review and most automated scans.
If this code was not written by you or a trusted author, the consecutive variation selectors may encode a hidden payload that is executed at runtime.
Such a payload could:
Open the affected file in an editor or tool that reveals non-printable characters — such as cat -A, a hex editor, or an IDE with
hidden character visualization enabled — and remove the consecutive variation selector characters.
The string below appears to contain only the letter a, but eight consecutive variation selectors follow it on the same line, encoding
the hidden text alert(1). Running it through cat -A reveals: const source = "aM-sM- M-^EM-!M-sM- M-^EM-,M-sM-
M-^EM-%M-sM- M-^EM-2M-sM- M-^EM-4M-sM- M-^DM-(M-sM- M-^DM-1M-sM- M-^DM-)"; // Noncompliant: 8 consecutive variation selectors encode
"alert(1)"$.
If this text it passed to a sensitive method, this can lead to decoding the code and unexpected behavior:
const { process } = require('sensitive-module')
const source = "a󠅡󠅬󠅥󠅲󠅴󠄨󠄱󠄩"; // Noncompliant: 8 consecutive variation selectors encode "alert(1)"
process(source);
Even if the imported method is still sensitive, the value passed to it is simply a constant:
const { process } = require('sensitive-module')
const source = "a󠅡󠅬󠅥󠅲󠅴󠄨󠄱󠄩";
process(source);
Each variation selector in the ranges U+FE00–U+FE0F and U+E0100–U+E01EF encodes a byte value:
characters in the first range map to values 0–15 by subtracting 0xFE00, and characters in the second range map to values 0–239 by
subtracting 0xE0100. A sequence of consecutive selectors therefore encodes a sequence of bytes, which can be decoded as UTF-8 text and
executed, for example with eval().
A single variation selector after an appropriate base character can be legitimate (for instance, U+FE0F selects the emoji presentation
of many symbols). The rule flags only sequences of two or more consecutive selectors, which have no standard use and strongly indicate hidden
content.