Your lockfile is the single most trusted artifact in your Node.js build pipeline. Every security guide tells you to commit it, every CI tool tells you to use npm ci, and every developer assumes it is immutable once merged. In 2026, attackers have internalized this trust — and they are weaponizing it. The lockfile is no longer just a reproducibility tool; it is a primary attack surface for supply chain compromise.

Votre lockfile est l'artefact le plus fiable de votre pipeline Node.js. Tous les guides de sécurité vous disent de le versionner, chaque outil CI vous dit d'utiliser npm ci, et chaque développeur suppose qu'il est immuable une fois mergé. En 2026, les attaquants ont intériorisé cette confiance — et l'exploitent. Le lockfile n'est plus seulement un outil de reproductibilité ; c'est une surface d'attaque majeure pour les compromissions de la chaîne d'approvisionnement.

Miasma (June 2026), TanStack/Mini Shai-Hulud (May 2026), and the CanisterWorm campaign (April 2026) all demonstrated a common thread: once malicious code reaches the registry under a legitimate package name and version, it will be pulled into millions of lockfiles within hours. But the reverse is equally dangerous — an attacker who controls your lockfile does not need to compromise the registry at all. Lockfile injection bypasses every SCA scanner that only checks package.json.

Miasma (juin 2026), TanStack/Mini Shai-Hulud (mai 2026) et la campagne CanisterWorm (avril 2026) ont tous démontré un fil conducteur commun : dès qu'un code malveillant atteint le registry sous un nom et une version légitimes, il se retrouve dans des millions de lockfiles en quelques heures. Mais l'inverse est tout aussi dangereux — un attaquant qui contrôle votre lockfile n'a pas besoin de compromettre le registry. L'injection de lockfile contourne tous les scanners SCA qui ne vérifient que package.json.

This guide covers: how package-lock.json v3 actually works (the resolved and integrity fields), what a lockfile injection attack looks like in a PR diff, what the TanStack postmortem revealed about lockfile trust assumptions, and a concrete CI/CD checklist to harden your pipeline today.

Ce guide couvre : comment fonctionne réellement package-lock.json v3 (les champs resolved et integrity), à quoi ressemble une attaque d'injection de lockfile dans un diff de PR, ce que le post-mortem TanStack a révélé sur les hypothèses de confiance des lockfiles, et une checklist CI/CD concrète pour durcir votre pipeline dès aujourd'hui.

454K+
malicious npm packages in 2025
packages npm malveillants en 2025
Source: Sonatype 2026 SSSC
99%
of open source malware targets npm
des malwares open source ciblent npm
Source: Sonatype 2026 SSSC
79
transitive dependencies per avg npm project
dépendances transitives par projet npm moyen
Source: bastion.tech / npm Threat Landscape 2026
84
malicious TanStack versions in <6 minutes (May 2026)
versions TanStack malveillantes en <6 min (mai 2026)
Source: Snyk / Wiz / TanStack Postmortem

How package-lock.json v3 Works: The resolved and integrity Fields

Comment fonctionne package-lock.json v3 : Les champs resolved et integrity

Understanding what to defend starts with understanding the format. package-lock.json has evolved through three versions since npm v1. Starting with npm v7 (lockfileVersion: 2), the format moved from a "dependencies" flat map to a "packages" map keyed by relative install path. npm v9 dropped the backward-compatible "dependencies" block entirely, producing pure lockfileVersion: 3 files.

Comprendre ce que l'on défend commence par comprendre le format. package-lock.json a évolué à travers trois versions depuis npm v1. À partir de npm v7 (lockfileVersion: 2), le format est passé d'une map plate "dependencies" à une map "packages" indexée par chemin d'installation relatif. npm v9 a supprimé complètement le bloc rétrocompatible "dependencies", produisant des fichiers purs lockfileVersion: 3.

Each entry in the "packages" map has two security-critical fields:

Chaque entrée dans la map "packages" possède deux champs critiques pour la sécurité :

{
  "lockfileVersion": 3,
  "packages": {
    "node_modules/express": {
      "version": "4.19.2",
      "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
      "integrity": "sha512-5T6nhjsT+EOMr7UkCDPILiz4lmjBr/+PTMH4k5CzGxHkBMf8PGxFZbsH5VTG1QFSmT7FNbmLu5neBHAqUF8A==",
      "dependencies": { ... }
    }
  }
}

resolved — The exact URL from which the tarball was fetched. For packages from the public registry, this must always point to https://registry.npmjs.org/. If a PR changes this field to a custom domain, that is a red flag: your next npm ci will pull the tarball from the attacker's server, not npm.

resolved — L'URL exacte depuis laquelle le tarball a été téléchargé. Pour les packages du registry public, cela doit toujours pointer vers https://registry.npmjs.org/. Si une PR modifie ce champ vers un domaine personnalisé, c'est un signal d'alarme : votre prochain npm ci téléchargera le tarball depuis le serveur de l'attaquant, pas npm.

integrity — A Subresource Integrity (SRI) hash in the format sha512-<base64>. This is the SHA-512 hash of the tarball content. When npm downloads a package, it verifies the downloaded tarball against this hash. A mismatch causes npm ci to fail with an integrity error.

integrity — Un hash de type Subresource Integrity (SRI) au format sha512-<base64>. Il s'agit du hash SHA-512 du contenu du tarball. Quand npm télécharge un package, il vérifie le tarball téléchargé par rapport à ce hash. Une discordance fait échouer npm ci avec une erreur d'intégrité.

The critical insight: these two fields are stored together. An attacker who can modify your lockfile can change both — pointing resolved to their malicious tarball and updating integrity to match it. The hash will verify correctly at install time. Your CI/CD will succeed. Your SCA scanner, which reads package.json and the package name/version from the lockfile, will see nothing wrong.

L'insight critique : ces deux champs sont stockés ensemble. Un attaquant qui peut modifier votre lockfile peut changer les deux — pointant resolved vers son tarball malveillant et mettant à jour integrity pour correspondre. Le hash se vérifiera correctement lors de l'installation. Votre CI/CD réussira. Votre scanner SCA, qui lit package.json et le nom/version du package depuis le lockfile, ne verra rien d'anormal.

Lockfile Injection: The Attack Your SCA Scanner Misses

Injection de Lockfile : L'Attaque que Votre Scanner SCA Rate

Lockfile injection is a supply chain attack that targets the lockfile directly — not package.json, not the registry, but the artifact your CI/CD trusts unconditionally. The attack pattern was documented by Pentstark Research in 2025 and has since been observed in the wild as teams gain contributor-level access to open source projects.

L'injection de lockfile est une attaque de chaîne d'approvisionnement qui cible directement le lockfile — pas package.json, pas le registry, mais l'artefact que votre CI/CD approuve sans condition. Le vecteur a été documenté par Pentstark Research en 2025 et a depuis été observé dans la nature alors que des équipes obtiennent un accès contributeur aux projets open source.

The attack works in three steps:

L'attaque fonctionne en trois étapes :

Step 1 — Gain write access to the lockfile. This can happen via a compromised maintainer account (as in Miasma/Red Hat), a malicious PR from an outside contributor, or a compromised CI/CD cache (as in TanStack). The attacker does not need push access to main — they only need to get a lockfile change merged.

Étape 1 — Obtenir l'accès en écriture au lockfile. Cela peut se produire via un compte de mainteneur compromis (comme dans Miasma/Red Hat), une PR malveillante d'un contributeur externe, ou un cache CI/CD compromis (comme dans TanStack). L'attaquant n'a pas besoin d'accès push sur main — il doit seulement faire merger une modification du lockfile.

Step 2 — Modify the lockfile to point to a malicious tarball. The change targets a transitive dependency — one that package.json does not list directly, so automated checks focused on declared dependencies will not flag it. The attacker changes the resolved URL to their controlled server and updates the integrity SHA-512 to match their tarball.

Étape 2 — Modifier le lockfile pour pointer vers un tarball malveillant. La modification cible une dépendance transitive — une que package.json ne liste pas directement, donc les vérifications automatisées centrées sur les dépendances déclarées ne la signaleront pas. L'attaquant change l'URL resolved vers son serveur contrôlé et met à jour le integrity SHA-512 pour correspondre à son tarball.

Step 3 — Wait for CI to run npm ci. The next CI run fetches the malicious tarball, verifies its (matching) integrity hash, installs it into node_modules, and executes its postinstall script. The build succeeds. The artifact is malicious. Every downstream deployment carries the payload.

Étape 3 — Attendre que CI exécute npm ci. Le prochain run CI télécharge le tarball malveillant, vérifie son hash d'intégrité (correspondant), l'installe dans node_modules, et exécute son script postinstall. Le build réussit. L'artefact est malveillant. Chaque déploiement en aval transporte la charge utile.

The most insidious variant changes only one transitive dependency out of hundreds. A typical npm project with 20 direct dependencies has approximately 79 transitive dependencies in its lockfile, sourced from hundreds of individual publishers. A PR titled "chore: update dependencies" that changes one resolved URL in 4,000 lines of lockfile is almost impossible to catch in a human review.

La variante la plus insidieuse ne modifie qu'une seule dépendance transitive sur des centaines. Un projet npm typique avec 20 dépendances directes compte environ 79 dépendances transitives dans son lockfile, provenant de centaines d'éditeurs individuels. Une PR intitulée "chore: update dependencies" qui modifie une URL resolved dans 4 000 lignes de lockfile est presque impossible à détecter dans une revue humaine.

What TanStack and Miasma Revealed About Lockfile Trust

Ce que TanStack et Miasma ont Révélé sur la Confiance dans les Lockfiles

The TanStack supply chain compromise of May 11, 2026 (CVE-2026-45321, CVSS 9.6) demonstrated a different but related failure: attackers can produce lockfile-compatible malicious packages that pass integrity verification because they compromised the build pipeline that generates the integrity hash. In six minutes, between 19:20 and 19:26 UTC, 84 malicious versions across 42 @tanstack/* packages were published to npm. Each had a valid integrity field computed from a legitimately built but poisoned tarball.

La compromission de la chaîne d'approvisionnement TanStack du 11 mai 2026 (CVE-2026-45321, CVSS 9.6) a démontré un échec différent mais lié : les attaquants peuvent produire des packages malveillants compatibles avec le lockfile qui passent la vérification d'intégrité parce qu'ils ont compromis le pipeline de build qui génère le hash d'intégrité. En six minutes, entre 19h20 et 19h26 UTC, 84 versions malveillantes de 42 packages @tanstack/* ont été publiées sur npm. Chacune avait un champ integrity valide calculé à partir d'un tarball légitimement construit mais empoisonné.

The TanStack postmortem identified three compounding factors. First, GitHub Actions cache poisoning allowed malicious build artifacts to survive across the fork/base trust boundary via a pull_request_target workflow that lacked protection. Second, an OIDC token was extracted from the runner's process memory during the poisoned build, giving the attacker npm publish rights. Third, no lockfile diff review was configured to flag unexpected version bumps in @tanstack/* packages.

Le post-mortem TanStack a identifié trois facteurs aggravants. Premièrement, l'empoisonnement du cache GitHub Actions a permis à des artefacts de build malveillants de survivre à travers la frontière de confiance fork/base via un workflow pull_request_target sans protection. Deuxièmement, un token OIDC a été extrait de la mémoire du processus du runner pendant le build empoisonné, donnant à l'attaquant les droits de publication npm. Troisièmement, aucune revue de diff de lockfile n'était configurée pour signaler les bumps de version inattendus dans les packages @tanstack/*.

The recommended mitigation from the postmortem — pin the integrity field for all @tanstack/* packages in package-lock.json — illustrates the defensive potential of the integrity system when used proactively. If teams had pinned known-good integrity hashes and configured CI to reject any change to those hashes without explicit approval, the malicious versions would have been rejected at install time.

La mesure d'atténuation recommandée dans le post-mortem — épingler le champ integrity pour tous les packages @tanstack/* dans package-lock.json — illustre le potentiel défensif du système d'intégrité lorsqu'il est utilisé de manière proactive. Si les équipes avaient épinglé des hashes d'intégrité connus bons et configuré CI pour rejeter tout changement à ces hashes sans approbation explicite, les versions malveillantes auraient été rejetées à l'installation.

Miasma (June 1–9, 2026) demonstrated yet another vector: compromising the maintainer account to publish directly under a legitimate namespace (@redhat-cloud-services). In this case, the integrity fields in consumer lockfiles were technically valid — they matched the published (malicious) tarball. The lockfile was not injected; it was correctly updated to reflect the compromised registry state. This highlights that lockfile security alone is insufficient: registry-level detection (npm audit signatures, OpenSSF Package Analysis) must run alongside lockfile verification.

Miasma (1-9 juin 2026) a démontré encore un autre vecteur : compromettre le compte mainteneur pour publier directement sous un namespace légitime (@redhat-cloud-services). Dans ce cas, les champs integrity dans les lockfiles consommateurs étaient techniquement valides — ils correspondaient au tarball publié (malveillant). Le lockfile n'a pas été injecté ; il a été correctement mis à jour pour refléter l'état compromis du registry. Cela souligne que la sécurité du lockfile seule est insuffisante : la détection au niveau du registry (npm audit signatures, OpenSSF Package Analysis) doit s'exécuter parallèlement à la vérification du lockfile.

npm ci vs npm install: The Security Distinction That Matters

npm ci vs npm install : La Distinction de Sécurité Qui Compte

The choice between npm ci and npm install is not just a performance optimization — it is a security decision.

Le choix entre npm ci et npm install n'est pas seulement une optimisation de performance — c'est une décision de sécurité.

# npm install — DANGEROUS in CI
# Updates lockfile if package.json has changed
# May pull newer versions of transitive deps
# Silently modifies package-lock.json
npm install

# npm ci — REQUIRED in CI
# Reads ONLY from the lockfile
# Fails if package.json and lockfile are out of sync
# Never modifies the lockfile
# Deletes node_modules first (clean install)
npm ci

npm install in a CI environment is dangerous for two reasons. First, it can silently upgrade transitive dependencies to versions not pinned in your lockfile, introducing unreviewed code. Second, if your CI environment modifies the lockfile (via npm install) and those changes are cached or committed, an attacker who can influence what gets installed gains a path to lockfile tampering without touching the repository directly.

npm install dans un environnement CI est dangereux pour deux raisons. Premièrement, il peut mettre à jour silencieusement les dépendances transitives vers des versions non épinglées dans votre lockfile, introduisant du code non examiné. Deuxièmement, si votre environnement CI modifie le lockfile (via npm install) et que ces changements sont mis en cache ou committés, un attaquant qui peut influencer ce qui est installé obtient un chemin vers la manipulation du lockfile sans toucher directement le repository.

The TanStack attack exploited exactly this pattern: a pull_request_target workflow ran npm install (or equivalent build steps) using a poisoned cache, which modified build outputs. If npm ci had been used with a read-only lockfile verification step, the cache poisoning would have been caught as an integrity mismatch between the cached node_modules and the committed lockfile state.

L'attaque TanStack a exploité exactement ce schéma : un workflow pull_request_target a exécuté npm install (ou des étapes de build équivalentes) en utilisant un cache empoisonné, qui a modifié les sorties de build. Si npm ci avait été utilisé avec une étape de vérification de lockfile en lecture seule, l'empoisonnement du cache aurait été détecté comme une discordance d'intégrité entre le node_modules mis en cache et l'état du lockfile commité.

Detecting Tampered Lockfiles: Tools and Techniques

Détecter les Lockfiles Falsifiés : Outils et Techniques

Several tools and built-in npm features can catch lockfile tampering before it reaches production.

Plusieurs outils et fonctionnalités intégrées npm peuvent détecter la manipulation de lockfile avant qu'elle n'atteigne la production.

1. npm audit signatures — Introduced in npm CLI v9.5 (2023), this command verifies the registry signature of every installed package against the npm public key. It detects packages that were not published through the official npm registry signing process, which would catch Miasma-style attacks where the malicious tarball bypassed the registry's signing pipeline. Run this in CI after npm ci:

1. npm audit signatures — Introduit dans npm CLI v9.5 (2023), cette commande vérifie la signature du registry de chaque package installé par rapport à la clé publique npm. Elle détecte les packages qui n'ont pas été publiés via le processus de signature officiel du registry npm, ce qui permettrait de détecter les attaques de style Miasma où le tarball malveillant a contourné le pipeline de signature du registry. Exécutez-le dans CI après npm ci :

npm ci
npm audit signatures
# Audits 847 packages...
# all packages have valid registry signatures

2. lockcheck CLI — An open-source tool (github.com/DhanushNehru/lockcheck) that scans package-lock.json for suspicious patterns. On first run, it creates a baseline snapshot. On subsequent runs, it diffs against the baseline and flags any changes to resolved URLs, integrity hashes, or new packages entries. Integrate it as a pre-commit hook and CI check:

2. lockcheck CLI — Un outil open source (github.com/DhanushNehru/lockcheck) qui scanne package-lock.json à la recherche de motifs suspects. Lors du premier run, il crée un snapshot de référence. Lors des runs suivants, il compare avec la référence et signale tout changement dans les URLs resolved, les hashes integrity, ou les nouvelles entrées packages. Intégrez-le comme hook pre-commit et vérification CI :

npx lockcheck scan
# Scanning package-lock.json...
# Baseline exists — diffing against snapshot
# ⚠ CHANGED resolved URL: node_modules/semver
#   Before: https://registry.npmjs.org/semver/-/semver-7.6.0.tgz
#   After:  https://evil.example.com/semver-7.6.0.tgz
# ⚠ CHANGED integrity: node_modules/semver
#   sha512 hash changed

3. Verify the resolved domain allowlist in CI — A simple shell check in your CI pipeline that rejects any resolved URL pointing outside your expected registries:

3. Vérifier la liste d'autorisation de domaine resolved dans CI — Une vérification shell simple dans votre pipeline CI qui rejette toute URL resolved pointant en dehors de vos registries attendus :

# GitHub Actions step: validate lockfile resolved domains
- name: Validate lockfile resolved URLs
  run: |
    UNEXPECTED=$(node -e "
      const lock = require('./package-lock.json');
      const allowed = ['registry.npmjs.org'];
      const bad = Object.entries(lock.packages || {})
        .filter(([,v]) => v.resolved)
        .filter(([,v]) => !allowed.some(h => v.resolved.includes(h)))
        .map(([k,v]) => k + ' -> ' + v.resolved);
      console.log(bad.join('\n'));
    ")
    if [ -n "$UNEXPECTED" ]; then
      echo "ERROR: Unexpected resolved domains in lockfile:"
      echo "$UNEXPECTED"
      exit 1
    fi

4. Verify the integrity hash manually with npm pack — For critical packages, you can verify that the published tarball matches the expected integrity hash:

4. Vérifier le hash integrity manuellement avec npm pack — Pour les packages critiques, vous pouvez vérifier que le tarball publié correspond au hash d'intégrité attendu :

# Extract expected integrity from lockfile
EXPECTED=$(node -p "require('./package-lock.json').packages['node_modules/express'].integrity")

# Download and hash the tarball from npm
ACTUAL=$(npm pack express@4.19.2 --dry-run --json 2>/dev/null | node -p "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'))[0].integrity")

if [ "$EXPECTED" != "$ACTUAL" ]; then
  echo "INTEGRITY MISMATCH for express@4.19.2"
  echo "Lockfile: $EXPECTED"
  echo "Registry: $ACTUAL"
  exit 1
fi

Lockfile Diff Reviews in Pull Requests: What to Look For

Revues de Diff de Lockfile dans les Pull Requests : Ce qu'il Faut Surveiller

Any PR that modifies package-lock.json requires a security review, not just a cursory glance. Because lockfiles can be thousands of lines long, reviewers naturally skim or collapse the diff. Attackers count on this. Here are the specific patterns to flag:

Toute PR qui modifie package-lock.json nécessite une revue de sécurité, pas seulement un coup d'œil rapide. Parce que les lockfiles peuvent comporter des milliers de lignes, les reviewers naturellement font défiler ou effondrent le diff. Les attaquants comptent là-dessus. Voici les modèles spécifiques à signaler :

Red flag #1 — Changed resolved URL for an existing package. A legitimate dependency update changes the version string in both the "packages" key and the resolved URL. A lockfile injection changes only the resolved URL domain while keeping the version the same. Look for resolved changes where the version number did not change.

Signal d'alarme n°1 — URL resolved changée pour un package existant. Une mise à jour légitime de dépendance change la chaîne de version à la fois dans la clé "packages" et dans l'URL resolved. Une injection de lockfile change uniquement le domaine de l'URL resolved tout en gardant la version identique. Cherchez les changements resolved où le numéro de version n'a pas changé.

Red flag #2 — New packages entry with no corresponding package.json change. If a new transitive dependency appears in the lockfile but no direct dependency was added or upgraded in package.json, investigate why. Legitimate transitive additions always trace back to a direct dependency change.

Signal d'alarme n°2 — Nouvelle entrée packages sans changement correspondant dans package.json. Si une nouvelle dépendance transitive apparaît dans le lockfile mais qu'aucune dépendance directe n'a été ajoutée ou mise à jour dans package.json, investiguez pourquoi. Les additions transitives légitimes se tracent toujours vers un changement de dépendance directe.

Red flag #3 — integrity change without a version change. A correct dependency upgrade changes both the version and the integrity hash. An integrity-only change on a fixed version means the tarball content changed without a version bump — a clear indicator of tampering (or a mutable tag, which is itself a security issue).

Signal d'alarme n°3 — Changement integrity sans changement de version. Une mise à jour correcte de dépendance change à la fois la version et le hash d'intégrité. Un changement uniquement d'intégrité sur une version fixe signifie que le contenu du tarball a changé sans bump de version — un indicateur clair de manipulation (ou un tag mutable, qui est lui-même un problème de sécurité).

Red flag #4 — Lockfile change in a PR that describes unrelated changes. A PR labeled "fix: button alignment" that also modifies package-lock.json should raise questions. Require contributors to explain every lockfile change in their PR description.

Signal d'alarme n°4 — Changement de lockfile dans une PR qui décrit des changements sans rapport. Une PR intitulée "fix: alignement bouton" qui modifie aussi package-lock.json devrait soulever des questions. Exigez que les contributeurs expliquent chaque changement de lockfile dans la description de leur PR.

Automate these checks using CODEOWNERS to require security team review on lockfile changes:

Automatisez ces vérifications en utilisant CODEOWNERS pour exiger la revue de l'équipe sécurité sur les changements de lockfile :

# .github/CODEOWNERS
package-lock.json @your-org/security-team
yarn.lock         @your-org/security-team
pnpm-lock.yaml    @your-org/security-team

The Dependency Tree Sprawl Problem

Le Problème de l'Explosion de l'Arbre de Dépendances

The average npm project with a handful of direct dependencies pulls in approximately 79 transitive dependencies from hundreds of individual publishers. A typical production application has over 1,000 entries in its lockfile. This is the fundamental challenge: you are not just trusting the packages you install, you are trusting their dependencies, their dependencies' dependencies, and so on, several levels deep.

Le projet npm moyen avec quelques dépendances directes tire environ 79 dépendances transitives de centaines d'éditeurs individuels. Une application de production typique a plus de 1 000 entrées dans son lockfile. C'est le défi fondamental : vous ne faites pas confiance qu'aux packages que vous installez, mais aussi à leurs dépendances, aux dépendances de leurs dépendances, et ainsi de suite, sur plusieurs niveaux de profondeur.

Tools to understand and control your dependency tree:

Outils pour comprendre et contrôler votre arbre de dépendances :

# Visualize full dependency tree
npm ls --all

# Find who introduced a specific transitive dep
npm why semver

# Show only production deps (exclude devDependencies)
npm ls --omit=dev

# Count total lockfile entries
node -e "const l=require('./package-lock.json'); console.log(Object.keys(l.packages||{}).length, 'packages in lockfile')"

# List packages with install scripts (high-risk)
node -e "
const l=require('./package-lock.json');
Object.entries(l.packages||{})
  .filter(([k,v]) => v.scripts?.install || v.scripts?.postinstall || v.hasInstallScript)
  .forEach(([k]) => console.log('has install script:', k));
"

The hasInstallScript field in package-lock.json v3 is a particularly important signal. npm sets this field to true for any package that has a lifecycle script (install, postinstall, preinstall). A new transitive dependency appearing in your lockfile with hasInstallScript: true should always trigger a review — it means the package will execute arbitrary code during installation.

Le champ hasInstallScript dans package-lock.json v3 est un signal particulièrement important. npm définit ce champ à true pour tout package ayant un script de cycle de vie (install, postinstall, preinstall). Une nouvelle dépendance transitive apparaissant dans votre lockfile avec hasInstallScript: true devrait toujours déclencher une revue — cela signifie que le package exécutera du code arbitraire lors de l'installation.

CI/CD Lockfile Hardening Checklist for 2026

Checklist de Durcissement Lockfile CI/CD pour 2026

Apply this checklist to every Node.js project in your organization:

Appliquez cette checklist à chaque projet Node.js de votre organisation :

Repository configuration:

Configuration du repository :

# .github/CODEOWNERS — require security review on lockfile changes
package-lock.json @security-team

# Branch protection: require approval from CODEOWNERS
# Settings > Branches > Require review from code owners

CI pipeline steps (GitHub Actions):

Étapes du pipeline CI (GitHub Actions) :

jobs:
  lockfile-security:
    runs-on: ubuntu-latest
    steps:
      # 1. Use npm ci, never npm install
      - name: Install dependencies
        run: npm ci

      # 2. Verify registry signatures (npm v9+)
      - name: Verify package signatures
        run: npm audit signatures

      # 3. Standard vulnerability audit
      - name: Audit vulnerabilities
        run: npm audit --audit-level=high

      # 4. Detect packages with install scripts
      - name: Check for unexpected install scripts
        run: |
          node -e "
            const l = require('./package-lock.json');
            const scripted = Object.entries(l.packages || {})
              .filter(([k,v]) => v.hasInstallScript && k !== '')
              .map(([k]) => k);
            if (scripted.length > 0) {
              console.log('Packages with install scripts:', scripted.join(', '));
            }
          "

      # 5. Validate resolved URLs
      - name: Validate lockfile resolved domains
        run: |
          node -e "
            const lock = require('./package-lock.json');
            const allowed = ['registry.npmjs.org'];
            const bad = Object.entries(lock.packages || {})
              .filter(([,v]) => v.resolved)
              .filter(([,v]) => !allowed.some(h => v.resolved.includes(h)))
              .map(([k,v]) => k + ' -> ' + v.resolved);
            if (bad.length > 0) {
              console.error('LOCKFILE ALERT: Unexpected resolved domains:\n' + bad.join('\n'));
              process.exit(1);
            }
          "

      # 6. Detect lockfile modifications (lockfile must be committed, never auto-updated)
      - name: Ensure lockfile was not modified by CI
        run: git diff --exit-code package-lock.json

Package manager configuration (.npmrc):

Configuration du gestionnaire de packages (.npmrc) :

# .npmrc — project-level configuration
ignore-scripts=false        # Don't blindly ignore scripts — audit them instead
audit=true                  # Run audit on install
save-exact=true             # Pin exact versions in package.json
package-lock=true           # Always generate/update lockfile (default)
ci=true                     # Equivalent to npm ci behavior in CI environments
# For private registries, configure explicitly:
# registry=https://your-private-registry.example.com/

Note on --ignore-scripts: while disabling lifecycle scripts seems like a silver bullet, the PackageGate disclosure (January 2026) identified six zero-day vulnerabilities in npm, pnpm, vlt, and Bun that allowed code execution even with --ignore-scripts enabled, via Git-based dependency configuration injection. Do not rely on --ignore-scripts as a primary defense — use it as one layer in a defense-in-depth strategy.

Note sur --ignore-scripts : bien que la désactivation des scripts de cycle de vie semble être une solution miracle, la divulgation PackageGate (janvier 2026) a identifié six vulnérabilités zero-day dans npm, pnpm, vlt et Bun qui permettaient l'exécution de code même avec --ignore-scripts activé, via l'injection de configuration de dépendances Git. Ne comptez pas sur --ignore-scripts comme défense primaire — utilisez-le comme une couche dans une stratégie de défense en profondeur.

Beyond npm: yarn.lock and pnpm-lock.yaml Security Considerations

Au-delà de npm : Considérations de Sécurité pour yarn.lock et pnpm-lock.yaml

The same principles apply to Yarn and pnpm lockfiles, with slightly different formats and tools.

Les mêmes principes s'appliquent aux lockfiles Yarn et pnpm, avec des formats et des outils légèrement différents.

yarn.lock (Classic Yarn v1 / Yarn Berry v2+) — Yarn Berry introduced enableHardenedMode in Yarn 4.x. When enabled, this mode requires all packages to have a __metadata.checksum that is verified on install, equivalent to npm's integrity field but enforced at the Yarn PnP layer. Enable it in your .yarnrc.yml:

yarn.lock (Yarn Classic v1 / Yarn Berry v2+) — Yarn Berry a introduit enableHardenedMode dans Yarn 4.x. Lorsqu'il est activé, ce mode exige que tous les packages aient un __metadata.checksum qui est vérifié à l'installation, équivalent au champ integrity de npm mais appliqué au niveau de la couche PnP de Yarn. Activez-le dans votre .yarnrc.yml :

# .yarnrc.yml (Yarn Berry)
enableHardenedMode: true
# This enforces checksum verification and prevents
# lockfile injection from resolving to unexpected tarballs

pnpm-lock.yaml — pnpm lockfiles include an integrity field per package, similar to npm. Use pnpm install --frozen-lockfile in CI (equivalent to npm ci) to prevent lockfile modifications. The PackageGate disclosure also affected pnpm, so ensure you are running pnpm v9+ with the latest security patches.

pnpm-lock.yaml — Les lockfiles pnpm incluent un champ integrity par package, similaire à npm. Utilisez pnpm install --frozen-lockfile dans CI (équivalent à npm ci) pour éviter les modifications du lockfile. La divulgation PackageGate a également affecté pnpm, donc assurez-vous d'utiliser pnpm v9+ avec les derniers patches de sécurité.

Frequently Asked Questions

Questions Fréquentes

Is npm ci enough to protect against lockfile injection?

npm ci est-il suffisant pour se protéger contre l'injection de lockfile ?

npm ci enforces the lockfile and verifies integrity hashes against the downloaded tarballs. However, if the attacker has already modified your committed lockfile with a matching resolved URL and integrity hash for a malicious tarball, npm ci will happily install the malicious package — the hash matches. You need lockfile diff reviews in PRs and resolved URL validation checks in CI to catch the tampering before it reaches the lockfile.

npm ci applique le lockfile et vérifie les hashes d'intégrité par rapport aux tarballs téléchargés. Cependant, si l'attaquant a déjà modifié votre lockfile commité avec une URL resolved et un hash integrity correspondants pour un tarball malveillant, npm ci installera joyeusement le package malveillant — le hash correspond. Vous avez besoin de revues de diff de lockfile dans les PR et de vérifications de validation d'URL résolue dans CI pour détecter la manipulation avant qu'elle n'atteigne le lockfile.

What is the difference between lockfile injection and the Miasma/TanStack attacks?

Quelle est la différence entre l'injection de lockfile et les attaques Miasma/TanStack ?

Lockfile injection modifies your project's lockfile to point to a malicious tarball without compromising the registry. Miasma and TanStack compromised the registry itself (by taking over maintainer credentials or poisoning the build pipeline) so that legitimate-looking package versions contained malicious code. In those cases, your lockfile was correct — it faithfully recorded the integrity of the (malicious) published tarball. Registry-level attacks require npm audit signatures and behavioral monitoring, not just lockfile integrity checks.

L'injection de lockfile modifie le lockfile de votre projet pour pointer vers un tarball malveillant sans compromettre le registry. Miasma et TanStack ont compromis le registry lui-même (en prenant le contrôle des credentials de mainteneur ou en empoisonnant le pipeline de build) de sorte que des versions de packages légitimes en apparence contenaient du code malveillant. Dans ces cas, votre lockfile était correct — il enregistrait fidèlement l'intégrité du tarball publié (malveillant). Les attaques au niveau du registry nécessitent npm audit signatures et une surveillance comportementale, pas seulement des vérifications d'intégrité de lockfile.

Should I commit my lockfile to the repository?

Dois-je commiter mon lockfile dans le repository ?

Yes, always. An uncommitted lockfile provides no security guarantee because every npm install in CI will potentially resolve different versions. Commit the lockfile, use npm ci in CI, and use the PR review process to audit changes. The lockfile's value as a security control depends entirely on it being version-controlled and treated as a first-class artifact.

Oui, toujours. Un lockfile non commité ne fournit aucune garantie de sécurité car chaque npm install dans CI peut potentiellement résoudre des versions différentes. Commitez le lockfile, utilisez npm ci dans CI, et utilisez le processus de revue de PR pour auditer les changements. La valeur du lockfile comme contrôle de sécurité dépend entièrement du fait qu'il soit versionné et traité comme un artefact de première classe.

Does lockfileVersion: 3 provide better security than v1 or v2?

lockfileVersion: 3 offre-t-il une meilleure sécurité que v1 ou v2 ?

The security properties (integrity hashes, resolved URLs) are the same across v1, v2, and v3. The v3 format change (from "dependencies" to "packages" keyed by path) makes it slightly easier to parse and validate programmatically, and the removal of the redundant v1 block reduces the attack surface (an attacker could previously hide a malicious entry in the v1 "dependencies" block while the v2 "packages" block appeared clean). Upgrade to npm v9+ (v3 lockfiles) if you have not already.

Les propriétés de sécurité (hashes d'intégrité, URLs résolues) sont identiques dans v1, v2 et v3. Le changement de format v3 (de "dependencies" à "packages" indexé par chemin) facilite légèrement l'analyse et la validation programmatique, et la suppression du bloc v1 redondant réduit la surface d'attaque (un attaquant pouvait précédemment cacher une entrée malveillante dans le bloc "dependencies" v1 tandis que le bloc "packages" v2 semblait propre). Mettez à niveau vers npm v9+ (lockfiles v3) si ce n'est pas déjà fait.

How does CVE OptiBot help with lockfile security?

Comment CVE OptiBot aide-t-il avec la sécurité du lockfile ?

CVE OptiBot scans your lockfiles daily against the OSV.dev database to detect vulnerable package versions — including transitive dependencies that your package.json never lists directly. When a new CVE is disclosed for a package in your lockfile (like the TanStack CVE-2026-45321 or the Node.js CVE-2026-21637), CVE OptiBot alerts you with the affected version, the CVE severity, and the recommended fix. This covers the registry-compromise attack vector that lockfile integrity checks alone cannot detect.

CVE OptiBot scanne vos lockfiles quotidiennement par rapport à la base de données OSV.dev pour détecter les versions de packages vulnérables — y compris les dépendances transitives que votre package.json ne liste jamais directement. Lorsqu'une nouvelle CVE est divulguée pour un package dans votre lockfile (comme la CVE-2026-45321 TanStack ou la CVE-2026-21637 Node.js), CVE OptiBot vous alerte avec la version affectée, la sévérité de la CVE et le correctif recommandé. Cela couvre le vecteur d'attaque de compromission du registry que les seules vérifications d'intégrité du lockfile ne peuvent pas détecter.

Should I use npm shrinkwrap instead of package-lock.json?

Dois-je utiliser npm shrinkwrap plutôt que package-lock.json ?

npm shrinkwrap produces an npm-shrinkwrap.json file with the same format as package-lock.json v3, but it is published with the package (included in the npm tarball). For applications (not libraries), there is no security benefit to using shrinkwrap over lockfile — use package-lock.json. For published packages where you want to enforce exact transitive versions for consumers, shrinkwrap is appropriate, but be aware it forces your dependency versions onto consumers, which can cause conflicts.

npm shrinkwrap produit un fichier npm-shrinkwrap.json avec le même format que package-lock.json v3, mais il est publié avec le package (inclus dans le tarball npm). Pour les applications (pas les bibliothèques), il n'y a aucun avantage de sécurité à utiliser shrinkwrap plutôt que lockfile — utilisez package-lock.json. Pour les packages publiés où vous souhaitez imposer des versions transitives exactes aux consommateurs, shrinkwrap est approprié, mais sachez qu'il force vos versions de dépendances sur les consommateurs, ce qui peut provoquer des conflits.

Monitor Every Package in Your Lockfile — Not Just the Ones in package.json

Surveillez Chaque Package de Votre Lockfile — Pas Seulement Ceux dans package.json

CVE OptiBot scans your complete lockfile daily against OSV.dev — all 79+ transitive dependencies, not just your direct ones. When a new CVE is disclosed for any package in your dependency tree, you get an instant alert with severity, impacted version, and fix. Free for open source projects.

CVE OptiBot scanne votre lockfile complet quotidiennement via OSV.dev — toutes les 79+ dépendances transitives, pas seulement vos dépendances directes. Quand une nouvelle CVE est divulguée pour un package de votre arbre de dépendances, vous recevez une alerte immédiate avec la sévérité, la version impactée et le correctif. Gratuit pour les projets open source.

Start free lockfile monitoring Démarrer le monitoring gratuit de lockfile