Dans les coulisses de la compromission de keyv sur npm : malware preinstall, provenance fiable et hooks d’IDE
4 août 2026
0 minutes de lectureLe 4 août 2026, des attaquants ont compromis le processus de publication de keyv et de packages npm associés. Les versions malveillantes ajoutent un hook preinstall qui exécute un chargeur obfusqué avant le démarrage du code de l’application, puis lance une charge utile de deuxième étape beaucoup plus volumineuse.
Il s’agit d’un incident actif touchant la chaîne logistique logicielle, et non d’une preuve de concept. Snyk Security Research a téléchargé et comparé de façon indépendante les archives publiées sans les installer, répertorié tous les packages renvoyés par npm pour le mainteneur jaredwray et identifié 11 versions malveillantes contenant les mêmes deux fichiers de charge utile. À 11:16 UTC, huit de ces versions portaient encore le tag latest.
En bref
Incident : Code malveillant intégré à des packages npm légitimes
Package principal :
keyv@6.0.0Versions touchées identifiées par Snyk : 11 parmi
keyv, les packages liés àcacheableetectoExécution :
"preinstall": "node setup.mjs"Charge utile :
setup.mjscharge une deuxième étape de 727 680 octets nomméeMath_Symbol.jsÉtat observé à 11:16 UTC : Huit versions malveillantes portaient toujours le tag
latest. Trois avaient été supprimées.Avis :
SNYK-JS-KEYV-18515941CVE/GHSA : Aucun CVE ni GHSA n’avait été attribué au moment de l’analyse
Gravité opérationnelle : Critique, car l’installation permet l’exécution de code contrôlé par l’attaquant avec les privilèges du développeur ou de l’exécuteur CI
Remédiation : Épinglez une version ou revenez à une version antérieure connue comme saine. Aucun successeur sain de
keyv@6.0.0n’avait été publié.Action immédiate : N’installez pas les versions touchées. Si l’une d’elles a été exécutée, isolez l’hôte, recherchez et désactivez les mécanismes de persistance, puis faites tourner les identifiants exposés depuis un système sain.
Gravité et statut de l’avis
Snyk a publié SNYK-JS-KEYV-18515941 pendant la rédaction de cet article. L’avis classe keyv@6.0.0 comme du code malveillant intégré, selon CWE-506.
Aucun CVE ni avis GitHub n’avait été attribué pendant nos recherches. L’installation concernée exécute du code contrôlé par l’attaquant sans privilèges supplémentaires ni interaction avec l’application, en utilisant les autorisations et identifiants accessibles au développeur ou au processus CI.
Ce que Snyk a confirmé de manière indépendante
Nous avons interrogé le point de terminaison de recherche du registre npm pour les packages maintenus par jaredwray. Il a renvoyé 61 noms de packages. Pour chacun, nous avons récupéré le manifeste du registre, examiné toutes les versions publiées le 4 août et vérifié leurs scripts de cycle de vie ainsi que les métadonnées de leurs archives.
Cette vérification a révélé les versions malveillantes suivantes et confirmé que les autres packages dans l’espace de noms @keyv n’avaient pas été compromis :
keyv@6.0.0, publié à 09:35:00 UTC@cacheable/net@2.1.1, publié à 10:09:44 UTC@cacheable/node-cache@3.1.2, publié à 10:10:34 UTCcacheable@2.5.1, publié à 10:10:44 UTCflat-cache@6.1.24, publié à 10:10:55 UTC, puis supprimé@cacheable/memory@2.2.1, publié à 10:11:29 UTCcacheable-request@13.0.20, publié à 10:11:24 UTC, puis suppriméfile-entry-cache@11.1.6, publié à 10:13:02 UTC@cacheable/utils@2.5.1, publié à 10:14:21 UTCcache-manager@7.2.10, publié à 10:14:41 UTC, puis suppriméecto@5.0.1, publié à 10:28:01 UTC
La dernière entrée est importante pour délimiter l’incident. ecto@5.0.1 est apparu après les premiers avertissements publics, et ses fichiers setup.mjs et Math_Symbol.js sont identiques octet pour octet à ceux de keyv@6.0.0. Les premières listes de packages qui omettent ecto sont incomplètes.
À 11:16 UTC, notre instantané indiquait que npm avait supprimé flat-cache@6.1.24, cacheable-request@13.0.20 et cache-manager@7.2.10, et replacé leurs tags latest sur 6.1.23, 13.0.19 et 7.2.9. Les huit autres versions touchées étaient toujours présentes et portaient le tag latest. L’état du registre peut évoluer rapidement pendant un incident actif : les miroirs privés et les fichiers de verrouillage restent donc des éléments de l’enquête, même après la suppression d’une version par npm.
L’archive publiée diffère en trois points
Nous avons récupéré keyv@6.0.0 et sa version candidate, keyv@6.0.0-rc.1, directement depuis le registre, puis comparé chaque fichier avec SHA-256. Nous n’avons ni installé le package ni exécuté l’une ou l’autre des charges utiles.
Seuls trois chemins diffèrent :
package.jsonest passé de la version6.0.0-rc.1à6.0.0, les deux fichiers de charge utile ont été ajoutés à la liste des fichiers publiés et le scriptpreinstalla été ajouté.setup.mjsa été ajouté (29 918 octets).Math_Symbol.jsa été ajouté (727 680 octets).
Tous les fichiers de dist/ sont identiques octet pour octet entre la version candidate et la version stable compromise. La bibliothèque continue de fonctionner normalement après l’installation, tandis que le hook de cycle de vie s’exécute séparément. Ce petit diff est un indice de détection utile et une technique de dissimulation efficace.
La modification du manifeste est explicite :
Le manifeste du registre de keyv@6.0.0 indique la valeur d’intégrité suivante pour l’archive :
Voici les empreintes calculées de façon indépendante :
Nous avons ensuite répété la vérification des empreintes de la charge utile sur chaque version touchée récupérable lors de notre première analyse. Les neuf archives alors disponibles contenaient toutes le même fichier setup.mjs de 29 918 octets et le même fichier Math_Symbol.js de 727 680 octets, avec exactement les empreintes indiquées ci-dessus. npm a supprimé l’une de ces versions par la suite. Pour la détection, appuyez-vous sur les empreintes et le comportement du cycle de vie, plutôt que sur un seul nom de package.
Fonctionnement du malware
npm exécute automatiquement preinstall lors de l’installation des dépendances. Le développeur n’a pas besoin d’importer keyv, de démarrer une application ni d’appeler une API vulnérable. La résolution et l’installation du package concerné suffisent.
L’analyse statique de setup.mjs révèle des vérifications de plateforme pour Linux, macOS et Windows, l’utilisation de child_process.execFileSync, des opérations sur le système de fichiers, un accès HTTPS et une gestion de l’environnement d’exécution Bun. Le chargeur moins obfusqué, ajouté sous .claude/setup.mjs, identifie la version Bun 1.3.13 et le nom du fichier de deuxième étape, math_init.js. Le chargeur peut récupérer sur GitHub une version adaptée de Bun si Bun est absent, exécuter la charge utile plus volumineuse et supprimer les fichiers temporaires de l’environnement d’exécution.
L’analyse indépendante du malware fournie avec les rapports d’incident indique que la deuxième étape chiffrée cible les jetons GitHub et npm, les identifiants cloud, les clés privées, les chaînes de connexion aux bases de données, les jetons Vault, les jetons de compte de service Kubernetes et la mémoire des exécuteurs GitHub Actions. Elle signale également un mécanisme de persistance gh-token-monitor, qui surveille un jeton GitHub volé et exécute un gestionnaire fourni lorsque le jeton cesse de fonctionner.
Nous n’avons pas exécuté ni déchiffré nous-mêmes Math_Symbol.js ; ces détails sur les capacités de la deuxième étape doivent donc rester attribués à leur source. Les recommandations opérationnelles concordent avec l’analyse antérieure de Snyk sur le même mécanisme de persistance gh-token-monitor : contenir et désactiver le moniteur avant de révoquer les jetons.
Un second vecteur d’exécution cible les outils de développement
Le hook de cycle de vie npm n’est qu’un point d’entrée. Un enregistrement de l’API GitHub pour le commit d8c850c7 montre l’ajout de cinq fichiers au dépôt keyv :
.claude/settings.json.claude/setup.mjs.claude/math_init.js.vscode/tasks.json.vscode/setup.mjs
La configuration Claude enregistre une commande SessionStart qui appelle .vscode/setup.mjs. La tâche VS Code utilise runOn: "folderOpen" et appelle .claude/setup.mjs. Ces fichiers créent un autre vecteur d’exécution lorsqu’un développeur ou un agent de programmation fait confiance à la configuration locale du projet et l’active. Selon la confiance accordée à l’espace de travail et les paramètres de l’utilisateur, VS Code peut demander confirmation avant d’autoriser les tâches automatiques. L’ouverture d’un dépôt cloné constitue donc une condition d’exposition, pas la preuve que du code a été exécuté.
GitHub a vérifié cryptographiquement le commit, dont l’identité d’auteur est github-actions[bot]. Le badge de vérification prouve que GitHub a signé l’objet commit. Il ne prouve pas que la modification a été autorisée par le mainteneur du projet. Les éléments disponibles étayent la compromission d’un compte, d’un identifiant, d’une session ou du processus de publication. Ils ne permettent pas d’identifier la personne qui en est à l’origine ; le mainteneur doit être considéré comme une victime de l’incident.
Une provenance valide a signé la version malveillante
Le manifeste npm désigne GitHub Actions comme éditeur de confiance pour keyv@6.0.0 et renvoie à une attestation npm pour cette version. Le code source malveillant figurait dans l’état du dépôt associé au tag ; le workflow légitime a donc compilé et attesté l’artefact malveillant.
Le patch du commit de publication a également ajouté un test qui exécutait setup.mjs via execFileSync. Un commit ultérieur a supprimé uniquement ce test. L’exécution de la suite de tests de la version aurait donc pu lancer le malware dans l’environnement CI avant la publication.
La provenance reste un élément précieux pour établir l’origine d’une compilation. Cet incident en montre les limites : elle peut attester fidèlement une compilation dont le code source ou le contexte du workflow a déjà été compromis.
Impact et ampleur potentielle
Les principaux packages touchés affichent des volumes d’installation très élevés. Entre le 5 juillet et le 3 août, npm a enregistré 619 682 667 téléchargements de keyv, 579 751 309 de flat-cache et 571 240 025 de file-entry-cache.
Ces chiffres se chevauchent fortement, car les packages dépendent les uns des autres et se retrouvent dans les mêmes chaînes d’outils. Ils mesurent la portée dans l’écosystème, et non le nombre d’hôtes compromis. La fenêtre d’exposition de chaque version malveillante était également bien plus courte qu’un mois.
Snyk observe un impact potentiel important parmi les projets surveillés. L’utilisation transitive compte, car flat-cache et file-entry-cache sont souvent introduits par des outils de développement tels qu’ESLint. Les ordinateurs portables des développeurs et les exécuteurs CI sont des cibles particulièrement intéressantes, car ils contiennent souvent des identifiants GitHub, npm, cloud, de signature et de déploiement.
Au moment de la rédaction, aucun décompte public vérifié des exécutions réussies de la deuxième étape ou des identifiants exfiltrés n’est disponible. Les packages ont été publiés activement sur le registre npm de production et, selon notre instantané, huit portaient encore le tag latest : il s’agit donc d’une diffusion réelle, et non d’un exploit de laboratoire. L’avis de Snyk classe la maturité de l’exploitation comme « Attacked », sans publier de nombre de victimes.
Comment détecter une exposition
Commencez par examiner l’arbre des dépendances résolues, y compris les dépendances transitives :
Recherchez les versions concernées dans les fichiers de verrouillage : elles peuvent y rester épinglées même après la modification d’un dist-tag par npm.
Examinez les manifestes installés à la recherche du hook exact, sans exécuter le code du package :
Recherchez la charge utile et les indicateurs de persistance :
Examinez également les dépôts accessibles avec les identifiants GitHub exposés afin d’y repérer des fichiers .claude/settings.json ou .vscode/tasks.json inattendus, des fichiers de workflow contenant toJSON(secrets) et de nouveaux artefacts GitHub Actions.
Les clients Snyk peuvent consulter le nouvel avis sur keyv@6.0.0. Relancez les tests de dépendances et surveillez vos projets à mesure que les renseignements sur l’incident évoluent :
La leçon Snyk Learn sur les packages légitimes compromis fournit des informations complémentaires pour intégrer l’intégrité des dépendances et les contrôles du registre aux workflows de développement habituels.
Remédiation
Si un package touché a été installé
Considérez la machine ou l’exécuteur comme potentiellement compromis, même si node_modules a déjà été supprimé.
Isolez l’hôte du réseau habituel. Conservez les journaux, les données des processus, les journaux npm, les sorties des tâches CI et les horodatages du système de fichiers pour l’enquête.
Recherchez les mécanismes de
gh-token-monitoravant de révoquer les identifiants GitHub. Vérifiez~/.local/bin/gh-token-monitor.sh,~/.config/gh-token-monitor/,~/.config/systemd/user/gh-token-monitor.serviceet~/Library/LaunchAgents/com.user.gh-token-monitor.plist.Désactivez tout mécanisme de persistance détecté. Coordonnez sa suppression avec l’équipe de réponse aux incidents et conservez-en une copie à des fins d’analyse forensique. La révocation peut être détectée par le mécanisme de surveillance.
Faites tourner les identifiants depuis un système dont l’intégrité est connue. Cela inclut les jetons d’accès personnels et les jetons d’application GitHub, les jetons npm, AWS, GCP, Azure, Vault, Kubernetes, les identifiants de bases de données, les clés privées et tous les secrets accessibles aux tâches CI concernées.
Examinez les journaux d’audit des comptes et des services cloud. Recherchez des publications npm inattendues, la création de dépôts, des modifications de workflows, des artefacts Actions, des appels d’API cloud et l’utilisation de jetons depuis des emplacements inconnus.
Supprimez les artefacts concernés des registres privés et des caches. La suppression sur npm n’efface pas les copies déjà stockées dans un proxy interne ou le cache d’un développeur.
Verrouillez les versions dont l’innocuité est vérifiée avant la réinstallation
Les versions antérieures suivantes ne comportaient aucun hook de cycle de vie à l’installation dans leurs manifestes de registre au moment de notre vérification :
keyv@5.6.0 est la dernière version stable 5.x dont l’innocuité est confirmée dans notre instantané. Revenir de keyv@6.0.0 à la version 5.x peut nécessiter des modifications du code. 6.0.0-rc.1 avait une sortie dist/ identique octet pour octet et aucun hook de cycle de vie, mais les équipes de production devraient privilégier une version stable éprouvée, sauf si elles ont explicitement validé cette version candidate.
Après avoir ajouté les substitutions, régénérez le fichier de verrouillage sans exécuter les scripts de cycle de vie :
La désactivation des scripts de cycle de vie limite cette voie d’exécution. Certains packages légitimes nécessitent des scripts d’installation : les équipes devraient donc gérer une liste d’autorisation restreinte plutôt que d’activer les scripts globalement. Les bonnes pratiques de sécurité npm de Snyk détaillent davantage les installations déterministes, le contrôle des scripts et l’examen des packages.
Chronologie de l’incident
Toutes les heures ci-dessous sont en UTC, le 4 août 2026.
09:02 à 09:17 : Le commit
ee2681a9préparekeyv@6.0.0, ajoute le hook de cycle de vie, les fichiers de charge utile et un test qui exécute le chargeur. GitHub indique une heure d’auteur de 09:02 et une heure de validation de 09:17.09:04 : Le commit vérifié
d8c850c7ajoute les hooks d’exécution de Claude et VS Code.09:23 : Le commit
f97eabcdsupprime le test preinstall.09:30 à 09:32 : Plusieurs packages
@keyv/*en version 6 sont publiés sans le hook malveillant.09:35 : npm publie
keyv@6.0.0avec le hook malveillant.09:51 : Le commit
1f79edd8ajoute les fichiers de charge utile aux espaces de travail@keyv/*, créant un risque pour les versions ultérieures.09:49 à 09:51 : Les issues GitHub
#2044,#2045et#2046signalent l’incident. L’API des issues a ensuite renvoyé410 Gone.10:09 à 10:14 : Les versions malveillantes de la famille Cacheable sont publiées.
10:18 et 10:20 : Le chercheur en sécurité Charlie Eriksen publie les deux avertissements publics fournis.
10:28 :
ecto@5.0.1est publié avec la même charge utile, portant à 11 le nombre de packages liés au mainteneur.10:39 : Les métadonnées du registre npm sont modifiées après la suppression de
cacheable-request@13.0.20.10:42 : Les métadonnées du registre npm sont modifiées après la suppression de
flat-cache@6.1.24.11:11 : Les métadonnées du registre npm sont modifiées après la suppression de
cache-manager@7.2.10.11:16 : L’instantané du registre de Snyk recense encore huit versions malveillantes sous le tag
latest.Pendant la rédaction : Snyk publie
SNYK-JS-KEYV-18515941et environ 70 autres avis de sécurité.
Cette chronologie concerne un incident en cours. Revérifiez les manifestes npm, les balises de distribution et les avis de sécurité Snyk juste avant publication.
Webinaire à la demande
OpenAI a corrigé sa propre copie, puis a compromis un environnement de production
Regardez le webinaire à la demande pour comprendre pourquoi l’auto-validation échoue par nature, pourquoi une architecture multi-modèle aggrave le problème et à quoi ressemble une validation indépendante en pratique. Repartez avec un cadre pour gouverner chaque ressource d’IA dans votre environnement, quel que soit le laboratoire qui l’a développée.
