For years, the conventional wisdom was simple: run npm install --ignore-scripts and your dependencies cannot execute arbitrary code at install time. That assumption died in June 2026. The Phantom Gyp technique — a 157-byte binding.gyp file — bypassed --ignore-scripts entirely to compromise 57 npm packages in under two hours, stealing credentials from AWS, GitHub, Azure, Kubernetes, and HashiCorp Vault. npm's response is definitive: npm v12, shipping in July 2026, disables all install scripts by default. If you run CI/CD pipelines with Node.js, you need to act before that deadline or face broken builds.

Pendant des années, la sagesse conventionnelle était simple : exécuter npm install --ignore-scripts et vos dépendances ne pouvaient pas exécuter de code arbitraire à l’installation. Cette hypothèse est morte en juin 2026. La technique Phantom Gyp — un fichier binding.gyp de 157 octets — a court-circuité --ignore-scripts pour compromettre 57 packages npm en moins de deux heures, volant des identifiants AWS, GitHub, Azure, Kubernetes et HashiCorp Vault. La réponse de npm est définitive : npm v12, livré en juillet 2026, désactive tous les scripts d’installation par défaut. Si vous exécutez des pipelines CI/CD avec Node.js, vous devez agir avant cette échéance ou risquer des builds cassées.

Why Install Scripts Are a Security Problem

Pourquoi les Scripts d’Installation Posent un Problème de Sécurité

npm supports four lifecycle scripts that run automatically during npm install: preinstall (before installation), install, postinstall (after installation), and a fifth implicit one — node-gyp rebuild — triggered automatically when a package contains a binding.gyp file, with no lifecycle script required. Every one of these runs with the full permissions of the installing user: access to the filesystem, network, environment variables, and any secrets loaded in the shell session.

npm supporte quatre scripts de cycle de vie qui s’exécutent automatiquement lors de npm install : preinstall (avant l’installation), install, postinstall (après l’installation), et un cinquième implicite — node-gyp rebuild — déclenché automatiquement quand un package contient un fichier binding.gyp, sans aucun script de cycle de vie requis. Chacun s’exécute avec les permissions complètes de l’utilisateur installant : accès au système de fichiers, au réseau, aux variables d’environnement et à tous les secrets chargés dans la session shell.

In a CI/CD environment, this means access to your entire secret store: GITHUB_TOKEN, NPM_TOKEN, AWS_SECRET_ACCESS_KEY, kubeconfig, cloud provider credentials, and any tokens injected by your pipeline. A single compromised transitive dependency — not even a direct dependency — can silently exfiltrate all of these during a routine npm install step.

Dans un environnement CI/CD, cela signifie l’accès à tout votre magasin de secrets : GITHUB_TOKEN, NPM_TOKEN, AWS_SECRET_ACCESS_KEY, kubeconfig, identifiants cloud et tous les jetons injectés par votre pipeline. Une seule dépendance transitive compromise — pas même une dépendance directe — peut silencieusement exfiltrer tout cela lors d’une étape npm install de routine.

The scale of the problem is significant: according to Unit42 Palo Alto's npm Threat Landscape report (updated June 2026), over 90 categories of credentials are targeted by malicious packages using postinstall hooks as their primary execution vector.

L’échelle du problème est significative : selon le rapport npm Threat Landscape de Unit42 Palo Alto (mis à jour en juin 2026), plus de 90 catégories d’identifiants sont ciblées par des packages malveillants utilisant les hooks postinstall comme vecteur d’exécution principal.

Phantom Gyp: How a 157-Byte File Bypassed --ignore-scripts

Phantom Gyp : Comment un Fichier de 157 Octets a Court-Circuité --ignore-scripts

--ignore-scripts was the go-to defense for teams that wanted to run npm install without executing arbitrary code. It blocks preinstall, install, and postinstall scripts. What it does not block is the implicit node-gyp rebuild that npm triggers automatically when it detects a binding.gyp file in any installed package.

--ignore-scripts était la défense de référence pour les équipes souhaitant exécuter npm install sans exécuter de code arbitraire. Il bloque les scripts preinstall, install et postinstall. Ce qu’il ne bloque pas, c’est le node-gyp rebuild implicite que npm déclenche automatiquement lorsqu’il détecte un fichier binding.gyp dans un package installé.

The Phantom Gyp technique weaponized exactly this gap. Attackers injected a 157-byte binding.gyp file into compromised package versions. The file itself contains only minimal valid GYP syntax, but it abuses GYP's command substitution feature — a mechanism designed for legitimate build-time variable expansion — to execute a hidden payload. When npm triggers node-gyp rebuild on detection, the payload runs, completely invisible to --ignore-scripts.

La technique Phantom Gyp a armé exactement cette lacune. Les attaquants ont injecté un fichier binding.gyp de 157 octets dans des versions de packages compromis. Le fichier lui-même ne contient qu’une syntaxe GYP minimale valide, mais il exploite la fonctionnalité de substitution de commandes de GYP — un mécanisme conçu pour l’expansion légitime de variables au moment de la compilation — pour exécuter une charge utile cachée. Quand npm déclenche node-gyp rebuild à la détection, la charge utile s’exécute, complètement invisible pour --ignore-scripts.

The payload itself used four layers of obfuscation: a ROT cipher, AES-128-GCM encryption, a runtime switch (downloading the Bun JavaScript runtime in under one second to execute the final stage outside of Node.js, evading monitoring tools that only watch Node.js processes), and a dead-man's switch that deleted evidence of execution. The result: arbitrary code execution during npm install with --ignore-scripts active, with no forensic trace.

La charge utile elle-même utilisait quatre couches d’obfuscation : un chiffrement ROT, un chiffrement AES-128-GCM, un changement de runtime (téléchargeant le runtime JavaScript Bun en moins d’une seconde pour exécuter l’étape finale hors de Node.js, évitant les outils de surveillance qui ne surveillent que les processus Node.js), et un interrupteur à homme mort qui supprimait les preuves d’exécution. Résultat : exécution de code arbitraire pendant npm install avec --ignore-scripts actif, sans trace forensique.

Miasma Wave 2 in June 2026: 57 Packages in Under Two Hours

Miasma Vague 2 en Juin 2026 : 57 Packages en Moins de Deux Heures

The Phantom Gyp technique was deployed at scale during the Miasma Wave 2 attack on June 3, 2026. Starting at 23:30 UTC, attackers compromised 57 npm packages across 286 malicious versions within a rolling campaign lasting under two hours. The largest victim was @vapi-ai/server-sdk, the official Vapi.ai voice AI server SDK with over 408,000 monthly downloads, hit first.

La technique Phantom Gyp a été déployée à grande échelle lors de l’attaque Miasma Vague 2 le 3 juin 2026. Démarrant à 23h30 UTC, les attaquants ont compromis 57 packages npm en 286 versions malveillantes lors d’une campagne de moins de deux heures. La plus grande victime était @vapi-ai/server-sdk, le SDK officiel de Vapi.ai pour les agents IA vocaux avec plus de 408 000 téléchargements mensuels, touché en premier.

The malware harvested credentials from a comprehensive list of targets: npm tokens, GitHub tokens, AWS access keys, Google Cloud credentials, Azure service principal secrets, HashiCorp Vault tokens, and Kubernetes service account tokens. Exfiltration used attacker-controlled GitHub repositories (traced to account liuende501, hosting 236 credential dead-drop repositories). The worm then used any harvested npm token to self-propagate by republishing packages from any maintainer account it could reach.

Le malware récoltait des identifiants depuis une liste complète de cibles : jetons npm, jetons GitHub, clés d’accès AWS, identifiants Google Cloud, secrets de principal de service Azure, jetons HashiCorp Vault et jetons de compte de service Kubernetes. L’exfiltration utilisait des dépôts GitHub contrôlés par l’attaquant (tracés au compte liuende501, hébergeant 236 dépôts de dead-drop). Le ver utilisait ensuite tout jeton npm récolté pour se propager en républiant des packages depuis tout compte mainteneur accessible.

Beyond credential theft, the malware injected backdoor configuration files into AI coding assistants present on the developer's machine: Claude Code (.claude/), Cursor (.cursor/), and Gemini (.gemini/). This meant that every AI-assisted suggestion inside a poisoned project could be quietly influenced by the attacker — a persistence vector that survives package removal.

Au-delà du vol de d’identifiants, le malware injectait des fichiers de configuration de backdoor dans les assistants de code IA présents sur la machine du développeur : Claude Code (.claude/), Cursor (.cursor/) et Gemini (.gemini/). Cela signifiait que chaque suggestion assistée par IA dans un projet empoisonné pouvait être silencieusement influencée par l’attaquant — un vecteur de persistance qui survit à la suppression du package.

npm v12: What Changes in July 2026

npm v12 : Ce qui Change en Juillet 2026

In direct response to Phantom Gyp and the Miasma campaign, GitHub announced the npm v12 security overhaul via changelog on June 9, 2026. Three categories of behavior become opt-in instead of opt-out:

En réponse directe à Phantom Gyp et la campagne Miasma, GitHub a annoncé la refonte de sécurité npm v12 via changelog le 9 juin 2026. Trois catégories de comportement deviennent opt-in au lieu d’opt-out :

1. Install Scripts Blocked by Default

1. Scripts d’Installation Bloqués par Défaut

preinstall, install, and postinstall scripts from dependencies no longer run automatically. Critically, this also covers the implicit node-gyp rebuild triggered by binding.gyp files — the Phantom Gyp vector is patched at the npm level. Packages that legitimately need install scripts must be explicitly allowlisted in your project's npm configuration.

Les scripts preinstall, install et postinstall des dépendances ne s’exécutent plus automatiquement. De manière cruciale, cela couvre également le node-gyp rebuild implicite déclenché par les fichiers binding.gyp — le vecteur Phantom Gyp est corrigé au niveau npm. Les packages qui ont légitimement besoin de scripts d’installation doivent être explicitement ajoutés à la liste d’autorisation dans la configuration npm de votre projet.

2. Git Dependencies Blocked by Default

2. Dépendances Git Bloquées par Défaut

npm install will no longer resolve github:user/repo or git+https://... references by default. You must pass --allow-git or migrate to published registry versions. This closes a common supply chain bypass used to install unreviewed code directly from repositories.

npm install ne résoudra plus par défaut les références github:user/repo ou git+https://.... Vous devez passer --allow-git ou migrer vers des versions de registre publiées. Cela ferme un bypass courant de chaîne d’approvisionnement utilisé pour installer du code non examiné directement depuis des dépôts.

3. Remote URL Dependencies Blocked by Default

3. Dépendances URL Distantes Bloquées par Défaut

Dependencies resolved from remote URLs (direct HTTPS tarballs) are blocked. You must opt in with --allow-remote. Like Git dependencies, these bypass the integrity and audit mechanisms of the npm registry.

Les dépendances résolues depuis des URL distantes (tarballs HTTPS directs) sont bloquées. Vous devez activer cette fonctionnalité avec --allow-remote. Comme les dépendances Git, celles-ci court-circuitent les mécanismes d’intégrité et d’audit du registre npm.

The preparation window is now. npm 11.16.0 (available today) runs in warning mode: it flags every dependency that will break under v12 without blocking anything. Every team should upgrade to npm 11.16.0 immediately to audit their dependency tree before the July deadline.

La fenêtre de préparation, c’est maintenant. npm 11.16.0 (disponible aujourd’hui) fonctionne en mode avertissement : il signale chaque dépendance qui cassera sous v12 sans rien bloquer. Chaque équipe devrait mettre à niveau vers npm 11.16.0 immédiatement pour auditer son arbre de dépendances avant l’échéance de juillet.

Migration Guide: Native Modules Without Install Scripts

Guide de Migration : Modules Natifs Sans Scripts d’Installation

The most common reason npm packages use install scripts is to compile native C/C++ addons via node-gyp. Examples: bcrypt, canvas, sharp, sqlite3, certain crypto libraries. The better-practice alternative is to ship prebuilt binaries and avoid compile-at-install-time entirely.

La raison la plus courante pour laquelle les packages npm utilisent des scripts d’installation est de compiler des addons natifs C/C++ via node-gyp. Exemples : bcrypt, canvas, sharp, sqlite3, certaines bibliothèques crypto. L’alternative de meilleure pratique est de livrer des binaires précompilés et d’éviter complètement la compilation au moment de l’installation.

Recommended: prebuildify + node-gyp-build

Recommandé : prebuildify + node-gyp-build

The prebuildify tool compiles binaries for all target platforms at publish time and bundles them inside the npm package. At install time, node-gyp-build selects the correct prebuilt binary for the current platform — no compilation, no install script, no internet access needed. The irony: shipping all platform binaries bundled is typically faster to download than a single binary fetched via install script.

L’outil prebuildify compile les binaires pour toutes les plateformes cibles au moment de la publication et les regroupe dans le package npm. Au moment de l’installation, node-gyp-build sélectionne le binaire précompilé correct pour la plateforme actuelle — pas de compilation, pas de script d’installation, pas d’accès internet nécessaire. L’ironie : livrer tous les binaires de plateforme regroupés est typiquement plus rapide à télécharger qu’un seul binaire récupéré via un script d’installation.

bash# Publisher: compile prebuilts for all platforms
npm install --save-dev prebuildify
npx prebuildify --napi --strip

# Consumer: package.json (no postinstall needed)
{
  "dependencies": {
    "node-gyp-build": "^4.8.0"
  },
  "gypfile": false
}

Alternative: prebuild + prebuild-install

Alternative : prebuild + prebuild-install

The older prebuild approach downloads a single prebuilt binary at install time. This still requires a postinstall hook (and will need to be allowlisted under npm v12), but the download is from a controlled GitHub release rather than arbitrary code execution.

L’ancienne approche prebuild télécharge un seul binaire précompilé au moment de l’installation. Cela nécessite toujours un hook postinstall (et devra être autorisé sous npm v12), mais le téléchargement provient d’une release GitHub contrôlée plutôt que d’une exécution de code arbitraire.

If You Must Keep Install Scripts: Allowlisting Under npm v12

Si Vous Devez Garder des Scripts d’Installation : Liste d’Autorisation sous npm v12

For packages that legitimately require install scripts and cannot be migrated, npm v12 will require explicit allowlisting in .npmrc:

Pour les packages qui nécessitent légitimement des scripts d’installation et ne peuvent pas être migrés, npm v12 nécessitera une autorisation explicite dans .npmrc :

.npmrc# npm v12: explicitly allowlist packages that may run install scripts
# Generate this list now using: npm 11.16.0+ (warning mode)
install-scripts-allowed[]=sharp
install-scripts-allowed[]=canvas
install-scripts-allowed[]=sqlite3
install-scripts-allowed[]=bcrypt

Use npm 11.16.0 warning mode now to generate this list automatically before the v12 deadline:

Utilisez le mode d’avertissement npm 11.16.0 maintenant pour générer cette liste automatiquement avant la déadline v12 :

bash"># Upgrade to warning-mode npm
npm install -g npm@11.16.0

# Run install — warnings will list every package
# that will break under v12
npm install 2>&1 | grep "install script blocked"

Detecting Install Scripts in CI/CD: The hasInstallScript Flag

Détection des Scripts d’Installation en CI/CD : Le Flag hasInstallScript

Every dependency that has a lifecycle script (preinstall, install, postinstall) is marked with "hasInstallScript": true in the package-lock.json. This is the highest-signal indicator available in your lockfile. Any PR that introduces a new dependency with this flag set should trigger a mandatory security review.

Chaque dépendance ayant un script de cycle de vie (preinstall, install, postinstall) est marquée avec "hasInstallScript": true dans le package-lock.json. C’est l’indicateur de signal le plus élevé disponible dans votre lockfile. Toute PR qui introduit une nouvelle dépendance avec ce flag activé devrait déclencher une revue de sécurité obligatoire.

bash"># Find all packages with install scripts in your lockfile
node -e "
const lock = require('./package-lock.json');
const packages = lock.packages || {};
Object.entries(packages)
  .filter(([, meta]) => meta.hasInstallScript)
  .forEach(([name]) => console.log('INSTALL SCRIPT:', name));
"

Add this as a CI gate in GitHub Actions to catch new install scripts before they land:

Ajoutez cela comme porte CI dans GitHub Actions pour détecter les nouveaux scripts d’installation avant qu’ils n’atterrissent :

yaml"># .github/workflows/lockfile-audit.yml
name: Lockfile Security Audit
on: [pull_request]

jobs:
  audit-install-scripts:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check for new install scripts
        run: |
          node -e "
            const lock = require('./package-lock.json');
            const packages = lock.packages || {};
            const found = Object.entries(packages)
              .filter(([, m]) => m.hasInstallScript)
              .map(([n]) => n);
            if (found.length > 0) {
              console.log('Packages with install scripts:', found.join(', '));
              // Diff against baseline to catch NEW ones
            }
          "
      - name: Run npm audit
        run: npm audit --audit-level=high

Note: the Phantom Gyp technique uses binding.gyp — which triggers an implicit node-gyp rebuild, not an explicit lifecycle script. These packages may have "gypfile": true in their package.json but no hasInstallScript flag in the lockfile. Check for both:

Note : la technique Phantom Gyp utilise binding.gyp — qui déclenche un rebuild node-gyp implicite, pas un script de cycle de vie explicite. Ces packages peuvent avoir "gypfile": true dans leur package.json mais pas de flag hasInstallScript dans le lockfile. Vérifiez les deux :

bash"># Detect packages with binding.gyp (implicit node-gyp rebuild)
node -e "
const lock = require('./package-lock.json');
const fs = require('fs');
const packages = lock.packages || {};
Object.entries(packages).forEach(([name, meta]) => {
  if (meta.resolved) {
    const pkgJsonPath = name.replace('node_modules/', '') ;
    // Check node_modules for binding.gyp files after install
    const gypPath = \`node_modules/\${pkgJsonPath.replace('node_modules/', '')}/binding.gyp\`;
    if (fs.existsSync(gypPath)) {
      console.log('BINDING.GYP FOUND:', name);
    }
  }
});
"

npm Install Script Threat — 2026 Numbers

Scripts d’Installation npm — Chiffres 2026

57
packages compromised
by Phantom Gyp (Jun 2026)
packages compromis
par Phantom Gyp (juin 2026)
Source: Chainguard / Snyk, June 2026
286
malicious versions
in <2 hours (Wave 2)
versions malveillantes
en <2 heures (Vague 2)
Source: Chainguard, June 2026
90+
credential categories
targeted via postinstall
catégories d’identifiants
ciblées via postinstall
Source: Unit42 Palo Alto, June 2026
157B
binding.gyp file size
to bypass --ignore-scripts
taille du fichier binding.gyp
pour bypass --ignore-scripts
Source: StepSecurity / Snyk, June 2026

What to Do Before the npm v12 July 2026 Deadline

Que Faire Avant la Déadline npm v12 de Juillet 2026

Here is a prioritized checklist for teams running Node.js in production or CI/CD:

Voici une liste de contrôle priorisée pour les équipes qui font fonctionner Node.js en production ou en CI/CD :

Frequently Asked Questions

Questions Fréquentes

Does --ignore-scripts protect against Phantom Gyp?

--ignore-scripts protège-t-il contre Phantom Gyp ?

No. --ignore-scripts blocks explicit lifecycle scripts (preinstall, install, postinstall) but does not block the implicit node-gyp rebuild triggered by binding.gyp files. npm v12 is the first npm version to block this implicit rebuild by default.

Non. --ignore-scripts bloque les scripts de cycle de vie explicites (preinstall, install, postinstall) mais ne bloque pas le node-gyp rebuild implicite déclenché par les fichiers binding.gyp. npm v12 est la première version npm à bloquer ce rebuild implicite par défaut.

When exactly does npm v12 ship and what is the migration deadline?

Quand npm v12 sera-t-il livré et quelle est la déadline de migration ?

npm v12 ships in July 2026. The exact date has not been fixed, but npm 11.16.0 is the warning-mode bridge version available now. GitHub's changelog announcement (June 9, 2026) states the breaking changes will be enforced in v12. Teams should complete their allowlist audit and migration before the end of July.

npm v12 est livré en juillet 2026. La date exacte n’a pas été fixée, mais npm 11.16.0 est la version bridge en mode avertissement disponible maintenant. L’annonce changelog de GitHub (9 juin 2026) indique que les changements cassants seront appliqués dans v12. Les équipes devraient compléter leur audit de liste d’autorisation et leur migration avant la fin juillet.

Will npm v12 break packages like sharp, canvas, or bcrypt?

npm v12 cassera-t-il des packages comme sharp, canvas ou bcrypt ?

Yes, unless you either: (a) migrate to a version that ships prebuilt binaries via prebuildify (most major packages are already adding this), or (b) add them to your install-scripts-allowed[] allowlist in .npmrc. Run npm 11.16.0 in warning mode now to see exactly which of your dependencies will break.

Oui, sauf si vous : (a) migrez vers une version qui livre des binaires précompilés via prebuildify (la plupart des packages majeurs l’ajoutent déjà), ou (b) les ajoutez à votre liste d’autorisation install-scripts-allowed[] dans .npmrc. Exécutez npm 11.16.0 en mode avertissement maintenant pour voir exactement quelles dépendances casseront.

Is prebuildify safe from the Phantom Gyp attack?

prebuildify est-il sûr face à l’attaque Phantom Gyp ?

Yes. When you use prebuildify + node-gyp-build, the binding.gyp is only needed at build time on the publisher's machine. The published package contains prebuilt binaries and the node-gyp-build loader — no binding.gyp, no compile-at-install, no Phantom Gyp vector. The gypfile field in package.json should be set to false for published packages.

Oui. Quand vous utilisez prebuildify + node-gyp-build, le binding.gyp n’est nécessaire qu’au moment de la compilation sur la machine de l’éditeur. Le package publié contient des binaires précompilés et le chargeur node-gyp-build — pas de binding.gyp, pas de compilation à l’installation, pas de vecteur Phantom Gyp. Le champ gypfile dans package.json devrait être défini à false pour les packages publiés.

How do I know if a transitive dependency uses install scripts?

Comment savoir si une dépendance transitive utilise des scripts d’installation ?

Check the hasInstallScript field in your package-lock.json — it is set for all dependencies, direct and transitive, that have install scripts. You can also run npm query ':attr(hasInstallScript, [value=true])' in npm v9+. For binding.gyp detection, inspect node_modules after install or use socket.dev analysis before installing.

Vérifiez le champ hasInstallScript dans votre package-lock.json — il est défini pour toutes les dépendances, directes et transitives, qui ont des scripts d’installation. Vous pouvez aussi exécuter npm query ':attr(hasInstallScript, [value=true])' dans npm v9+. Pour la détection de binding.gyp, inspectez node_modules après l’installation ou utilisez l’analyse socket.dev avant l’installation.