Consecutive Unicode Variation Selectors can be used to hide executable code inside source files, making it invisible to human reviewers.

Why is this an issue?

Unicode Variation Selectors (U+FE00U+FE0F and U+E0100U+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.

What is the potential impact?

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:

How to fix it

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.

Code examples

Noncompliant code example

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);

Compliant solution

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);

How does this work?

Each variation selector in the ranges U+FE00U+FE0F and U+E0100U+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.

Resources

Articles & blog posts

Standards

Related rules