A script can look like a few harmless lines, or even like empty space, and still carry a hidden payload. The trick is Unicode characters that take up bytes but draw nothing on screen.
MITRE tracks this as Invisible Unicode, technique T1027.018, a sub-technique of Obfuscated Files or Information in the Stealth tactic. It applies to Linux, macOS and Windows.
How attackers use it
Unicode assigns a code point to every character in every writing system. Some valid code points are not rendered visibly: zero-width spaces, variation selectors and bidirectional formatting controls. MITRE says adversaries abuse these to hide data, change how content is interpreted, or make malicious code look like benign text or whitespace.
The hidden content may be encoded in binary, Base64 or a custom scheme, then rebuilt at run time with scripting features such as JavaScript Proxy traps, eval() or other dynamic execution. Private Use Area (PUA) characters, a range reserved for custom assignment, are another option. If no font or application defines them, they usually render blank.
The same characters also support other techniques, such as phishing and right-to-left override.
The effect is that code review by eye fails. The reviewer sees nothing wrong because there is nothing to see.
How to detect it
MITRE's strategy (DET0920) has three analytics. All look for a file that looks plain but behaves otherwise:
- Windows. Scripts or files with a low ratio of printable characters that decode and dynamically evaluate content at run time, then start processes or open network connections.
- Linux and similar. Scripts with a high concentration of invisible Unicode followed by decoding or interpretation steps such as base64 decode or eval, then process or network activity. A mismatch between file entropy and structure and what the script outputs is a hint.
- macOS. Scripts or apps with invisible Unicode payloads rebuilt at run time, with unusual AppleScript, JavaScript for Automation or shell execution and network behavior that the visible text does not explain.
For source code, the most practical control is a scan in your repository and pull request flow. Look for any non-printing characters in files that should be plain text. Legitimate uses exist in some languages and documents, so allowlist deliberately rather than ignoring the finding.
How to reduce the risk
MITRE lists no specific mitigation for this sub-technique. Sensible steps:
- Scan source and scripts for invisible and bidirectional characters before merge.
- Make your editor and code review tool display them. Many editors can be set to show hidden characters.
- Treat dynamic evaluation in scripts as a review flag, especially when paired with encoded strings.
- Check third party packages and scripts before you run them. The free in-browser dependency scan needs no login, the file never leaves your machine and it covers up to 100 dependencies.
Check your own environment
A read-only scan for common invisible code points. This Python script works on Linux, macOS and Windows. It lists file names and line numbers only.
import os, re, sys
pat = re.compile('[---︀-️-]')
for root, dirs, files in os.walk(sys.argv[1]):
for name in files:
path = os.path.join(root, name)
try:
with open(path, encoding='utf-8') as f:
for n, line in enumerate(f, 1):
if pat.search(line):
print(path, n)
except (UnicodeDecodeError, OSError):
pass
Run it as python3 scan.py path/to/repo. Expect some hits from legitimate files, such as a byte order mark at the start of a file or emoji variation selectors, and review each one.
Then ask:
- Does your pull request pipeline flag hidden or bidirectional characters?
- Do your editors show them?
- Which scripts in your build use eval or similar dynamic execution, and who reviews them?
Try it yourself
Click through it at your own pace, or open it full screen.
Quick check
Why does visual code review miss invisible Unicode?
The characters take up bytes but draw nothing, so the text looks blank or harmless while the file still holds encoded content that a script can rebuild and run.
What run time features does MITRE say rebuild the hidden payload?
Scripting features such as JavaScript Proxy traps, eval and other dynamic execution methods.
Sources
- MITRE ATT&CK, Invisible Unicode (T1027.018)