In this article
Se défendre contre Glassworm : le malware invisible qui bouleverse la sécurité de la chaîne d’approvisionnement logicielle
En octobre 2025, les chercheurs en sécurité de Koi Security ont découvert Glassworm, le premier ver à propagation autonome ciblant les extensions VS Code et utilisant des caractères Unicode invisibles pour dissimuler du code malveillant à la vue de tous. Avec plus de 35 800 installations compromises et une infrastructure C2 toujours active, Glassworm marque une nouvelle étape dans l’évolution des attaques contre la chaîne d’approvisionnement logicielle et remet en cause notre modèle traditionnel de sécurité fondé sur la revue de code.
Cet article examine l’attaque Glassworm, dont les origines remontent à la vulnérabilité Trojan Source (CVE-2021-42574), et montre comment les développeurs et les équipes de sécurité peuvent détecter et prévenir ces attaques par caractères invisibles avec anti-trojan-source, un outil de détection open source en ligne de commande qui propose désormais une analyse Unicode par catégorie et des rapports améliorés.
À retenir :
Des caractères Unicode invisibles peuvent dissimuler du code malveillant exécutable, littéralement indétectable lors d’une revue de code
La revue de code traditionnelle, certaines vues de différences et la coloration syntaxique ne détectent absolument pas ces attaques
L’attaque se propage d’elle-même grâce aux identifiants volés, chaque victime devenant un nouveau vecteur d’infection
La détection nécessite des outils spécialisés qui analysent les caractères Unicode par catégorie, et pas seulement à partir de listes explicites
L’évolution des attaques par caractères invisibles
Tout a commencé avec l’article universitaire au titre bien trouvé, Trojan Source. Pour être tout à fait exact sur l’historique des incidents de sécurité informatique, des caractères et glyphes Unicode avaient toutefois déjà été utilisés au cours des décennies précédentes pour dissimuler des informations et induire en erreur les systèmes et les utilisateurs. Cette technique n’a donc rien de nouveau en soi.
En novembre 2021, Nicholas Boucher et Ross Anderson, chercheurs à l’Université de Cambridge, ont publié leur article fondateur Trojan Source: Invisible Vulnerabilities, révélant une vulnérabilité critique (CVE-2021-42574) qui touche pratiquement tous les langages de programmation modernes.
Le problème fondamental venait des caractères de contrôle Unicode bidirectionnels (bidi), capables de réordonner visuellement le code source de façon à tromper les réviseurs, tout en préservant l’ordre d’exécution logique suivi par les compilateurs et les interpréteurs.
Voici du code C :
bool isAdmin = false;
/*
begin admins only
*/ if (isAdmin) {
printf("You are an admin.\n");
/*
end admins only
*/ }Le code ci-dessus semble être protégé par un contrôle d’accès. Pourtant, des caractères de contrôle Unicode dissimulés peuvent faire apparaître le commentaire comme du code et ainsi modifier complètement le flux logique. Le compilateur voit une chose ; le réviseur humain en voit une autre.
Techniques d’attaque :
Retours anticipés : faire apparaître une instruction `return` à l’intérieur d’un commentaire
Mise en commentaire : faire apparaître visuellement des commentaires comme du code exécutable
Chaînes étirées : faire apparaître visuellement des littéraux de chaîne comme du code
Homoglyphes : utiliser des caractères visuellement identiques issus de systèmes d’écriture différents (CVE-2021-42694)
L’effet d’amplification sur la chaîne d’approvisionnement logicielle
Les chercheurs à l’origine de Trojan Source ont mis en évidence cet important effet multiplicateur dans le développement logiciel moderne :
« Si un adversaire parvient à intégrer des vulnérabilités ciblées dans du code open source en trompant les réviseurs humains, les logiciels en aval hériteront probablement de ces vulnérabilités. »
C’est précisément ce que l’on observe aujourd’hui dans les attaques réelles.
Glassworm : la nouvelle évolution (2025)
Quatre ans après la divulgation de Trojan Source, les attaquants exploitent ces techniques dans la nature. En octobre 2025, Glassworm est apparu : le premier ver à propagation autonome utilisant des caractères Unicode invisibles pour compromettre des extensions VS Code sur la marketplace OpenVSX.
Ce qui distingue Glassworm :
Injection de code invisible : utilise des sélecteurs de variation Unicode qui ne produisent aucun rendu visuel. Le code n’est ni obfusqué ni minifié : il est littéralement invisible à l’œil nu
Propagation autonome : vole des identifiants NPM, GitHub et OpenVSX pour compromettre automatiquement d’autres packages et extensions, entraînant une croissance exponentielle
Une infrastructure indestructible :
La blockchain Solana comme C2 principal (immuable et impossible à démanteler)
Google Calendar comme C2 de secours (service légitime qui contourne les contrôles de sécurité)
Connexions directes par IP avec chiffrement dynamique
Fonctionnalités RAT complètes : transforme les postes de travail des développeurs infectés en infrastructure criminelle avec des proxys SOCKS, des serveurs VNC cachés et des canaux de commande P2P
Comprendre l’attaque Glassworm
Étape 1 : la charge utile invisible
En examinant l’extension VS Code CodeJoy compromise (version 1.8.3), les chercheurs en sécurité ont trouvé ce qui semblait être des lignes vides entre les lignes 2 et 7 :
import * as vscode from 'vscode';
// [MASSIVE GAP HERE - APPEARS EMPTY]
export function activate(context: vscode.ExtensionContext) {Mais cet espace n’était pas vide. Il contenait du code JavaScript exécutable encodé dans des caractères Unicode non imprimables, plus précisément des sélecteurs de variation Unicode qui ne produisent aucun rendu visuel.
Pour un développeur qui révise le code : des lignes vides ou des espaces.
Pour les outils d’analyse statique : rien à analyser.
Pour l’interpréteur JavaScript : du code malveillant exécutable.
Étape 2 : C2 via la blockchain
Le code invisible se connecte à la blockchain Solana et lit les mémos de transaction associés à une adresse de portefeuille codée en dur. Dans le mémo ? Un objet JSON contenant un lien encodé en base64 pour télécharger l’étape suivante :
{"link":"aHR0cDovLzIxNy42OS4zLjIxOC9xUUQlMkZKb2kzV0NXU2s4Z2dHSGlTdg=="}Décodé : http://217.69.3.218/qQD%2FJoi3WCWSk8ggGHiSv%3D%3D
À noter : les transactions blockchain sont immuables et ne peuvent être ni supprimées ni modifiées. Les portefeuilles crypto sont pseudonymes ; aucun hébergeur ni bureau d’enregistrement n’intervient, et les attaquants peuvent publier de nouvelles transactions contenant de nouvelles URL de charges utiles à moindre coût.
Étape 3 : collecte d’identifiants
La charge utile téléchargée (chiffrée avec AES-256-CBC, les clés étant transmises dans les en-têtes de réponse HTTP) cible les identifiants sensibles suivants :
Jetons d’authentification NPM — pour publier des packages malveillants
Jetons GitHub — pour compromettre des dépôts
Identifiants OpenVSX — pour injecter d’autres extensions
Identifiants Git — pour envoyer du code malveillant
49 extensions de portefeuilles de cryptomonnaies différentes — MetaMask, Phantom, Coinbase, etc.
Mais ce n’est pas tout : un C2 de secours utilise Google Calendar. Le malware récupère un événement de calendrier dont le titre contient une URL encodée en base64 :
aHR0cDovLzIxNy42OS4zLjIxOC9nZXRfem9tYmlfcGF5bG9hZC9xUUQlMkZKb2kzV0NXU2s4Z2dHSGlUdg==Remarquez le chemin : /get_zombi_payload/ — oui, « zombi » comme dans un botnet zombie.
L’étape suivante, et dernière, est appelée ZOMBI — le RAT complet (cheval de Troie d’accès à distance). La charge utile finale transforme les postes de travail des développeurs infectés en nœuds d’un réseau criminel. Elle s’appuie sur un proxy SOCKS pour acheminer le trafic au sein des réseaux d’entreprise, ainsi que sur d’autres mécanismes réseau comme WebRTC, BitTorrent et VNC caché.
L’attaquant peut utiliser votre navigateur et vos sessions ouvertes, accéder à vos e-mails et à Slack, lire votre code source, voler d’autres identifiants et se déplacer vers d’autres systèmes de votre réseau, tandis que rien ne semble se passer à l’écran.
Détecter les caractères invisibles avec anti-trojan-source
Le projet anti-trojan-source est un outil de sécurité open source spécialement conçu pour détecter les attaques basées sur Unicode, notamment l’injection de caractères invisibles de type Glassworm. Créé à l’origine pour détecter les attaques Trojan Source, il a évolué pour offrir une protection complète grâce à la détection de caractères spécifiques et à l’analyse Unicode par catégorie.
Ce que détecte anti-trojan-source
277 caractères confondables spécifiques :
Caractères de contrôle Unicode bidirectionnels (U+202A-U+202E, U+2066-U+2069)
Caractères de largeur nulle (U+200B, U+200C, U+200D)
Sélecteurs de variation (U+FE00-U+FE0F) — les 16 sélecteurs de base
Sélecteurs de variation étendus (U+E0100-U+E01EF) — 240 caractères supplémentaires utilisés par Glassworm
Espace insécable (U+00A0)
Liant de mots, trait d’union conditionnel et autres caractères invisibles
Détection par catégorie (à l’épreuve du temps) :
Tous les caractères de format Unicode (catégorie Cf) — détecte les caractères de formatage invisibles par catégorie
Tous les caractères de contrôle Unicode (catégorie Cc), à l’exception des espaces couramment utilisés (TAB, LF, CR)
Grâce à cette approche par catégorie, anti-trojan-source peut détecter les nouveaux caractères invisibles ajoutés à Unicode à l’avenir sans qu’il soit nécessaire de mettre à jour explicitement la liste de caractères.
Fonctionnalités de détection
1. Détection booléenne simple
import { hasConfusables } from 'anti-trojan-source'
const code = readFileSync('suspicious-file.js', 'utf-8')
const isDangerous = hasConfusables({ sourceText: code })
if (isDangerous) {
console.error('⚠️ Invisible characters detected!')
}2. Résultats détaillés
import { hasConfusables } from 'anti-trojan-source'
const findings = hasConfusables({
sourceText: code,
detailed: true
})
findings.forEach(finding => {
console.log(`Line ${finding.line}:${finding.column}`)
console.log(` Character: ${finding.codePoint} ${finding.name}`)
console.log(` Category: ${finding.category}`)
console.log(` Context: ${finding.snippet}`)
})Exemple de résultat :
Line 3:45
Character: U+E0100 VARIATION SELECTOR-17
Category: Variation Selector
Context: const value = getUserInput()
Line 12:8
Character: U+200B ZERO WIDTH SPACE
Category: Cf (Format)
Context: if (isAdmin) {3. Exemple d’utilisation de la CLI avec plusieurs modes de sortie
# Simple mode (exit code 1 if found)
npx anti-trojan-source --files='src/**/*.js'
# Verbose mode with detailed information
npx anti-trojan-source --files='src/**/*.js' --verbose
# JSON mode for programmatic processing
npx anti-trojan-source --files='src/**/*.js' --jsonGuide de mise en œuvre d’anti-trojan-source
Installez l’outil en ligne de commande dans votre projet :
1npm install -D anti-trojan-sourceOu utilisez directement npx (sans installation) :
npx anti-trojan-source --files='**/*.js'Analysez ensuite un seul fichier :
npx anti-trojan-source src/index.jsOu analysez plusieurs fichiers avec les motifs glob du shell :
npx anti-trojan-source --files='src/**/*.{js,ts,jsx,tsx}'La CLI anti-trojan-source prend en charge le mode verbosemode pour afficher des résultats détaillés. Pratique pour savoir précisément ce qui a été détecté et à quel endroit :
npx anti-trojan-source --files='src/**/*.js' --verboseExemple de résultat :
[x] Detected cases of trojan source in the following files:
|
- src/utils.js
Line 12:34 - U+200B ZERO WIDTH SPACE [Cf (Format)]
Snippet: const value = getUserInput()
Line 45:10 - U+202E RIGHT-TO-LEFT OVERRIDE [Cf (Format)]
Snippet: if (isAdmin) { // Check permissions
Line 78:22 - U+E0100 VARIATION SELECTOR-17 [Variation Selector]
Snippet: const token = process.env.API_KEYUn mode de sortie JSON est également disponible pour l’analyse programmatique. Il est utile pour l’automatisation, l’intégration CI/CD et la création de rapports personnalisés :
npx anti-trojan-source --files='src/**/*.js' --jsonExemple de résultat :
[
{
"file": "src/utils.js",
"findings": [
{
"line": 12,
"column": 34,
"codePoint": "U+200B",
"name": "ZERO WIDTH SPACE",
"category": "Cf (Format)",
"snippet": "const value = getUserInput()"
},
{
"line": 45,
"column": 10,
"codePoint": "U+202E",
"name": "RIGHT-TO-LEFT OVERRIDE",
"category": "Cf (Format)",
"snippet": "if (isAdmin) { // Check permissions"
}
]
}
]Intégration CI/CD
Voici un exemple de création d’une GitHub Action à intégrer à votre processus CI :
Créez le fichier .github/workflows/security-scan.yml :
name: Unicode Security Scan
on:
pull_request:
push:
branches: [main, master, develop]
jobs:
scan-invisible-chars:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Scan for invisible Unicode attacks
run: npx anti-trojan-source --files='**/*.{js,ts,jsx,tsx,py,java,go,rs}' --json
- name: Fail on detection
if: failure()
run: |
echo "::error::Invisible Unicode characters detected in source code"
echo "::error::This could indicate a Trojan Source or Glassworm-style attack"
exit 1Détection avancée par catégorie
Comprendre les catégories Unicode
anti-trojan-source ne recherche pas seulement des caractères spécifiques : il les analyse aussi selon leur catégorie Unicode, pour mieux résister aux nouvelles attaques.
Catégorie Format (Cf) :
Caractères qui modifient le formatage sans produire de rendu visuel
Inclut les contrôles bidirectionnels, les séparateurs invisibles et les caractères de largeur nulle
Exemples : U+200B (ESPACE DE LARGEUR NULLE), U+202E (INVERSION DROITE À GAUCHE)
Catégorie Contrôle (Cc) :
Caractères de contrôle non imprimables
Plages : U+0000-U+001F et U+007F-U+009F
Exception : TAB (U+0009), LF (U+000A) et CR (U+000D) figurent sur une liste d’autorisation, car ils sont légitimes
Pourquoi la détection par catégorie est-elle importante ?
Une approche traditionnelle ressemblerait au code JavaScript suivant :
const dangerousChars = ['\u200B', '\u200C', '\u200D', /* ... explicit list */];
// Problem: New Unicode versions add new characters
// Problem: Attackers can use unlisted charactersUne approche par catégorie permettrait quant à elle de :
Détecter TOUS les caractères Format (Cf), même ceux qui ne sont pas explicitement répertoriés
Détecter TOUS les caractères Contrôle (Cc), à l’exception des espaces autorisés
Pour la suite
Ne vous fiez pas uniquement à la revue visuelle du code, même si certains IDE comme VS Code et la visionneuse de différences en ligne de GitHub indiquent désormais certains caractères Unicode dans le texte. Ne vous dites pas « ça a l’air correct » et ne partez pas du principe que vos outils affichent automatiquement les caractères invisibles ; recherchez-les de manière proactive.
Nous vous recommandons d’analyser tout le code avant de fusionner les pull requests. Intégrez Snyk (c’est gratuit) : la solution exécute automatiquement une analyse statique du code et surveille en continu votre projet à partir de dépôts GitHub et d’autres intégrations.
Utilisez l’utilitaire en ligne de commande anti-trojan-source pour détecter spécifiquement les caractères potentiellement dangereux dans votre code source.
Prêt à pérenniser votre pipeline de développement face à la prochaine évolution des menaces visant la chaîne d’approvisionnement logicielle ? Téléchargez le livre blanc : Naviguer dans la chaîne d’approvisionnement logicielle moderne : sécuriser l’open source, le code généré par l’IA et la conformité SBOM.
Ressources
Outils :
CLI anti-trojan-source : https://github.com/lirantal/anti-trojan-source
Plugin ESLint : https://github.com/lirantal/eslint-plugin-anti-trojan-source
Recherche :
Article Trojan Source : https://trojansource.codes/trojan-source.pdf
Analyse de Glassworm : https://www.koi.ai/blog/glassworm-first-self-propagating-worm-using-invisible-code-hits-openvsx-marketplace
CVE-2021-42574 : divulgation de la vulnérabilité Trojan Source
CVE-2021-42694 : attaques par homoglyphes
Normes :
Norme Unicode : https://unicode.org/standard/standard.html
Catégories Unicode : https://www.unicode.org/reports/tr44/#General_Category_Values
Sécurisez votre chaîne d’approvisionnement avec Snyk
87 % des personnes interrogées ont été touchées par des problèmes de sécurité de la chaîne d’approvisionnement. Protégez la vôtre avec Snyk.