Latest commit

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Invisible-Code, Educational Demo of Attacks and Defense with Invisible Characters

WindowsLinuxmacOSCybersecurity

An educational project demonstrating how invisible Unicode characters can be used to hide malicious code within empty variables in seemingly legitimate source files, along with defense tools to detect them.

img

Table of Contents


What is this?

This project simulates several real-world cyberattack techniques, though in a benign and controlled manner: concealing a payload within invisible Unicode characters. The malicious code is concealed between the quotes of a string that appears to be empty; text editors, web browsers, and even GitHub show nothing there. Anyone opening the file sees normal code with completely empty quotes, but running it launches a hidden background process.

The goal is educational: understand how these attacks work so you can defend against them. Learn how these attacks operate in supply chain attacks (Supply Chain Attacks) and other attack types so you can protect CI/CD pipelines, developer workstations, production environments, and even guard against running a seemingly harmless file containing invisible code.


Project structure

.
├── level_1/ # Level 1: Zero-Width binary encoding
│ ├── README.md
│ ├── level1_python.py
│ └── level1_node.js
│
├── level_2/ # Level 2: Variation Selectors
│ ├── README.md
│ ├── level2_python.py
│ └── level2_node.js
│
├── level_3/ # Level 3: Full reverse shell
│ ├── README.md
│ ├── python/ # Generator and listener in Python
│ │ ├── bait_generator.py
│ │ ├── rs_listener.py
│ │ └── Bait To Client/
│ ├── javascript/ # Generator and listener in JavaScript
│ │ ├── bait_generator.js
│ │ ├── rs_listener.js
│ │ └── Bait To Client/
│ └── csharp/ # Generator and listener in C#
│ ├── BaitGenerator.cs
│ └── rs_listener.cs
│
└── defense/ # Detection scanners (14 categories)
├── README.md
├── python/unicode_scanner.py
└── javascript/unicode_scanner.js
  • Each level is self-contained with its own code and README
  • Each language is independent: use Python, JavaScript, or C# without needing the others
  • The defense/ folder contains scanners that detect invisible characters in any file

Learning levels

The project is organized into 3 progressive levels. Each level is self-contained with its own code, scripts, and README.

LevelTechniqueWhat you will learnDifficulty
Level 1Zero-Width binaryBinary encoding with invisible characters. 2 characters (U+200B, U+200D) represent bits 0 and 1. Invisible in ALL editors.Introductory
Level 2Variation Selectors256 invisible characters that map 1:1 to bytes. 8x more compact than Level 1. Invisible in IDEs and GitHub.Basic
Level 3Full reverse shellReal-world application: a benign reverse shell hidden inside files that look normal. Generators, listeners, and defense.Basic type II

Recommendation: start with Level 1 even if you already have experience. Each level builds on the previous one, and the progression makes everything clearer.

# Level 1, try the binary encoding:cd level_1
python level1_python.py # or: node level1_node.js# Level 2, try Variation Selectors:cd level_2
python level2_python.py # or: node level2_node.js# Level 3, the full attack:cd level_3 # see level_3/README.md for the step-by-step guide

The trick: invisible code between quotes

# This LOOKS like an empty string:empty=''# But between those quotes there are thousands of invisible Unicode characters# encoding a complete hidden PAYLOAD.# No editor, IDE, browser, or even GitHub shows ANYTHING between the quotes.# A 5-line file can weigh 15 KB because of the hidden code.

Open any file from the levels in your favorite text editor. Look for the string that appears empty. You will not see anything. Run it, and you will discover that a hidden message was there (levels 1 and 2) or a full reverse shell (level 3).


Where do these attacks appear?

This invisible character technique can be used across multiple attack vectors. It is not just theory: real cases have been found in GitHub projects and npm/PyPI packages. Recent incidents in npm repositories (e.g., massively popular libraries with millions of downloads) have demonstrated the impact of these vulnerabilities.

Known attack vectors

VectorHow it worksReal-world example
Supply Chain AttackAn attacker publishes a package on npm/PyPI with code hidden in invisible characters. Upon installation, the payload executes silentlyMalicious npm packages that steal environment variables or tokens
Trojan Source (CVE-2021-42574)Bidi characters are used to make code look different from what it actually does. An if appears to guard a function, but in reality it always executesUniversity of Cambridge research (2021)
Pre-install / Post-install hooksIn package.json, the preinstall or postinstall fields run a script automatically during npm install. The attacker hides the payload thereTyposquatting on npm and CI/CD pipeline attacks
Pull requests with hidden commitsAn attacker opens a PR on an open source project. In some commit, one of the .js, .py, or .cs files hides the payload in "empty" strings. If the reviewer lacks detection tools, the malicious code makes it to productionMalicious commits in projects
Shared filesA "useful" script is sent to a colleague (hex converter, utility, emulator). The file works normally, but running it also launches a hidden background processExactly what this project demonstrates

How do they do it in practice?

  1. In a package.json: Using lifecycle hooks like "preinstall": "node setup.js" or "postinstall". If setup.js has a payload obfuscated with Unicode, the system (or the CI/CD pipeline) will blindly execute it when building the project.

  2. In a GitHub commit: a .js or .py file that looks like a normal utility. Inside a string that appears empty ('' or ` `), there are thousands of invisible characters encoding a malicious payload.

  3. In a configuration file: A .env or .yaml looks normal but contains a hidden payload that executes when parsed by certain frameworks.


Invisible Unicode techniques

Multiple families of invisible Unicode characters can be exploited. Each one carries a different risk level depending on its potential to conceal code:

TechniqueUnicode RangeRiskDescription
Variation SelectorsU+FE00-FE0F, U+E0100-E01EFCRITICALSteganography: 256 values, 1 char = 1 byte
Tags BlockU+E0001-E007FCRITICALSteganography: maps 1:1 to ASCII
Zero-Width CharactersU+200B-200DHIGHBinary encoding: 8 chars = 1 byte
Bidi OverridesU+202A-202E, U+2066-2069HIGHTrojan Source (CVE-2021-42574)
Bidi MarksU+200E-200F, U+061CMEDIUMLTR/RTL direction marks
Invisible OperatorsU+2060-2064MEDIUMWord Joiner and invisible operators
Mongolian Free VSU+180B-180DMEDIUMMongolian variation selectors
Hangul FillersU+115F-1160, U+3164, U+FFA0MEDIUMEmpty, invisible Hangul characters
Line/Paragraph SeparatorsU+2028-2029MEDIUMBreak strings in JavaScript
Deprecated FormatU+206A-206FLOWDeprecated but functional formatting
Interlinear AnnotationsU+FFF9-FFFBLOWInvisible annotation markers
Musical FormattingU+1D173-1D17ALOWInvisible musical formatting
Shorthand ControlsU+1BCA0-1BCA3LOWInvisible shorthand formatting
Other InvisibleU+00AD, U+034F, U+180E, U+FEFFLOWSoft Hyphen, CGJ, MVS, BOM

The defense scanners in this project detect all 14 categories.


Requirements

You only need 1 of the following languages to use the project:

LanguageMinimum version
Python3.6+
Node.js14+
C# (.NET)6+

You do not need all three installed. Use whichever you prefer.


Defense and Detection Strategies

No single method protects against all vectors. Each strategy covers different scenarios:

1. Scan files with the Unicode detector (this project)

This project includes scanners that detect invisible characters in any source code file. This is the most direct defense against this technique, regardless of how the file arrived (PR, package, shared file, etc.):

# Python:
python defense/python/unicode_scanner.py --decode suspicious_file.py
# JavaScript:
node defense/javascript/unicode_scanner.js --decode suspicious_file.js

Protects against: all vectors (shared files, PRs, packages, hidden commits)

For more details on using the scanner, see the defense README.

2. Disable automatic script execution in npm/pnpm

This blocks the preinstall and postinstall fields in package.json, preventing code from running automatically when you install a package:

# For npm:
npm config set ignore-scripts true# For pnpm:
pnpm config set ignore-scripts true

Protects against: Supply Chain Attacks via npm/pnpm hooks only. Does not protect against files you run manually (like the baits in this project) or against hidden code in PRs or commits.

3. Check actual file sizes

A file with a hidden payload weighs much more than it appears. For example, a 1 KB visible script can weigh 15 KB on disk because of the invisible characters. If a file with only a few lines weighs more than expected, it is suspicious.

Protects against: shared files and PRs, a quick check before running or merging.


Disclaimer

LEGAL WARNING

This project is EXCLUSIVELY EDUCATIONAL and is designed to:

  • Demonstrate how invisible Unicode characters can be exploited in real attacks
  • Teach about the multiple attack vectors where this technique appears
  • Provide DEFENSE tools to detect these attacks
  • Train cybersecurity professionals in detection techniques

Using this software for unauthorized access to computer systems is ILLEGAL and is punishable under:

  • Computer Fraud and Abuse Act (CFAA)
  • Computer Misuse Act 1990, United Kingdom
  • And equivalent legislation in each jurisdiction

The author assumes NO responsibility for misuse of this software. It should only be used in controlled environments, with explicit authorization, and for the purposes of learning, research, or authorized penetration testing.

By cloning or downloading this repository, you agree to use it solely for educational and legal purposes.

License

This project is licensed under the MIT License.

Share with the community

Share with others who might benefit from this tool

Share on RedditShare on Hacker NewsShare on TwitterShare on FacebookShare on LinkedIn


⭐ If this helps you, consider giving it a ⭐

Made with ❤️ for you

About

Learn how attackers hide code in plain sight using invisible Unicode characters, and how to detect it

Resources

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Invisible-Code, Educational Demo of Attacks and Defense with Invisible Characters

WindowsLinuxmacOSCybersecurity

An educational project demonstrating how invisible Unicode characters can be used to hide malicious code within empty variables in seemingly legitimate source files, along with defense tools to detect them.

img

Table of Contents


What is this?

This project simulates several real-world cyberattack techniques, though in a benign and controlled manner: concealing a payload within invisible Unicode characters. The malicious code is concealed between the quotes of a string that appears to be empty; text editors, web browsers, and even GitHub show nothing there. Anyone opening the file sees normal code with completely empty quotes, but running it launches a hidden background process.

The goal is educational: understand how these attacks work so you can defend against them. Learn how these attacks operate in supply chain attacks (Supply Chain Attacks) and other attack types so you can protect CI/CD pipelines, developer workstations, production environments, and even guard against running a seemingly harmless file containing invisible code.


Project structure

.
├── level_1/ # Level 1: Zero-Width binary encoding
│ ├── README.md
│ ├── level1_python.py
│ └── level1_node.js
│
├── level_2/ # Level 2: Variation Selectors
│ ├── README.md
│ ├── level2_python.py
│ └── level2_node.js
│
├── level_3/ # Level 3: Full reverse shell
│ ├── README.md
│ ├── python/ # Generator and listener in Python
│ │ ├── bait_generator.py
│ │ ├── rs_listener.py
│ │ └── Bait To Client/
│ ├── javascript/ # Generator and listener in JavaScript
│ │ ├── bait_generator.js
│ │ ├── rs_listener.js
│ │ └── Bait To Client/
│ └── csharp/ # Generator and listener in C#
│ ├── BaitGenerator.cs
│ └── rs_listener.cs
│
└── defense/ # Detection scanners (14 categories)
├── README.md
├── python/unicode_scanner.py
└── javascript/unicode_scanner.js
  • Each level is self-contained with its own code and README
  • Each language is independent: use Python, JavaScript, or C# without needing the others
  • The defense/ folder contains scanners that detect invisible characters in any file

Learning levels

The project is organized into 3 progressive levels. Each level is self-contained with its own code, scripts, and README.

LevelTechniqueWhat you will learnDifficulty
Level 1Zero-Width binaryBinary encoding with invisible characters. 2 characters (U+200B, U+200D) represent bits 0 and 1. Invisible in ALL editors.Introductory
Level 2Variation Selectors256 invisible characters that map 1:1 to bytes. 8x more compact than Level 1. Invisible in IDEs and GitHub.Basic
Level 3Full reverse shellReal-world application: a benign reverse shell hidden inside files that look normal. Generators, listeners, and defense.Basic type II

Recommendation: start with Level 1 even if you already have experience. Each level builds on the previous one, and the progression makes everything clearer.

# Level 1, try the binary encoding:cd level_1
python level1_python.py # or: node level1_node.js# Level 2, try Variation Selectors:cd level_2
python level2_python.py # or: node level2_node.js# Level 3, the full attack:cd level_3 # see level_3/README.md for the step-by-step guide

The trick: invisible code between quotes

# This LOOKS like an empty string:empty=''# But between those quotes there are thousands of invisible Unicode characters# encoding a complete hidden PAYLOAD.# No editor, IDE, browser, or even GitHub shows ANYTHING between the quotes.# A 5-line file can weigh 15 KB because of the hidden code.

Open any file from the levels in your favorite text editor. Look for the string that appears empty. You will not see anything. Run it, and you will discover that a hidden message was there (levels 1 and 2) or a full reverse shell (level 3).


Where do these attacks appear?

This invisible character technique can be used across multiple attack vectors. It is not just theory: real cases have been found in GitHub projects and npm/PyPI packages. Recent incidents in npm repositories (e.g., massively popular libraries with millions of downloads) have demonstrated the impact of these vulnerabilities.

Known attack vectors

VectorHow it worksReal-world example
Supply Chain AttackAn attacker publishes a package on npm/PyPI with code hidden in invisible characters. Upon installation, the payload executes silentlyMalicious npm packages that steal environment variables or tokens
Trojan Source (CVE-2021-42574)Bidi characters are used to make code look different from what it actually does. An if appears to guard a function, but in reality it always executesUniversity of Cambridge research (2021)
Pre-install / Post-install hooksIn package.json, the preinstall or postinstall fields run a script automatically during npm install. The attacker hides the payload thereTyposquatting on npm and CI/CD pipeline attacks
Pull requests with hidden commitsAn attacker opens a PR on an open source project. In some commit, one of the .js, .py, or .cs files hides the payload in "empty" strings. If the reviewer lacks detection tools, the malicious code makes it to productionMalicious commits in projects
Shared filesA "useful" script is sent to a colleague (hex converter, utility, emulator). The file works normally, but running it also launches a hidden background processExactly what this project demonstrates

How do they do it in practice?

  1. In a package.json: Using lifecycle hooks like "preinstall": "node setup.js" or "postinstall". If setup.js has a payload obfuscated with Unicode, the system (or the CI/CD pipeline) will blindly execute it when building the project.

  2. In a GitHub commit: a .js or .py file that looks like a normal utility. Inside a string that appears empty ('' or ` `), there are thousands of invisible characters encoding a malicious payload.

  3. In a configuration file: A .env or .yaml looks normal but contains a hidden payload that executes when parsed by certain frameworks.


Invisible Unicode techniques

Multiple families of invisible Unicode characters can be exploited. Each one carries a different risk level depending on its potential to conceal code:

TechniqueUnicode RangeRiskDescription
Variation SelectorsU+FE00-FE0F, U+E0100-E01EFCRITICALSteganography: 256 values, 1 char = 1 byte
Tags BlockU+E0001-E007FCRITICALSteganography: maps 1:1 to ASCII
Zero-Width CharactersU+200B-200DHIGHBinary encoding: 8 chars = 1 byte
Bidi OverridesU+202A-202E, U+2066-2069HIGHTrojan Source (CVE-2021-42574)
Bidi MarksU+200E-200F, U+061CMEDIUMLTR/RTL direction marks
Invisible OperatorsU+2060-2064MEDIUMWord Joiner and invisible operators
Mongolian Free VSU+180B-180DMEDIUMMongolian variation selectors
Hangul FillersU+115F-1160, U+3164, U+FFA0MEDIUMEmpty, invisible Hangul characters
Line/Paragraph SeparatorsU+2028-2029MEDIUMBreak strings in JavaScript
Deprecated FormatU+206A-206FLOWDeprecated but functional formatting
Interlinear AnnotationsU+FFF9-FFFBLOWInvisible annotation markers
Musical FormattingU+1D173-1D17ALOWInvisible musical formatting
Shorthand ControlsU+1BCA0-1BCA3LOWInvisible shorthand formatting
Other InvisibleU+00AD, U+034F, U+180E, U+FEFFLOWSoft Hyphen, CGJ, MVS, BOM

The defense scanners in this project detect all 14 categories.


Requirements

You only need 1 of the following languages to use the project:

LanguageMinimum version
Python3.6+
Node.js14+
C# (.NET)6+

You do not need all three installed. Use whichever you prefer.


Defense and Detection Strategies

No single method protects against all vectors. Each strategy covers different scenarios:

1. Scan files with the Unicode detector (this project)

This project includes scanners that detect invisible characters in any source code file. This is the most direct defense against this technique, regardless of how the file arrived (PR, package, shared file, etc.):

# Python:
python defense/python/unicode_scanner.py --decode suspicious_file.py
# JavaScript:
node defense/javascript/unicode_scanner.js --decode suspicious_file.js

Protects against: all vectors (shared files, PRs, packages, hidden commits)

For more details on using the scanner, see the defense README.

2. Disable automatic script execution in npm/pnpm

This blocks the preinstall and postinstall fields in package.json, preventing code from running automatically when you install a package:

# For npm:
npm config set ignore-scripts true# For pnpm:
pnpm config set ignore-scripts true

Protects against: Supply Chain Attacks via npm/pnpm hooks only. Does not protect against files you run manually (like the baits in this project) or against hidden code in PRs or commits.

3. Check actual file sizes

A file with a hidden payload weighs much more than it appears. For example, a 1 KB visible script can weigh 15 KB on disk because of the invisible characters. If a file with only a few lines weighs more than expected, it is suspicious.

Protects against: shared files and PRs, a quick check before running or merging.


Disclaimer

LEGAL WARNING

This project is EXCLUSIVELY EDUCATIONAL and is designed to:

  • Demonstrate how invisible Unicode characters can be exploited in real attacks
  • Teach about the multiple attack vectors where this technique appears
  • Provide DEFENSE tools to detect these attacks
  • Train cybersecurity professionals in detection techniques

Using this software for unauthorized access to computer systems is ILLEGAL and is punishable under:

  • Computer Fraud and Abuse Act (CFAA)
  • Computer Misuse Act 1990, United Kingdom
  • And equivalent legislation in each jurisdiction

The author assumes NO responsibility for misuse of this software. It should only be used in controlled environments, with explicit authorization, and for the purposes of learning, research, or authorized penetration testing.

By cloning or downloading this repository, you agree to use it solely for educational and legal purposes.

License

This project is licensed under the MIT License.

Share with the community

Share with others who might benefit from this tool

Share on RedditShare on Hacker NewsShare on TwitterShare on FacebookShare on LinkedIn


⭐ If this helps you, consider giving it a ⭐

Made with ❤️ for you

About

Learn how attackers hide code in plain sight using invisible Unicode characters, and how to detect it

Resources

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Invisible-Code, Educational Demo of Attacks and Defense with Invisible Characters

WindowsLinuxmacOSCybersecurity

An educational project demonstrating how invisible Unicode characters can be used to hide malicious code within empty variables in seemingly legitimate source files, along with defense tools to detect them.

img

Table of Contents


What is this?

This project simulates several real-world cyberattack techniques, though in a benign and controlled manner: concealing a payload within invisible Unicode characters. The malicious code is concealed between the quotes of a string that appears to be empty; text editors, web browsers, and even GitHub show nothing there. Anyone opening the file sees normal code with completely empty quotes, but running it launches a hidden background process.

The goal is educational: understand how these attacks work so you can defend against them. Learn how these attacks operate in supply chain attacks (Supply Chain Attacks) and other attack types so you can protect CI/CD pipelines, developer workstations, production environments, and even guard against running a seemingly harmless file containing invisible code.


Project structure

.
├── level_1/ # Level 1: Zero-Width binary encoding
│ ├── README.md
│ ├── level1_python.py
│ └── level1_node.js
│
├── level_2/ # Level 2: Variation Selectors
│ ├── README.md
│ ├── level2_python.py
│ └── level2_node.js
│
├── level_3/ # Level 3: Full reverse shell
│ ├── README.md
│ ├── python/ # Generator and listener in Python
│ │ ├── bait_generator.py
│ │ ├── rs_listener.py
│ │ └── Bait To Client/
│ ├── javascript/ # Generator and listener in JavaScript
│ │ ├── bait_generator.js
│ │ ├── rs_listener.js
│ │ └── Bait To Client/
│ └── csharp/ # Generator and listener in C#
│ ├── BaitGenerator.cs
│ └── rs_listener.cs
│
└── defense/ # Detection scanners (14 categories)
├── README.md
├── python/unicode_scanner.py
└── javascript/unicode_scanner.js
  • Each level is self-contained with its own code and README
  • Each language is independent: use Python, JavaScript, or C# without needing the others
  • The defense/ folder contains scanners that detect invisible characters in any file

Learning levels

The project is organized into 3 progressive levels. Each level is self-contained with its own code, scripts, and README.

LevelTechniqueWhat you will learnDifficulty
Level 1Zero-Width binaryBinary encoding with invisible characters. 2 characters (U+200B, U+200D) represent bits 0 and 1. Invisible in ALL editors.Introductory
Level 2Variation Selectors256 invisible characters that map 1:1 to bytes. 8x more compact than Level 1. Invisible in IDEs and GitHub.Basic
Level 3Full reverse shellReal-world application: a benign reverse shell hidden inside files that look normal. Generators, listeners, and defense.Basic type II

Recommendation: start with Level 1 even if you already have experience. Each level builds on the previous one, and the progression makes everything clearer.

# Level 1, try the binary encoding:cd level_1
python level1_python.py # or: node level1_node.js# Level 2, try Variation Selectors:cd level_2
python level2_python.py # or: node level2_node.js# Level 3, the full attack:cd level_3 # see level_3/README.md for the step-by-step guide

The trick: invisible code between quotes

# This LOOKS like an empty string:empty=''# But between those quotes there are thousands of invisible Unicode characters# encoding a complete hidden PAYLOAD.# No editor, IDE, browser, or even GitHub shows ANYTHING between the quotes.# A 5-line file can weigh 15 KB because of the hidden code.

Open any file from the levels in your favorite text editor. Look for the string that appears empty. You will not see anything. Run it, and you will discover that a hidden message was there (levels 1 and 2) or a full reverse shell (level 3).


Where do these attacks appear?

This invisible character technique can be used across multiple attack vectors. It is not just theory: real cases have been found in GitHub projects and npm/PyPI packages. Recent incidents in npm repositories (e.g., massively popular libraries with millions of downloads) have demonstrated the impact of these vulnerabilities.

Known attack vectors

VectorHow it worksReal-world example
Supply Chain AttackAn attacker publishes a package on npm/PyPI with code hidden in invisible characters. Upon installation, the payload executes silentlyMalicious npm packages that steal environment variables or tokens
Trojan Source (CVE-2021-42574)Bidi characters are used to make code look different from what it actually does. An if appears to guard a function, but in reality it always executesUniversity of Cambridge research (2021)
Pre-install / Post-install hooksIn package.json, the preinstall or postinstall fields run a script automatically during npm install. The attacker hides the payload thereTyposquatting on npm and CI/CD pipeline attacks
Pull requests with hidden commitsAn attacker opens a PR on an open source project. In some commit, one of the .js, .py, or .cs files hides the payload in "empty" strings. If the reviewer lacks detection tools, the malicious code makes it to productionMalicious commits in projects
Shared filesA "useful" script is sent to a colleague (hex converter, utility, emulator). The file works normally, but running it also launches a hidden background processExactly what this project demonstrates

How do they do it in practice?

  1. In a package.json: Using lifecycle hooks like "preinstall": "node setup.js" or "postinstall". If setup.js has a payload obfuscated with Unicode, the system (or the CI/CD pipeline) will blindly execute it when building the project.

  2. In a GitHub commit: a .js or .py file that looks like a normal utility. Inside a string that appears empty ('' or ` `), there are thousands of invisible characters encoding a malicious payload.

  3. In a configuration file: A .env or .yaml looks normal but contains a hidden payload that executes when parsed by certain frameworks.


Invisible Unicode techniques

Multiple families of invisible Unicode characters can be exploited. Each one carries a different risk level depending on its potential to conceal code:

TechniqueUnicode RangeRiskDescription
Variation SelectorsU+FE00-FE0F, U+E0100-E01EFCRITICALSteganography: 256 values, 1 char = 1 byte
Tags BlockU+E0001-E007FCRITICALSteganography: maps 1:1 to ASCII
Zero-Width CharactersU+200B-200DHIGHBinary encoding: 8 chars = 1 byte
Bidi OverridesU+202A-202E, U+2066-2069HIGHTrojan Source (CVE-2021-42574)
Bidi MarksU+200E-200F, U+061CMEDIUMLTR/RTL direction marks
Invisible OperatorsU+2060-2064MEDIUMWord Joiner and invisible operators
Mongolian Free VSU+180B-180DMEDIUMMongolian variation selectors
Hangul FillersU+115F-1160, U+3164, U+FFA0MEDIUMEmpty, invisible Hangul characters
Line/Paragraph SeparatorsU+2028-2029MEDIUMBreak strings in JavaScript
Deprecated FormatU+206A-206FLOWDeprecated but functional formatting
Interlinear AnnotationsU+FFF9-FFFBLOWInvisible annotation markers
Musical FormattingU+1D173-1D17ALOWInvisible musical formatting
Shorthand ControlsU+1BCA0-1BCA3LOWInvisible shorthand formatting
Other InvisibleU+00AD, U+034F, U+180E, U+FEFFLOWSoft Hyphen, CGJ, MVS, BOM

The defense scanners in this project detect all 14 categories.


Requirements

You only need 1 of the following languages to use the project:

LanguageMinimum version
Python3.6+
Node.js14+
C# (.NET)6+

You do not need all three installed. Use whichever you prefer.


Defense and Detection Strategies

No single method protects against all vectors. Each strategy covers different scenarios:

1. Scan files with the Unicode detector (this project)

This project includes scanners that detect invisible characters in any source code file. This is the most direct defense against this technique, regardless of how the file arrived (PR, package, shared file, etc.):

# Python:
python defense/python/unicode_scanner.py --decode suspicious_file.py
# JavaScript:
node defense/javascript/unicode_scanner.js --decode suspicious_file.js

Protects against: all vectors (shared files, PRs, packages, hidden commits)

For more details on using the scanner, see the defense README.

2. Disable automatic script execution in npm/pnpm

This blocks the preinstall and postinstall fields in package.json, preventing code from running automatically when you install a package:

# For npm:
npm config set ignore-scripts true# For pnpm:
pnpm config set ignore-scripts true

Protects against: Supply Chain Attacks via npm/pnpm hooks only. Does not protect against files you run manually (like the baits in this project) or against hidden code in PRs or commits.

3. Check actual file sizes

A file with a hidden payload weighs much more than it appears. For example, a 1 KB visible script can weigh 15 KB on disk because of the invisible characters. If a file with only a few lines weighs more than expected, it is suspicious.

Protects against: shared files and PRs, a quick check before running or merging.


Disclaimer

LEGAL WARNING

This project is EXCLUSIVELY EDUCATIONAL and is designed to:

  • Demonstrate how invisible Unicode characters can be exploited in real attacks
  • Teach about the multiple attack vectors where this technique appears
  • Provide DEFENSE tools to detect these attacks
  • Train cybersecurity professionals in detection techniques

Using this software for unauthorized access to computer systems is ILLEGAL and is punishable under:

  • Computer Fraud and Abuse Act (CFAA)
  • Computer Misuse Act 1990, United Kingdom
  • And equivalent legislation in each jurisdiction

The author assumes NO responsibility for misuse of this software. It should only be used in controlled environments, with explicit authorization, and for the purposes of learning, research, or authorized penetration testing.

By cloning or downloading this repository, you agree to use it solely for educational and legal purposes.

License

This project is licensed under the MIT License.

Share with the community

Share with others who might benefit from this tool

Share on RedditShare on Hacker NewsShare on TwitterShare on FacebookShare on LinkedIn


⭐ If this helps you, consider giving it a ⭐

Made with ❤️ for you

About

Learn how attackers hide code in plain sight using invisible Unicode characters, and how to detect it

Resources

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Invisible-Code, Educational Demo of Attacks and Defense with Invisible Characters

WindowsLinuxmacOSCybersecurity

An educational project demonstrating how invisible Unicode characters can be used to hide malicious code within empty variables in seemingly legitimate source files, along with defense tools to detect them.

img

Table of Contents


What is this?

This project simulates several real-world cyberattack techniques, though in a benign and controlled manner: concealing a payload within invisible Unicode characters. The malicious code is concealed between the quotes of a string that appears to be empty; text editors, web browsers, and even GitHub show nothing there. Anyone opening the file sees normal code with completely empty quotes, but running it launches a hidden background process.

The goal is educational: understand how these attacks work so you can defend against them. Learn how these attacks operate in supply chain attacks (Supply Chain Attacks) and other attack types so you can protect CI/CD pipelines, developer workstations, production environments, and even guard against running a seemingly harmless file containing invisible code.


Project structure

.
├── level_1/ # Level 1: Zero-Width binary encoding
│ ├── README.md
│ ├── level1_python.py
│ └── level1_node.js
│
├── level_2/ # Level 2: Variation Selectors
│ ├── README.md
│ ├── level2_python.py
│ └── level2_node.js
│
├── level_3/ # Level 3: Full reverse shell
│ ├── README.md
│ ├── python/ # Generator and listener in Python
│ │ ├── bait_generator.py
│ │ ├── rs_listener.py
│ │ └── Bait To Client/
│ ├── javascript/ # Generator and listener in JavaScript
│ │ ├── bait_generator.js
│ │ ├── rs_listener.js
│ │ └── Bait To Client/
│ └── csharp/ # Generator and listener in C#
│ ├── BaitGenerator.cs
│ └── rs_listener.cs
│
└── defense/ # Detection scanners (14 categories)
├── README.md
├── python/unicode_scanner.py
└── javascript/unicode_scanner.js
  • Each level is self-contained with its own code and README
  • Each language is independent: use Python, JavaScript, or C# without needing the others
  • The defense/ folder contains scanners that detect invisible characters in any file

Learning levels

The project is organized into 3 progressive levels. Each level is self-contained with its own code, scripts, and README.

LevelTechniqueWhat you will learnDifficulty
Level 1Zero-Width binaryBinary encoding with invisible characters. 2 characters (U+200B, U+200D) represent bits 0 and 1. Invisible in ALL editors.Introductory
Level 2Variation Selectors256 invisible characters that map 1:1 to bytes. 8x more compact than Level 1. Invisible in IDEs and GitHub.Basic
Level 3Full reverse shellReal-world application: a benign reverse shell hidden inside files that look normal. Generators, listeners, and defense.Basic type II

Recommendation: start with Level 1 even if you already have experience. Each level builds on the previous one, and the progression makes everything clearer.

# Level 1, try the binary encoding:cd level_1
python level1_python.py # or: node level1_node.js# Level 2, try Variation Selectors:cd level_2
python level2_python.py # or: node level2_node.js# Level 3, the full attack:cd level_3 # see level_3/README.md for the step-by-step guide

The trick: invisible code between quotes

# This LOOKS like an empty string:empty=''# But between those quotes there are thousands of invisible Unicode characters# encoding a complete hidden PAYLOAD.# No editor, IDE, browser, or even GitHub shows ANYTHING between the quotes.# A 5-line file can weigh 15 KB because of the hidden code.

Open any file from the levels in your favorite text editor. Look for the string that appears empty. You will not see anything. Run it, and you will discover that a hidden message was there (levels 1 and 2) or a full reverse shell (level 3).


Where do these attacks appear?

This invisible character technique can be used across multiple attack vectors. It is not just theory: real cases have been found in GitHub projects and npm/PyPI packages. Recent incidents in npm repositories (e.g., massively popular libraries with millions of downloads) have demonstrated the impact of these vulnerabilities.

Known attack vectors

VectorHow it worksReal-world example
Supply Chain AttackAn attacker publishes a package on npm/PyPI with code hidden in invisible characters. Upon installation, the payload executes silentlyMalicious npm packages that steal environment variables or tokens
Trojan Source (CVE-2021-42574)Bidi characters are used to make code look different from what it actually does. An if appears to guard a function, but in reality it always executesUniversity of Cambridge research (2021)
Pre-install / Post-install hooksIn package.json, the preinstall or postinstall fields run a script automatically during npm install. The attacker hides the payload thereTyposquatting on npm and CI/CD pipeline attacks
Pull requests with hidden commitsAn attacker opens a PR on an open source project. In some commit, one of the .js, .py, or .cs files hides the payload in "empty" strings. If the reviewer lacks detection tools, the malicious code makes it to productionMalicious commits in projects
Shared filesA "useful" script is sent to a colleague (hex converter, utility, emulator). The file works normally, but running it also launches a hidden background processExactly what this project demonstrates

How do they do it in practice?

  1. In a package.json: Using lifecycle hooks like "preinstall": "node setup.js" or "postinstall". If setup.js has a payload obfuscated with Unicode, the system (or the CI/CD pipeline) will blindly execute it when building the project.

  2. In a GitHub commit: a .js or .py file that looks like a normal utility. Inside a string that appears empty ('' or ` `), there are thousands of invisible characters encoding a malicious payload.

  3. In a configuration file: A .env or .yaml looks normal but contains a hidden payload that executes when parsed by certain frameworks.


Invisible Unicode techniques

Multiple families of invisible Unicode characters can be exploited. Each one carries a different risk level depending on its potential to conceal code:

TechniqueUnicode RangeRiskDescription
Variation SelectorsU+FE00-FE0F, U+E0100-E01EFCRITICALSteganography: 256 values, 1 char = 1 byte
Tags BlockU+E0001-E007FCRITICALSteganography: maps 1:1 to ASCII
Zero-Width CharactersU+200B-200DHIGHBinary encoding: 8 chars = 1 byte
Bidi OverridesU+202A-202E, U+2066-2069HIGHTrojan Source (CVE-2021-42574)
Bidi MarksU+200E-200F, U+061CMEDIUMLTR/RTL direction marks
Invisible OperatorsU+2060-2064MEDIUMWord Joiner and invisible operators
Mongolian Free VSU+180B-180DMEDIUMMongolian variation selectors
Hangul FillersU+115F-1160, U+3164, U+FFA0MEDIUMEmpty, invisible Hangul characters
Line/Paragraph SeparatorsU+2028-2029MEDIUMBreak strings in JavaScript
Deprecated FormatU+206A-206FLOWDeprecated but functional formatting
Interlinear AnnotationsU+FFF9-FFFBLOWInvisible annotation markers
Musical FormattingU+1D173-1D17ALOWInvisible musical formatting
Shorthand ControlsU+1BCA0-1BCA3LOWInvisible shorthand formatting
Other InvisibleU+00AD, U+034F, U+180E, U+FEFFLOWSoft Hyphen, CGJ, MVS, BOM

The defense scanners in this project detect all 14 categories.


Requirements

You only need 1 of the following languages to use the project:

LanguageMinimum version
Python3.6+
Node.js14+
C# (.NET)6+

You do not need all three installed. Use whichever you prefer.


Defense and Detection Strategies

No single method protects against all vectors. Each strategy covers different scenarios:

1. Scan files with the Unicode detector (this project)

This project includes scanners that detect invisible characters in any source code file. This is the most direct defense against this technique, regardless of how the file arrived (PR, package, shared file, etc.):

# Python:
python defense/python/unicode_scanner.py --decode suspicious_file.py
# JavaScript:
node defense/javascript/unicode_scanner.js --decode suspicious_file.js

Protects against: all vectors (shared files, PRs, packages, hidden commits)

For more details on using the scanner, see the defense README.

2. Disable automatic script execution in npm/pnpm

This blocks the preinstall and postinstall fields in package.json, preventing code from running automatically when you install a package:

# For npm:
npm config set ignore-scripts true# For pnpm:
pnpm config set ignore-scripts true

Protects against: Supply Chain Attacks via npm/pnpm hooks only. Does not protect against files you run manually (like the baits in this project) or against hidden code in PRs or commits.

3. Check actual file sizes

A file with a hidden payload weighs much more than it appears. For example, a 1 KB visible script can weigh 15 KB on disk because of the invisible characters. If a file with only a few lines weighs more than expected, it is suspicious.

Protects against: shared files and PRs, a quick check before running or merging.


Disclaimer

LEGAL WARNING

This project is EXCLUSIVELY EDUCATIONAL and is designed to:

  • Demonstrate how invisible Unicode characters can be exploited in real attacks
  • Teach about the multiple attack vectors where this technique appears
  • Provide DEFENSE tools to detect these attacks
  • Train cybersecurity professionals in detection techniques

Using this software for unauthorized access to computer systems is ILLEGAL and is punishable under:

  • Computer Fraud and Abuse Act (CFAA)
  • Computer Misuse Act 1990, United Kingdom
  • And equivalent legislation in each jurisdiction

The author assumes NO responsibility for misuse of this software. It should only be used in controlled environments, with explicit authorization, and for the purposes of learning, research, or authorized penetration testing.

By cloning or downloading this repository, you agree to use it solely for educational and legal purposes.

License

This project is licensed under the MIT License.

Share with the community

Share with others who might benefit from this tool

Share on RedditShare on Hacker NewsShare on TwitterShare on FacebookShare on LinkedIn


⭐ If this helps you, consider giving it a ⭐

Made with ❤️ for you

About

Learn how attackers hide code in plain sight using invisible Unicode characters, and how to detect it

Resources

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Invisible-Code, Educational Demo of Attacks and Defense with Invisible Characters

WindowsLinuxmacOSCybersecurity

An educational project demonstrating how invisible Unicode characters can be used to hide malicious code within empty variables in seemingly legitimate source files, along with defense tools to detect them.

img

Table of Contents


What is this?

This project simulates several real-world cyberattack techniques, though in a benign and controlled manner: concealing a payload within invisible Unicode characters. The malicious code is concealed between the quotes of a string that appears to be empty; text editors, web browsers, and even GitHub show nothing there. Anyone opening the file sees normal code with completely empty quotes, but running it launches a hidden background process.

The goal is educational: understand how these attacks work so you can defend against them. Learn how these attacks operate in supply chain attacks (Supply Chain Attacks) and other attack types so you can protect CI/CD pipelines, developer workstations, production environments, and even guard against running a seemingly harmless file containing invisible code.


Project structure

.
├── level_1/ # Level 1: Zero-Width binary encoding
│ ├── README.md
│ ├── level1_python.py
│ └── level1_node.js
│
├── level_2/ # Level 2: Variation Selectors
│ ├── README.md
│ ├── level2_python.py
│ └── level2_node.js
│
├── level_3/ # Level 3: Full reverse shell
│ ├── README.md
│ ├── python/ # Generator and listener in Python
│ │ ├── bait_generator.py
│ │ ├── rs_listener.py
│ │ └── Bait To Client/
│ ├── javascript/ # Generator and listener in JavaScript
│ │ ├── bait_generator.js
│ │ ├── rs_listener.js
│ │ └── Bait To Client/
│ └── csharp/ # Generator and listener in C#
│ ├── BaitGenerator.cs
│ └── rs_listener.cs
│
└── defense/ # Detection scanners (14 categories)
├── README.md
├── python/unicode_scanner.py
└── javascript/unicode_scanner.js
  • Each level is self-contained with its own code and README
  • Each language is independent: use Python, JavaScript, or C# without needing the others
  • The defense/ folder contains scanners that detect invisible characters in any file

Learning levels

The project is organized into 3 progressive levels. Each level is self-contained with its own code, scripts, and README.

LevelTechniqueWhat you will learnDifficulty
Level 1Zero-Width binaryBinary encoding with invisible characters. 2 characters (U+200B, U+200D) represent bits 0 and 1. Invisible in ALL editors.Introductory
Level 2Variation Selectors256 invisible characters that map 1:1 to bytes. 8x more compact than Level 1. Invisible in IDEs and GitHub.Basic
Level 3Full reverse shellReal-world application: a benign reverse shell hidden inside files that look normal. Generators, listeners, and defense.Basic type II

Recommendation: start with Level 1 even if you already have experience. Each level builds on the previous one, and the progression makes everything clearer.

# Level 1, try the binary encoding:cd level_1
python level1_python.py # or: node level1_node.js# Level 2, try Variation Selectors:cd level_2
python level2_python.py # or: node level2_node.js# Level 3, the full attack:cd level_3 # see level_3/README.md for the step-by-step guide

The trick: invisible code between quotes

# This LOOKS like an empty string:empty=''# But between those quotes there are thousands of invisible Unicode characters# encoding a complete hidden PAYLOAD.# No editor, IDE, browser, or even GitHub shows ANYTHING between the quotes.# A 5-line file can weigh 15 KB because of the hidden code.

Open any file from the levels in your favorite text editor. Look for the string that appears empty. You will not see anything. Run it, and you will discover that a hidden message was there (levels 1 and 2) or a full reverse shell (level 3).


Where do these attacks appear?

This invisible character technique can be used across multiple attack vectors. It is not just theory: real cases have been found in GitHub projects and npm/PyPI packages. Recent incidents in npm repositories (e.g., massively popular libraries with millions of downloads) have demonstrated the impact of these vulnerabilities.

Known attack vectors

VectorHow it worksReal-world example
Supply Chain AttackAn attacker publishes a package on npm/PyPI with code hidden in invisible characters. Upon installation, the payload executes silentlyMalicious npm packages that steal environment variables or tokens
Trojan Source (CVE-2021-42574)Bidi characters are used to make code look different from what it actually does. An if appears to guard a function, but in reality it always executesUniversity of Cambridge research (2021)
Pre-install / Post-install hooksIn package.json, the preinstall or postinstall fields run a script automatically during npm install. The attacker hides the payload thereTyposquatting on npm and CI/CD pipeline attacks
Pull requests with hidden commitsAn attacker opens a PR on an open source project. In some commit, one of the .js, .py, or .cs files hides the payload in "empty" strings. If the reviewer lacks detection tools, the malicious code makes it to productionMalicious commits in projects
Shared filesA "useful" script is sent to a colleague (hex converter, utility, emulator). The file works normally, but running it also launches a hidden background processExactly what this project demonstrates

How do they do it in practice?

  1. In a package.json: Using lifecycle hooks like "preinstall": "node setup.js" or "postinstall". If setup.js has a payload obfuscated with Unicode, the system (or the CI/CD pipeline) will blindly execute it when building the project.

  2. In a GitHub commit: a .js or .py file that looks like a normal utility. Inside a string that appears empty ('' or ` `), there are thousands of invisible characters encoding a malicious payload.

  3. In a configuration file: A .env or .yaml looks normal but contains a hidden payload that executes when parsed by certain frameworks.


Invisible Unicode techniques

Multiple families of invisible Unicode characters can be exploited. Each one carries a different risk level depending on its potential to conceal code:

TechniqueUnicode RangeRiskDescription
Variation SelectorsU+FE00-FE0F, U+E0100-E01EFCRITICALSteganography: 256 values, 1 char = 1 byte
Tags BlockU+E0001-E007FCRITICALSteganography: maps 1:1 to ASCII
Zero-Width CharactersU+200B-200DHIGHBinary encoding: 8 chars = 1 byte
Bidi OverridesU+202A-202E, U+2066-2069HIGHTrojan Source (CVE-2021-42574)
Bidi MarksU+200E-200F, U+061CMEDIUMLTR/RTL direction marks
Invisible OperatorsU+2060-2064MEDIUMWord Joiner and invisible operators
Mongolian Free VSU+180B-180DMEDIUMMongolian variation selectors
Hangul FillersU+115F-1160, U+3164, U+FFA0MEDIUMEmpty, invisible Hangul characters
Line/Paragraph SeparatorsU+2028-2029MEDIUMBreak strings in JavaScript
Deprecated FormatU+206A-206FLOWDeprecated but functional formatting
Interlinear AnnotationsU+FFF9-FFFBLOWInvisible annotation markers
Musical FormattingU+1D173-1D17ALOWInvisible musical formatting
Shorthand ControlsU+1BCA0-1BCA3LOWInvisible shorthand formatting
Other InvisibleU+00AD, U+034F, U+180E, U+FEFFLOWSoft Hyphen, CGJ, MVS, BOM

The defense scanners in this project detect all 14 categories.


Requirements

You only need 1 of the following languages to use the project:

LanguageMinimum version
Python3.6+
Node.js14+
C# (.NET)6+

You do not need all three installed. Use whichever you prefer.


Defense and Detection Strategies

No single method protects against all vectors. Each strategy covers different scenarios:

1. Scan files with the Unicode detector (this project)

This project includes scanners that detect invisible characters in any source code file. This is the most direct defense against this technique, regardless of how the file arrived (PR, package, shared file, etc.):

# Python:
python defense/python/unicode_scanner.py --decode suspicious_file.py
# JavaScript:
node defense/javascript/unicode_scanner.js --decode suspicious_file.js

Protects against: all vectors (shared files, PRs, packages, hidden commits)

For more details on using the scanner, see the defense README.

2. Disable automatic script execution in npm/pnpm

This blocks the preinstall and postinstall fields in package.json, preventing code from running automatically when you install a package:

# For npm:
npm config set ignore-scripts true# For pnpm:
pnpm config set ignore-scripts true

Protects against: Supply Chain Attacks via npm/pnpm hooks only. Does not protect against files you run manually (like the baits in this project) or against hidden code in PRs or commits.

3. Check actual file sizes

A file with a hidden payload weighs much more than it appears. For example, a 1 KB visible script can weigh 15 KB on disk because of the invisible characters. If a file with only a few lines weighs more than expected, it is suspicious.

Protects against: shared files and PRs, a quick check before running or merging.


Disclaimer

LEGAL WARNING

This project is EXCLUSIVELY EDUCATIONAL and is designed to:

  • Demonstrate how invisible Unicode characters can be exploited in real attacks
  • Teach about the multiple attack vectors where this technique appears
  • Provide DEFENSE tools to detect these attacks
  • Train cybersecurity professionals in detection techniques

Using this software for unauthorized access to computer systems is ILLEGAL and is punishable under:

  • Computer Fraud and Abuse Act (CFAA)
  • Computer Misuse Act 1990, United Kingdom
  • And equivalent legislation in each jurisdiction

The author assumes NO responsibility for misuse of this software. It should only be used in controlled environments, with explicit authorization, and for the purposes of learning, research, or authorized penetration testing.

By cloning or downloading this repository, you agree to use it solely for educational and legal purposes.

License

This project is licensed under the MIT License.

Share with the community

Share with others who might benefit from this tool

Share on RedditShare on Hacker NewsShare on TwitterShare on FacebookShare on LinkedIn


⭐ If this helps you, consider giving it a ⭐

Made with ❤️ for you

About

Learn how attackers hide code in plain sight using invisible Unicode characters, and how to detect it

Resources

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Invisible-Code, Educational Demo of Attacks and Defense with Invisible Characters

WindowsLinuxmacOSCybersecurity

An educational project demonstrating how invisible Unicode characters can be used to hide malicious code within empty variables in seemingly legitimate source files, along with defense tools to detect them.

img

Table of Contents


What is this?

This project simulates several real-world cyberattack techniques, though in a benign and controlled manner: concealing a payload within invisible Unicode characters. The malicious code is concealed between the quotes of a string that appears to be empty; text editors, web browsers, and even GitHub show nothing there. Anyone opening the file sees normal code with completely empty quotes, but running it launches a hidden background process.

The goal is educational: understand how these attacks work so you can defend against them. Learn how these attacks operate in supply chain attacks (Supply Chain Attacks) and other attack types so you can protect CI/CD pipelines, developer workstations, production environments, and even guard against running a seemingly harmless file containing invisible code.


Project structure

.
├── level_1/ # Level 1: Zero-Width binary encoding
│ ├── README.md
│ ├── level1_python.py
│ └── level1_node.js
│
├── level_2/ # Level 2: Variation Selectors
│ ├── README.md
│ ├── level2_python.py
│ └── level2_node.js
│
├── level_3/ # Level 3: Full reverse shell
│ ├── README.md
│ ├── python/ # Generator and listener in Python
│ │ ├── bait_generator.py
│ │ ├── rs_listener.py
│ │ └── Bait To Client/
│ ├── javascript/ # Generator and listener in JavaScript
│ │ ├── bait_generator.js
│ │ ├── rs_listener.js
│ │ └── Bait To Client/
│ └── csharp/ # Generator and listener in C#
│ ├── BaitGenerator.cs
│ └── rs_listener.cs
│
└── defense/ # Detection scanners (14 categories)
├── README.md
├── python/unicode_scanner.py
└── javascript/unicode_scanner.js
  • Each level is self-contained with its own code and README
  • Each language is independent: use Python, JavaScript, or C# without needing the others
  • The defense/ folder contains scanners that detect invisible characters in any file

Learning levels

The project is organized into 3 progressive levels. Each level is self-contained with its own code, scripts, and README.

LevelTechniqueWhat you will learnDifficulty
Level 1Zero-Width binaryBinary encoding with invisible characters. 2 characters (U+200B, U+200D) represent bits 0 and 1. Invisible in ALL editors.Introductory
Level 2Variation Selectors256 invisible characters that map 1:1 to bytes. 8x more compact than Level 1. Invisible in IDEs and GitHub.Basic
Level 3Full reverse shellReal-world application: a benign reverse shell hidden inside files that look normal. Generators, listeners, and defense.Basic type II

Recommendation: start with Level 1 even if you already have experience. Each level builds on the previous one, and the progression makes everything clearer.

# Level 1, try the binary encoding:cd level_1
python level1_python.py # or: node level1_node.js# Level 2, try Variation Selectors:cd level_2
python level2_python.py # or: node level2_node.js# Level 3, the full attack:cd level_3 # see level_3/README.md for the step-by-step guide

The trick: invisible code between quotes

# This LOOKS like an empty string:empty=''# But between those quotes there are thousands of invisible Unicode characters# encoding a complete hidden PAYLOAD.# No editor, IDE, browser, or even GitHub shows ANYTHING between the quotes.# A 5-line file can weigh 15 KB because of the hidden code.

Open any file from the levels in your favorite text editor. Look for the string that appears empty. You will not see anything. Run it, and you will discover that a hidden message was there (levels 1 and 2) or a full reverse shell (level 3).


Where do these attacks appear?

This invisible character technique can be used across multiple attack vectors. It is not just theory: real cases have been found in GitHub projects and npm/PyPI packages. Recent incidents in npm repositories (e.g., massively popular libraries with millions of downloads) have demonstrated the impact of these vulnerabilities.

Known attack vectors

VectorHow it worksReal-world example
Supply Chain AttackAn attacker publishes a package on npm/PyPI with code hidden in invisible characters. Upon installation, the payload executes silentlyMalicious npm packages that steal environment variables or tokens
Trojan Source (CVE-2021-42574)Bidi characters are used to make code look different from what it actually does. An if appears to guard a function, but in reality it always executesUniversity of Cambridge research (2021)
Pre-install / Post-install hooksIn package.json, the preinstall or postinstall fields run a script automatically during npm install. The attacker hides the payload thereTyposquatting on npm and CI/CD pipeline attacks
Pull requests with hidden commitsAn attacker opens a PR on an open source project. In some commit, one of the .js, .py, or .cs files hides the payload in "empty" strings. If the reviewer lacks detection tools, the malicious code makes it to productionMalicious commits in projects
Shared filesA "useful" script is sent to a colleague (hex converter, utility, emulator). The file works normally, but running it also launches a hidden background processExactly what this project demonstrates

How do they do it in practice?

  1. In a package.json: Using lifecycle hooks like "preinstall": "node setup.js" or "postinstall". If setup.js has a payload obfuscated with Unicode, the system (or the CI/CD pipeline) will blindly execute it when building the project.

  2. In a GitHub commit: a .js or .py file that looks like a normal utility. Inside a string that appears empty ('' or ` `), there are thousands of invisible characters encoding a malicious payload.

  3. In a configuration file: A .env or .yaml looks normal but contains a hidden payload that executes when parsed by certain frameworks.


Invisible Unicode techniques

Multiple families of invisible Unicode characters can be exploited. Each one carries a different risk level depending on its potential to conceal code:

TechniqueUnicode RangeRiskDescription
Variation SelectorsU+FE00-FE0F, U+E0100-E01EFCRITICALSteganography: 256 values, 1 char = 1 byte
Tags BlockU+E0001-E007FCRITICALSteganography: maps 1:1 to ASCII
Zero-Width CharactersU+200B-200DHIGHBinary encoding: 8 chars = 1 byte
Bidi OverridesU+202A-202E, U+2066-2069HIGHTrojan Source (CVE-2021-42574)
Bidi MarksU+200E-200F, U+061CMEDIUMLTR/RTL direction marks
Invisible OperatorsU+2060-2064MEDIUMWord Joiner and invisible operators
Mongolian Free VSU+180B-180DMEDIUMMongolian variation selectors
Hangul FillersU+115F-1160, U+3164, U+FFA0MEDIUMEmpty, invisible Hangul characters
Line/Paragraph SeparatorsU+2028-2029MEDIUMBreak strings in JavaScript
Deprecated FormatU+206A-206FLOWDeprecated but functional formatting
Interlinear AnnotationsU+FFF9-FFFBLOWInvisible annotation markers
Musical FormattingU+1D173-1D17ALOWInvisible musical formatting
Shorthand ControlsU+1BCA0-1BCA3LOWInvisible shorthand formatting
Other InvisibleU+00AD, U+034F, U+180E, U+FEFFLOWSoft Hyphen, CGJ, MVS, BOM

The defense scanners in this project detect all 14 categories.


Requirements

You only need 1 of the following languages to use the project:

LanguageMinimum version
Python3.6+
Node.js14+
C# (.NET)6+

You do not need all three installed. Use whichever you prefer.


Defense and Detection Strategies

No single method protects against all vectors. Each strategy covers different scenarios:

1. Scan files with the Unicode detector (this project)

This project includes scanners that detect invisible characters in any source code file. This is the most direct defense against this technique, regardless of how the file arrived (PR, package, shared file, etc.):

# Python:
python defense/python/unicode_scanner.py --decode suspicious_file.py
# JavaScript:
node defense/javascript/unicode_scanner.js --decode suspicious_file.js

Protects against: all vectors (shared files, PRs, packages, hidden commits)

For more details on using the scanner, see the defense README.

2. Disable automatic script execution in npm/pnpm

This blocks the preinstall and postinstall fields in package.json, preventing code from running automatically when you install a package:

# For npm:
npm config set ignore-scripts true# For pnpm:
pnpm config set ignore-scripts true

Protects against: Supply Chain Attacks via npm/pnpm hooks only. Does not protect against files you run manually (like the baits in this project) or against hidden code in PRs or commits.

3. Check actual file sizes

A file with a hidden payload weighs much more than it appears. For example, a 1 KB visible script can weigh 15 KB on disk because of the invisible characters. If a file with only a few lines weighs more than expected, it is suspicious.

Protects against: shared files and PRs, a quick check before running or merging.


Disclaimer

LEGAL WARNING

This project is EXCLUSIVELY EDUCATIONAL and is designed to:

  • Demonstrate how invisible Unicode characters can be exploited in real attacks
  • Teach about the multiple attack vectors where this technique appears
  • Provide DEFENSE tools to detect these attacks
  • Train cybersecurity professionals in detection techniques

Using this software for unauthorized access to computer systems is ILLEGAL and is punishable under:

  • Computer Fraud and Abuse Act (CFAA)
  • Computer Misuse Act 1990, United Kingdom
  • And equivalent legislation in each jurisdiction

The author assumes NO responsibility for misuse of this software. It should only be used in controlled environments, with explicit authorization, and for the purposes of learning, research, or authorized penetration testing.

By cloning or downloading this repository, you agree to use it solely for educational and legal purposes.

License

This project is licensed under the MIT License.

Share with the community

Share with others who might benefit from this tool

Share on RedditShare on Hacker NewsShare on TwitterShare on FacebookShare on LinkedIn


⭐ If this helps you, consider giving it a ⭐

Made with ❤️ for you

About

Learn how attackers hide code in plain sight using invisible Unicode characters, and how to detect it

Resources

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Invisible-Code, Educational Demo of Attacks and Defense with Invisible Characters

WindowsLinuxmacOSCybersecurity

An educational project demonstrating how invisible Unicode characters can be used to hide malicious code within empty variables in seemingly legitimate source files, along with defense tools to detect them.

img

Table of Contents


What is this?

This project simulates several real-world cyberattack techniques, though in a benign and controlled manner: concealing a payload within invisible Unicode characters. The malicious code is concealed between the quotes of a string that appears to be empty; text editors, web browsers, and even GitHub show nothing there. Anyone opening the file sees normal code with completely empty quotes, but running it launches a hidden background process.

The goal is educational: understand how these attacks work so you can defend against them. Learn how these attacks operate in supply chain attacks (Supply Chain Attacks) and other attack types so you can protect CI/CD pipelines, developer workstations, production environments, and even guard against running a seemingly harmless file containing invisible code.


Project structure

.
├── level_1/ # Level 1: Zero-Width binary encoding
│ ├── README.md
│ ├── level1_python.py
│ └── level1_node.js
│
├── level_2/ # Level 2: Variation Selectors
│ ├── README.md
│ ├── level2_python.py
│ └── level2_node.js
│
├── level_3/ # Level 3: Full reverse shell
│ ├── README.md
│ ├── python/ # Generator and listener in Python
│ │ ├── bait_generator.py
│ │ ├── rs_listener.py
│ │ └── Bait To Client/
│ ├── javascript/ # Generator and listener in JavaScript
│ │ ├── bait_generator.js
│ │ ├── rs_listener.js
│ │ └── Bait To Client/
│ └── csharp/ # Generator and listener in C#
│ ├── BaitGenerator.cs
│ └── rs_listener.cs
│
└── defense/ # Detection scanners (14 categories)
├── README.md
├── python/unicode_scanner.py
└── javascript/unicode_scanner.js
  • Each level is self-contained with its own code and README
  • Each language is independent: use Python, JavaScript, or C# without needing the others
  • The defense/ folder contains scanners that detect invisible characters in any file

Learning levels

The project is organized into 3 progressive levels. Each level is self-contained with its own code, scripts, and README.

LevelTechniqueWhat you will learnDifficulty
Level 1Zero-Width binaryBinary encoding with invisible characters. 2 characters (U+200B, U+200D) represent bits 0 and 1. Invisible in ALL editors.Introductory
Level 2Variation Selectors256 invisible characters that map 1:1 to bytes. 8x more compact than Level 1. Invisible in IDEs and GitHub.Basic
Level 3Full reverse shellReal-world application: a benign reverse shell hidden inside files that look normal. Generators, listeners, and defense.Basic type II

Recommendation: start with Level 1 even if you already have experience. Each level builds on the previous one, and the progression makes everything clearer.

# Level 1, try the binary encoding:cd level_1
python level1_python.py # or: node level1_node.js# Level 2, try Variation Selectors:cd level_2
python level2_python.py # or: node level2_node.js# Level 3, the full attack:cd level_3 # see level_3/README.md for the step-by-step guide

The trick: invisible code between quotes

# This LOOKS like an empty string:empty=''# But between those quotes there are thousands of invisible Unicode characters# encoding a complete hidden PAYLOAD.# No editor, IDE, browser, or even GitHub shows ANYTHING between the quotes.# A 5-line file can weigh 15 KB because of the hidden code.

Open any file from the levels in your favorite text editor. Look for the string that appears empty. You will not see anything. Run it, and you will discover that a hidden message was there (levels 1 and 2) or a full reverse shell (level 3).


Where do these attacks appear?

This invisible character technique can be used across multiple attack vectors. It is not just theory: real cases have been found in GitHub projects and npm/PyPI packages. Recent incidents in npm repositories (e.g., massively popular libraries with millions of downloads) have demonstrated the impact of these vulnerabilities.

Known attack vectors

VectorHow it worksReal-world example
Supply Chain AttackAn attacker publishes a package on npm/PyPI with code hidden in invisible characters. Upon installation, the payload executes silentlyMalicious npm packages that steal environment variables or tokens
Trojan Source (CVE-2021-42574)Bidi characters are used to make code look different from what it actually does. An if appears to guard a function, but in reality it always executesUniversity of Cambridge research (2021)
Pre-install / Post-install hooksIn package.json, the preinstall or postinstall fields run a script automatically during npm install. The attacker hides the payload thereTyposquatting on npm and CI/CD pipeline attacks
Pull requests with hidden commitsAn attacker opens a PR on an open source project. In some commit, one of the .js, .py, or .cs files hides the payload in "empty" strings. If the reviewer lacks detection tools, the malicious code makes it to productionMalicious commits in projects
Shared filesA "useful" script is sent to a colleague (hex converter, utility, emulator). The file works normally, but running it also launches a hidden background processExactly what this project demonstrates

How do they do it in practice?

  1. In a package.json: Using lifecycle hooks like "preinstall": "node setup.js" or "postinstall". If setup.js has a payload obfuscated with Unicode, the system (or the CI/CD pipeline) will blindly execute it when building the project.

  2. In a GitHub commit: a .js or .py file that looks like a normal utility. Inside a string that appears empty ('' or ` `), there are thousands of invisible characters encoding a malicious payload.

  3. In a configuration file: A .env or .yaml looks normal but contains a hidden payload that executes when parsed by certain frameworks.


Invisible Unicode techniques

Multiple families of invisible Unicode characters can be exploited. Each one carries a different risk level depending on its potential to conceal code:

TechniqueUnicode RangeRiskDescription
Variation SelectorsU+FE00-FE0F, U+E0100-E01EFCRITICALSteganography: 256 values, 1 char = 1 byte
Tags BlockU+E0001-E007FCRITICALSteganography: maps 1:1 to ASCII
Zero-Width CharactersU+200B-200DHIGHBinary encoding: 8 chars = 1 byte
Bidi OverridesU+202A-202E, U+2066-2069HIGHTrojan Source (CVE-2021-42574)
Bidi MarksU+200E-200F, U+061CMEDIUMLTR/RTL direction marks
Invisible OperatorsU+2060-2064MEDIUMWord Joiner and invisible operators
Mongolian Free VSU+180B-180DMEDIUMMongolian variation selectors
Hangul FillersU+115F-1160, U+3164, U+FFA0MEDIUMEmpty, invisible Hangul characters
Line/Paragraph SeparatorsU+2028-2029MEDIUMBreak strings in JavaScript
Deprecated FormatU+206A-206FLOWDeprecated but functional formatting
Interlinear AnnotationsU+FFF9-FFFBLOWInvisible annotation markers
Musical FormattingU+1D173-1D17ALOWInvisible musical formatting
Shorthand ControlsU+1BCA0-1BCA3LOWInvisible shorthand formatting
Other InvisibleU+00AD, U+034F, U+180E, U+FEFFLOWSoft Hyphen, CGJ, MVS, BOM

The defense scanners in this project detect all 14 categories.


Requirements

You only need 1 of the following languages to use the project:

LanguageMinimum version
Python3.6+
Node.js14+
C# (.NET)6+

You do not need all three installed. Use whichever you prefer.


Defense and Detection Strategies

No single method protects against all vectors. Each strategy covers different scenarios:

1. Scan files with the Unicode detector (this project)

This project includes scanners that detect invisible characters in any source code file. This is the most direct defense against this technique, regardless of how the file arrived (PR, package, shared file, etc.):

# Python:
python defense/python/unicode_scanner.py --decode suspicious_file.py
# JavaScript:
node defense/javascript/unicode_scanner.js --decode suspicious_file.js

Protects against: all vectors (shared files, PRs, packages, hidden commits)

For more details on using the scanner, see the defense README.

2. Disable automatic script execution in npm/pnpm

This blocks the preinstall and postinstall fields in package.json, preventing code from running automatically when you install a package:

# For npm:
npm config set ignore-scripts true# For pnpm:
pnpm config set ignore-scripts true

Protects against: Supply Chain Attacks via npm/pnpm hooks only. Does not protect against files you run manually (like the baits in this project) or against hidden code in PRs or commits.

3. Check actual file sizes

A file with a hidden payload weighs much more than it appears. For example, a 1 KB visible script can weigh 15 KB on disk because of the invisible characters. If a file with only a few lines weighs more than expected, it is suspicious.

Protects against: shared files and PRs, a quick check before running or merging.


Disclaimer

LEGAL WARNING

This project is EXCLUSIVELY EDUCATIONAL and is designed to:

  • Demonstrate how invisible Unicode characters can be exploited in real attacks
  • Teach about the multiple attack vectors where this technique appears
  • Provide DEFENSE tools to detect these attacks
  • Train cybersecurity professionals in detection techniques

Using this software for unauthorized access to computer systems is ILLEGAL and is punishable under:

  • Computer Fraud and Abuse Act (CFAA)
  • Computer Misuse Act 1990, United Kingdom
  • And equivalent legislation in each jurisdiction

The author assumes NO responsibility for misuse of this software. It should only be used in controlled environments, with explicit authorization, and for the purposes of learning, research, or authorized penetration testing.

By cloning or downloading this repository, you agree to use it solely for educational and legal purposes.

License

This project is licensed under the MIT License.

Share with the community

Share with others who might benefit from this tool

Share on RedditShare on Hacker NewsShare on TwitterShare on FacebookShare on LinkedIn


⭐ If this helps you, consider giving it a ⭐

Made with ❤️ for you

About

Learn how attackers hide code in plain sight using invisible Unicode characters, and how to detect it

Resources

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Invisible-Code, Educational Demo of Attacks and Defense with Invisible Characters

WindowsLinuxmacOSCybersecurity

An educational project demonstrating how invisible Unicode characters can be used to hide malicious code within empty variables in seemingly legitimate source files, along with defense tools to detect them.

img

Table of Contents


What is this?

This project simulates several real-world cyberattack techniques, though in a benign and controlled manner: concealing a payload within invisible Unicode characters. The malicious code is concealed between the quotes of a string that appears to be empty; text editors, web browsers, and even GitHub show nothing there. Anyone opening the file sees normal code with completely empty quotes, but running it launches a hidden background process.

The goal is educational: understand how these attacks work so you can defend against them. Learn how these attacks operate in supply chain attacks (Supply Chain Attacks) and other attack types so you can protect CI/CD pipelines, developer workstations, production environments, and even guard against running a seemingly harmless file containing invisible code.


Project structure

.
├── level_1/ # Level 1: Zero-Width binary encoding
│ ├── README.md
│ ├── level1_python.py
│ └── level1_node.js
│
├── level_2/ # Level 2: Variation Selectors
│ ├── README.md
│ ├── level2_python.py
│ └── level2_node.js
│
├── level_3/ # Level 3: Full reverse shell
│ ├── README.md
│ ├── python/ # Generator and listener in Python
│ │ ├── bait_generator.py
│ │ ├── rs_listener.py
│ │ └── Bait To Client/
│ ├── javascript/ # Generator and listener in JavaScript
│ │ ├── bait_generator.js
│ │ ├── rs_listener.js
│ │ └── Bait To Client/
│ └── csharp/ # Generator and listener in C#
│ ├── BaitGenerator.cs
│ └── rs_listener.cs
│
└── defense/ # Detection scanners (14 categories)
├── README.md
├── python/unicode_scanner.py
└── javascript/unicode_scanner.js
  • Each level is self-contained with its own code and README
  • Each language is independent: use Python, JavaScript, or C# without needing the others
  • The defense/ folder contains scanners that detect invisible characters in any file

Learning levels

The project is organized into 3 progressive levels. Each level is self-contained with its own code, scripts, and README.

LevelTechniqueWhat you will learnDifficulty
Level 1Zero-Width binaryBinary encoding with invisible characters. 2 characters (U+200B, U+200D) represent bits 0 and 1. Invisible in ALL editors.Introductory
Level 2Variation Selectors256 invisible characters that map 1:1 to bytes. 8x more compact than Level 1. Invisible in IDEs and GitHub.Basic
Level 3Full reverse shellReal-world application: a benign reverse shell hidden inside files that look normal. Generators, listeners, and defense.Basic type II

Recommendation: start with Level 1 even if you already have experience. Each level builds on the previous one, and the progression makes everything clearer.

# Level 1, try the binary encoding:cd level_1
python level1_python.py # or: node level1_node.js# Level 2, try Variation Selectors:cd level_2
python level2_python.py # or: node level2_node.js# Level 3, the full attack:cd level_3 # see level_3/README.md for the step-by-step guide

The trick: invisible code between quotes

# This LOOKS like an empty string:empty=''# But between those quotes there are thousands of invisible Unicode characters# encoding a complete hidden PAYLOAD.# No editor, IDE, browser, or even GitHub shows ANYTHING between the quotes.# A 5-line file can weigh 15 KB because of the hidden code.

Open any file from the levels in your favorite text editor. Look for the string that appears empty. You will not see anything. Run it, and you will discover that a hidden message was there (levels 1 and 2) or a full reverse shell (level 3).


Where do these attacks appear?

This invisible character technique can be used across multiple attack vectors. It is not just theory: real cases have been found in GitHub projects and npm/PyPI packages. Recent incidents in npm repositories (e.g., massively popular libraries with millions of downloads) have demonstrated the impact of these vulnerabilities.

Known attack vectors

VectorHow it worksReal-world example
Supply Chain AttackAn attacker publishes a package on npm/PyPI with code hidden in invisible characters. Upon installation, the payload executes silentlyMalicious npm packages that steal environment variables or tokens
Trojan Source (CVE-2021-42574)Bidi characters are used to make code look different from what it actually does. An if appears to guard a function, but in reality it always executesUniversity of Cambridge research (2021)
Pre-install / Post-install hooksIn package.json, the preinstall or postinstall fields run a script automatically during npm install. The attacker hides the payload thereTyposquatting on npm and CI/CD pipeline attacks
Pull requests with hidden commitsAn attacker opens a PR on an open source project. In some commit, one of the .js, .py, or .cs files hides the payload in "empty" strings. If the reviewer lacks detection tools, the malicious code makes it to productionMalicious commits in projects
Shared filesA "useful" script is sent to a colleague (hex converter, utility, emulator). The file works normally, but running it also launches a hidden background processExactly what this project demonstrates

How do they do it in practice?

  1. In a package.json: Using lifecycle hooks like "preinstall": "node setup.js" or "postinstall". If setup.js has a payload obfuscated with Unicode, the system (or the CI/CD pipeline) will blindly execute it when building the project.

  2. In a GitHub commit: a .js or .py file that looks like a normal utility. Inside a string that appears empty ('' or ` `), there are thousands of invisible characters encoding a malicious payload.

  3. In a configuration file: A .env or .yaml looks normal but contains a hidden payload that executes when parsed by certain frameworks.


Invisible Unicode techniques

Multiple families of invisible Unicode characters can be exploited. Each one carries a different risk level depending on its potential to conceal code:

TechniqueUnicode RangeRiskDescription
Variation SelectorsU+FE00-FE0F, U+E0100-E01EFCRITICALSteganography: 256 values, 1 char = 1 byte
Tags BlockU+E0001-E007FCRITICALSteganography: maps 1:1 to ASCII
Zero-Width CharactersU+200B-200DHIGHBinary encoding: 8 chars = 1 byte
Bidi OverridesU+202A-202E, U+2066-2069HIGHTrojan Source (CVE-2021-42574)
Bidi MarksU+200E-200F, U+061CMEDIUMLTR/RTL direction marks
Invisible OperatorsU+2060-2064MEDIUMWord Joiner and invisible operators
Mongolian Free VSU+180B-180DMEDIUMMongolian variation selectors
Hangul FillersU+115F-1160, U+3164, U+FFA0MEDIUMEmpty, invisible Hangul characters
Line/Paragraph SeparatorsU+2028-2029MEDIUMBreak strings in JavaScript
Deprecated FormatU+206A-206FLOWDeprecated but functional formatting
Interlinear AnnotationsU+FFF9-FFFBLOWInvisible annotation markers
Musical FormattingU+1D173-1D17ALOWInvisible musical formatting
Shorthand ControlsU+1BCA0-1BCA3LOWInvisible shorthand formatting
Other InvisibleU+00AD, U+034F, U+180E, U+FEFFLOWSoft Hyphen, CGJ, MVS, BOM

The defense scanners in this project detect all 14 categories.


Requirements

You only need 1 of the following languages to use the project:

LanguageMinimum version
Python3.6+
Node.js14+
C# (.NET)6+

You do not need all three installed. Use whichever you prefer.


Defense and Detection Strategies

No single method protects against all vectors. Each strategy covers different scenarios:

1. Scan files with the Unicode detector (this project)

This project includes scanners that detect invisible characters in any source code file. This is the most direct defense against this technique, regardless of how the file arrived (PR, package, shared file, etc.):

# Python:
python defense/python/unicode_scanner.py --decode suspicious_file.py
# JavaScript:
node defense/javascript/unicode_scanner.js --decode suspicious_file.js

Protects against: all vectors (shared files, PRs, packages, hidden commits)

For more details on using the scanner, see the defense README.

2. Disable automatic script execution in npm/pnpm

This blocks the preinstall and postinstall fields in package.json, preventing code from running automatically when you install a package:

# For npm:
npm config set ignore-scripts true# For pnpm:
pnpm config set ignore-scripts true

Protects against: Supply Chain Attacks via npm/pnpm hooks only. Does not protect against files you run manually (like the baits in this project) or against hidden code in PRs or commits.

3. Check actual file sizes

A file with a hidden payload weighs much more than it appears. For example, a 1 KB visible script can weigh 15 KB on disk because of the invisible characters. If a file with only a few lines weighs more than expected, it is suspicious.

Protects against: shared files and PRs, a quick check before running or merging.


Disclaimer

LEGAL WARNING

This project is EXCLUSIVELY EDUCATIONAL and is designed to:

  • Demonstrate how invisible Unicode characters can be exploited in real attacks
  • Teach about the multiple attack vectors where this technique appears
  • Provide DEFENSE tools to detect these attacks
  • Train cybersecurity professionals in detection techniques

Using this software for unauthorized access to computer systems is ILLEGAL and is punishable under:

  • Computer Fraud and Abuse Act (CFAA)
  • Computer Misuse Act 1990, United Kingdom
  • And equivalent legislation in each jurisdiction

The author assumes NO responsibility for misuse of this software. It should only be used in controlled environments, with explicit authorization, and for the purposes of learning, research, or authorized penetration testing.

By cloning or downloading this repository, you agree to use it solely for educational and legal purposes.

License

This project is licensed under the MIT License.

Share with the community

Share with others who might benefit from this tool

Share on RedditShare on Hacker NewsShare on TwitterShare on FacebookShare on LinkedIn


⭐ If this helps you, consider giving it a ⭐

Made with ❤️ for you

About

Learn how attackers hide code in plain sight using invisible Unicode characters, and how to detect it

Resources

Stars

11 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages