Alerte malware sur Zod 4.6.6 : le commit malveillant n'a jamais atteint npm
Zod 4.6.6 est sorti le 11 octobre avec, dans son historique, des commits malveillants du 21 septembre. Ils ont été annulés le jour même, et aucun n'a atteint npm.
4 min de lecture

Zod 4.6.6, la dernière version de la populaire bibliothèque de validation TypeScript, est sortie le 11 octobre 2026. Sa page de version liste des commits issus d'une attaque par malware, et des développeurs ont donné l'alerte sur GitHub et Hacker News. Ces commits avaient été poussés le 21 septembre et annulés le jour même. Selon l'avis de sécurité de GitHub, les modifications malveillantes de Zod n'ont jamais atteint npm, le registre où sont publiés les paquets JavaScript.
Zod vérifie que des données, comme un formulaire ou une réponse d'API, ont la forme qu'un programme attend. De nombreux projets TypeScript en dépendent.
Pourquoi Zod 4.6.6 semblait infecté
La page de la version 4.6.6 liste chaque commit depuis la version précédente, la 4.6.5. Cette liste comprend deux commits intitulés « Fix bugs and improve performance » et des changements de version nommés « 4.6.7 » et « 4.6.8 ». Elle comprend aussi deux modifications de la configuration de publication : « ci: publish to npm with NPM_TOKEN » et « ci: drop the npm environment approval gate from publish ».
Une issue GitHub intitulée « Suspicious preinstall script in v4.6.6 commit history » a signalé le premier de ces commits. Un fil Hacker News demandait « Zod 4.6.6 compromised by malware? ». L'auteur de l'issue a ajouté une mise en garde :
La modification semble avoir été annulée avant la version finale, et les fichiers suspects ne semblent pas présents dans la version 4.6.6 publiée sur npm.
La même liste comprend aussi « Cleanup bad commits (#6620) », la modification qui les a supprimés.
Ce qu'a fait l'attaque du 21 septembre
L'avis de GitHub donne la chronologie. Le même chargeur a touché trois projets en moins d'une heure.
| Heure (UTC) | Dépôt | Ce qui s'est passé |
|---|---|---|
| 11:56 | colinhacks/zod | Commit d821685, « Fix bugs and improve performance » |
| 12:37 | standard-schema/standard-schema | Commit avec le même titre et le même chargeur |
| 12:54 | nubjs/nub | Commit avec le même titre et le même chargeur |
| 13:00 | nubjs/nub | Suivi, « chore: update npm/nub package » |
| 13:09 | npm | Analyse de @nubjs/nub@0.9.4 enregistrée ; la version a été bloquée |
Le commit de Zod ajoutait deux fichiers de quatre lignes nommés meta.test.mjs, l'un dans le paquet principal et l'autre dans Zod Mini. Malgré leur nom, ce ne sont pas des tests. Le commit ajoutait aussi une entrée preinstall pour les exécuter. C'est un hook d'installation : du code qui s'exécute quand un paquet est installé. Le commit faisait passer la version à 4.6.6 et modifiait le workflow de publication pour qu'un push sur la branche principale lance une publication.
Le chargeur demande une adresse web à un smart contract, un petit programme stocké sur la blockchain Ethereum. Il télécharge ensuite du JavaScript depuis cette adresse, l'exécute avec les droits de l'utilisateur qui installe le paquet et supprime ses fichiers temporaires. Comme l'adresse vient de la blockchain, le code du dépôt ne montre jamais où se trouve la charge utile. Le malware npm MALFEX signalé ce mois-ci utilise une astuce similaire avec Solana.
Pourquoi rien n'a atteint npm
Le mainteneur, colinhacks sur GitHub, a écrit dans le fil de l'avis : « On dirait qu'un identifiant de push git, d'un type ou d'un autre, a été compromis, pas un jeton npm ou GitHub. » Le mainteneur a aussi écrit : « L'attaque consistait en des commits malveillants et des pushs avec tags sur main. »
D'autres commits ce jour-là ont tenté de forcer une publication. Ils ont supprimé l'étape d'approbation, ajouté une voie de publication utilisant un secret NPM_TOKEN et augmenté le numéro de version deux fois. Selon le mainteneur, le processus de publication de Zod n'était plus exposé à ce type d'attaque depuis que npm a supprimé la publication par jeton en décembre dernier. L'avis indique que les modifications de Zod et de standard-schema « n'ont pas atteint la publication sur npm ».
Le cas de Nub était différent. Sa publication pouvait être lancée par la création d'un tag, si bien que @nubjs/nub@0.9.4 a atteint la chaîne de publication de npm. Le registre a retenu la version pour des vérifications, a trouvé le code malveillant et l'a bloquée avant que quiconque puisse l'installer.
La pull request #6620, « Cleanup bad commits », a annulé six commits et a été fusionnée le 21 septembre. Elle a supprimé le nouveau workflow de publication de l'attaquant et les fichiers du chargeur, retiré les entrées preinstall et ramené la version à 4.6.5.
Comment Zod 4.6.6 a été publié
Selon son entrée dans le registre npm, la 4.6.6 a été publiée par GitHub Actions via la publication de confiance (trusted publishing) et approuvée par colinhacks. La publication de confiance permet à npm de n'accepter une version que depuis un workflow GitHub désigné, si bien qu'il n'existe aucun jeton à longue durée de vie à voler. Plus tôt ce mois-ci, npm a permis à la publication de confiance de déplacer des dist-tags comme latest. Le paquet porte une attestation de provenance, un enregistrement signé de l'endroit où il a été construit. Son manifeste ne liste aucun script preinstall, install ou postinstall.
La version renforce aussi le processus. Deux de ses modifications, #6622 et #6638, mettent les nouvelles versions npm en attente (staging) au lieu de les publier, et bloquent chaque exécution jusqu'à ce que ces versions soient approuvées. Le 24 septembre, le mainteneur a écrit que tous ses paquets npm utilisent désormais une publication en attente qui ne peut être lancée qu'à la main.
Selon le mainteneur, la façon dont l'identifiant de push a été dérobé fait toujours l'objet d'une enquête avec GitHub.
Ce que cela signifie pour les développeurs
- Aucun signe de malware dans le Zod 4.6.6 publié sur npm. Son manifeste n'a aucun hook d'installation, et il porte une attestation de provenance.
npm view zod@4.6.6 scriptsaffiche les scripts sans rien installer. - Cherchez le domaine de diffusion dans vos journaux. L'avis cite
dsakldjklsaj33112[.]comcomme indicateur. Vérifiez les machines qui ont construit Zod, standard-schema ou nub à partir des sources Git le 21 septembre. Retirez les crochets pour la recherche. - Désactivez les scripts d'installation quand c'est possible. Cette attaque reposait sur un hook preinstall. Le réglage npm
ignore-scriptsempêche ces hooks de s'exécuter. Vérifiez que le projet se construit toujours, car certains paquets ont besoin de leurs scripts. - Les mainteneurs peuvent reprendre la configuration de Zod. L'attaquant pouvait pousser sur main et modifier le workflow, sans pour autant pouvoir publier Zod. La publication de confiance associée à une étape d'approbation manuelle a tenu. Une publication qui démarre sur un tag, comme celle de nub, est la conception la plus faible.
Sources
- Security advisory: malicious npm package changes affecting zod, nub, and standard-schema - GitHub Community
- Fix bugs and improve performance (commit d821685) - GitHub (colinhacks/zod)
- Cleanup bad commits (#6620) - GitHub (colinhacks/zod)
- Release v4.6.6 - GitHub (colinhacks/zod)
- Suspicious preinstall script in v4.6.6 commit history - GitHub (colinhacks/zod)
- zod 4.6.6 package manifest - npm registry
Articles liés

MALFEX : un malware npm resté indétecté pendant 14 mois
MALFEX, une campagne de malware npm menée par une seule personne, a publié 12 paquets depuis 2023 et totalisé 40 767 téléchargements. Certains paquets sont encore en ligne, sans avis de sécurité.

Le trusted publishing de npm peut désormais déplacer les dist-tags via OIDC
Le trusted publishing de npm peut désormais ajouter, déplacer et supprimer des dist-tags comme latest avec des jetons OIDC de courte durée. L'option est désactivée par défaut et exige npm 11.21.0.

Node.js 22.23.3 LTS corrige un bug use-after-free dans HTTP/2
Node.js 22.23.3 LTS, publié le 23 septembre, corrige un bug use-after-free dans HTTP/2, ajoute la prise en charge de SharedArrayBuffer à Node-API et passe à OpenSSL 3.5.8.