Hello, this is Ryuta Hamamoto from TIMEWELL.
There is executable code inside characters that display nothing at all.
Written down it sounds like a conjuring trick. It happened. The attack is called GlassWorm, and it spread through the extensions developers install in their editors.
What makes it awkward is not the scale of the damage but the assumption it breaks. We have secured software by having people read code and approve it. You cannot approve or reject something you cannot read.
What happened
Facts first. Koi Security detected and published this on 18 October 20251.
What was compromised were VS Code extensions. Extensions carrying malicious code appeared on OpenVSX and other marketplaces. Sixteen compromised extensions were identified, with 35,800 total downloads1.
What those extensions did, broadly1:
- Steal npm, GitHub, OpenVSX and Git credentials
- Target 49 different cryptocurrency wallet extensions
- Stand up a SOCKS proxy on the developer's machine, turning it into a relay for the attacker's traffic
- Install a hidden VNC server for full remote access
And the heaviest part: it uses the stolen credentials to compromise further packages and extensions1. One infected developer means every asset they can publish to becomes the next source of infection. It self-propagates. This is reported as the first such behaviour confirmed in VS Code extensions2.
AI Security training, taken seriously
A 2-day intensive course fully aligned with OWASP, NIST, ISO/IEC 42001, and METI. Take it as executives, practitioners, or both.
Why it does not reach the eye
This is the technical core, so let me take it slowly.
The characters used are variation selectors. The ranges are U+FE00 to U+FE0F (16 characters) and U+E0100 to U+E01EF (240 characters). GlassWorm mainly used the latter, which can represent 256 distinct values and therefore maps directly onto byte values 0 through 2553.
A variation selector exists to specify which visual form of a character to display. In Japanese, for instance, the same kanji can have several glyph forms, and a selector says "render this one." So it draws nothing itself. It is an instruction about how the preceding character appears, with no visible content of its own.
Which means that in an editor you see an empty line. The same in a GitHub diff. The line looks blank whether it holds nothing or several hundred characters3.
Execution works like this3:
- Recover a byte value from each character's code point by arithmetic
- Assemble those bytes into a Base64 string
- Decode to get JavaScript source
- Run it through
eval
So the invisible characters are data, and a short piece of visible code assembles and runs them. From the attacker's side, the payload hides in something unreadable while the visible portion looks harmless.
If you want to know whether your development practice can absorb this class of risk, the AI literacy check gives you a baseline before reading on.
Where you actually step on it
Concrete situations. None of them involve doing anything unusual.
One. Installing an extension.
You search the marketplace and pick one with a lot of downloads. That is the most direct route. The worst-affected extension in this case had over 35,000 installs1. Popularity is not safety. If anything, popular things do more damage when they are taken over.
And here the extensions were legitimate and became compromised later. Clean at install time, delivered by auto-update. "I checked it when I installed it" does not help.
Two. Reviewing a pull request.
You open a colleague's PR and read the diff. It looks like one blank line was added. You approve without thinking about it. That works.
This is the frightening part. It is not a question of the reviewer's attention. What is invisible stays invisible however carefully you look. Experienced reviewers may fare worse, since "the diff is small, so it is safe" is a reasonable heuristic that fails completely here.
Three. Updating dependencies.
You bump an npm package and glance at the lockfile diff. You do not read the contents. Nobody reads tens of thousands of lines every time. If upstream is compromised, it lands in your build.
Four. Having AI write or read code.
This route is new. You point a coding agent at a repository and let it work. The agent reads files as text. Invisible characters exist perfectly well as text. A human does not see them, they do not appear in the AI's output, and they can be copied into the next file.
Development tooling becoming the infection route is covered in when AI tools become the infection route, using the TanStack case. AI suggesting packages that do not exist is in Slopsquatting.
A command channel you cannot take down
One more element that resists the usual response: where the command-and-control lives.
GlassWorm has three layers of it1.
The primary channel is transaction memos on the Solana blockchain. What is written to a blockchain cannot be removed afterwards by design. A command channel that reporting cannot stop. The standard responses — take down the malicious domain, block the IP — do not apply.
A secondary channel connects directly to attacker servers, and a fallback encodes instructions into Google Calendar event titles1. Having that much redundancy is a sign of serious operational intent.
Looked at this way, stopping distribution does not end it. Anything already on a machine keeps fetching instructions.
The defence is to stop trusting your eyes
So what do you do? The direction is clear. Replace inspection that depends on human eyes with inspection done by machine.
One. Check locally before opening a PR.
In a Node.js environment, this checks for invisible characters4:
npx anti-trojan-source src/index.js
Make it a pre-commit habit. Failing locally costs a reviewer nothing.
Two. Fail the build in CI.
This is the reliable one. In GitHub Actions, insert a step like this4:
- name: Scan for invisible Unicode attacks
run: npx anti-trojan-source --files='**/*.{js,ts,jsx,tsx}' --json
Fail on detection. The machine stops it before a human approves it. Add extensions as you add languages. There is also an ESLint plugin form for folding into an existing setup4.
What matters here is not making "be careful" the countermeasure. This attack is designed so attention cannot catch it. What attention cannot catch, structure has to.
Three. Rotate credentials and reduce them.
GlassWorm's propagation runs on stolen credentials. Rotate npm, GitHub and OpenVSX tokens regularly, and delete tokens you are not using from developer machines4. Personal machines carrying tokens issued years ago is extremely common. A single inventory pass reduces the fuel available for spread.
Four. Manage extensions as assets.
Stop letting everyone install what they like, and know as an organisation what is installed. Review at adoption is not enough, because the compromise route here arrives via update. "We checked it at install" does not cover after-the-fact contamination.
What this does in an era of AI-read code
A closing thought.
The share of code no human reads is only going up. AI writes it, AI reads it, AI reviews it. In that world, "characters invisible to the human eye" become a region nobody's check covers.
An AI can handle invisible characters as text, but it will not necessarily report them as anomalous. There is no guarantee of "this line contains 240 variation selectors." They are valid Unicode, so they are not even corrupted data.
Which is why this check belongs in the automated pipeline. Not in human review, not in AI review, but in a deterministic rule. Look at the bytes; stop if unexpected characters appear. That is all.
Honestly, what struck me most about this attack was not its sophistication but its choice of target. Variation selectors exist so that multilingual text displays correctly. Slack created in good faith, for compatibility, turned out to be a hiding place. No new vulnerability was found; an existing specification was used sideways.
And this is not the first time.
In 2021, Nicholas Boucher and Ross Anderson at Cambridge University published Trojan Source. That one abuses Unicode's bidirectional text control characters, the specification that makes right-to-left languages such as Arabic and Hebrew display correctly. By reordering tokens at the display layer only, it makes the code a human reads and the code a compiler parses into different things5.
The reach was broad: compilers for C, C++, C#, JavaScript, Java, Rust, Go and Python were affected, and CVE-2021-42574 and CVE-2021-42694 were assigned5.
The tool named anti-trojan-source above is named after that attack, because it was built to counter it. A five-year-old countermeasure works against this one unchanged. The technique differs, but the shape of the hole — display diverging from substance — is the same.
This form of attack will surface in some other specification. Variation selectors, bidirectional controls, zero-width characters, private use areas. Unicode has plenty of "valid but invisible," and all of it qualifies. So the defence should be generalised too: verify mechanically, somewhere, that what you are reading and what the machine executes are the same thing. That is the substance of it.
If you want to review security training or your development practice, WARP SECURITY may be a useful reference point, and you can bring a specific situation to us here.
Footnotes
-
Koi Security's report on GlassWorm (detected and published 18 October 2025). Targeting VS Code extensions; sixteen compromised extensions listed in the report's IOC section (fifteen on OpenVSX, one on VS Code) with 35,800 total downloads; theft of npm authentication tokens, GitHub credentials, OpenVSX credentials, Git credentials and data from 49 different cryptocurrency wallet extensions; installation of a SOCKS proxy and a hidden VNC server on infected machines; a command-and-control arrangement whose primary channel embeds a Base64-encoded payload URL in Solana blockchain transaction memos, with direct connections to attacker servers and encoding into Google Calendar event titles as fallbacks; and a self-propagation cycle in which stolen credentials are used to compromise further packages and extensions, are all from that report. https://www.koi.ai/blog/glassworm-first-self-propagating-worm-using-invisible-code-hits-openvsx-marketplace — specific indicators of compromise such as wallet addresses, IP addresses and URLs are deliberately omitted from this article to avoid facilitating misuse. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
GlassWorm is reported as the first self-propagating worm confirmed to target VS Code extensions. https://www.veracode.com/blog/glassworm-vs-code-extension/ — https://www.bleepingcomputer.com/news/security/self-spreading-glassworm-malware-hits-openvsx-vs-code-registries/ ↩
-
The invisible characters used are variation selectors, with VS1 to VS16 at U+FE00 to U+FE0F (16 characters) and VS17 to VS256 at U+E0100 to U+E01EF (240 characters). GlassWorm's implementation mainly used the latter, supporting 256 distinct values corresponding to byte values 0 to 255. Variation selectors select a different visual representation of the same character and therefore do not render, so they do not appear visually in most text editors. Execution recovers byte values from the code points, reassembles them as a Base64 string, decodes that to JavaScript source and runs it via
eval(). https://www.endorlabs.com/reports/invisible-threats-glassworm-unicode-vscode ↩ ↩2 ↩3 -
On defences: not relying on visual code review alone; local checking before opening a pull request with
npx anti-trojan-source src/index.js; automated checking before merge in GitHub Actions withnpx anti-trojan-source --files='**/*.{js,ts,jsx,tsx}' --jsonand blocking on detection; the option of folding it into an existing workflow as an ESLint plugin; regular rotation of npm, GitHub and OpenVSX tokens and deletion of unused credentials; and continuous verification of third-party components — all from the same article. https://snyk.io/articles/defending-against-glassworm/ ↩ ↩2 ↩3 ↩4 -
Trojan Source, published in 2021 by Nicholas Boucher and Ross Anderson at Cambridge University. It abuses Unicode's bidirectional text control characters so that source code displays differently from how it executes. An attacker can reorder tokens at the encoding layer, producing a state in which a compiler and a human reviewer see different logic. Compilers for major languages including C, C++, C#, JavaScript, Java, Rust, Go and Python were affected, and CVE-2021-42574 and CVE-2021-42694 were assigned. https://access.redhat.com/security/vulnerabilities/RHSB-2021-007 — https://thehackernews.com/2021/11/new-trojan-source-technique-lets.html — https://krebsonsecurity.com/2021/11/trojan-source-bug-threatens-the-security-of-all-code/ ↩ ↩2




![Automating Code Review with Claude Code: A Practical Guide to Superpowers and Hooks [2026 Edition]](/images/columns/claude-code-automated-code-review/cover.png)

