FlawOpen

flawopen.com/Incidents/shai-hulud-npm-worm-september-2025

● npm ecosystem · CWE-506 / CWE-829 · September 2025 · Critique
Recherche · FlawOpen

Shai-Hulud : le ver npm de septembre 2025 et la confiance lors de l’installation

En septembre 2025, la campagne Shai-Hulud a utilisé des comptes de mainteneurs npm compromis et des hooks postinstall malveillants pour voler des identifiants et publier d’autres paquets infectés.[1]

💡 Explication en Langage Simple (ELI5)

Imaginez une usine qui reçoit une caisse de pièces avec des instructions de déballage s’exécutant seules. Elles copient les badges d’expédition de l’usine et les utilisent pour envoyer des caisses truquées ailleurs. Désactiver ces instructions automatiques ferme cette entrée, mais les pièces doivent encore être inspectées avant emploi.[1]

Concepts Clés et Termes

Lifecycle hook
En septembre, le hook npm postinstall exécutait du code malveillant durant l’installation. C’est une fonction fournie par le paquet, pas une vulnérabilité mémoire de npm avec CVE.
Publishing credential
Un jeton npm accorde les droits configurés pour son compte ou son paquet. Les droits de publication volés ont permis la propagation ; durée, portée et MFA dépendent de la politique.
Trusted publishing / OIDC
OIDC permet à un workflow CI autorisé d’obtenir des droits de publication temporaires sans conserver de jeton npm. Un workflow autorisé compromis reste dangereux.

Correspondances de référentiels

  • Référence CWE pour la faiblesse décrite dans ce guide : CWE-506 CWE-829

Chronologie de l'Incident

2025-09-14

GitHub indique avoir été averti de Shai-Hulud.[1]

2025-09-22

GitHub a annoncé avoir supprimé plus de 500 paquets compromis et bloqué les téléversements correspondant aux indicateurs du programme.[1]

Déroulement de l'Attaque Étape par Étape

Étape 1

Publication compromise

Un compte de mainteneur a publié un paquet modifié. L’hameçonnage était une hypothèse sur l’accès initial, non une preuve pour tous les comptes touchés.[1], [2]

Étape 2

Exécution à l’installation

Le postinstall de septembre s’exécutait avec les droits du développeur ou de la CI. Le preinstall de novembre ne doit pas être attribué à cette vague.[1], [3], [2]

Étape 3

Exposition des identifiants

Le programme cherchait dans la configuration et l’environnement des secrets npm, GitHub et cloud ; le risque dépassait les seuls jetons de publication.[1], [2]

Étape 4

Réplication

Les droits de publication volés permettaient de modifier et publier d’autres paquets maintenus par la victime.[1], [2]

Code Source : Vulnérable vs Sécurisé

Comparaison sûre de plans d’installation en Python : aucune fonction ne lance npm, ne lit de secret ou ne contacte le registre. Elle suit la documentation npm v10. Le plan renforcé exige un verrou de dépendances relu, désactive les scripts de cycle de vie et ne transmet pas l’environnement hérité. Un véritable exécuteur doit fournir un npm connu, un répertoire personnel et une configuration isolés sans secrets ni volumes sensibles. --ignore-scripts ne sécurise ni les imports ultérieurs ni les scripts lancés explicitement ; un verrou ne certifie pas l’innocuité du code.

IMPLÉMENTATION VULNÉRABLE
def install_plan(reviewed_lockfile, inherited_env):
    # Renvoie uniquement un plan : aucun paquet n’est installé et aucune commande n’est exécutée.
    return {"argv": ["npm", "ci"],
            "env": dict(inherited_env)}
PATCH SÉCURISÉ ET ROBUSTE
def install_plan(reviewed_lockfile, inherited_env):
    # Renvoie uniquement un plan : aucun paquet n’est installé et aucune commande n’est exécutée.
    if reviewed_lockfile is not True:
        raise ValueError("Review the lockfile before installation")
    return {"argv": ["npm", "ci", "--ignore-scripts"],
            "env": {}}

Liste de Contrôle de Sécurité pour l'Ingénierie

Sources