Principaux enseignements
- Les fautes de frappe et les « hallucinations » de l'IA fournissent toutes deux aux pirates un nom de paquet exploitable, et aucune des deux ne nécessite de vulnérabilité.
- La provenance permet de déterminer d'où provient un objet, mais ne garantit pas la légitimité de la transaction ou du compte qui lui est associé.
- La vigilance n'est pas une solution évolutive ; privilégiez donc les dépendances fixes, le routage déterministe et les listes d'autorisation de scripts.
Avez-vous déjà réfléchi à la longueur de la chaîne de personnes en qui vous placez votre confiance chaque fois que vous installez un paquet ? La personne qui a écrit le code. Celle qui l’a relu. Le registre qui l’héberge. Le moteur d’intégration continue qui le récupère à 3 heures du matin, sans que personne ne surveille. Les pirates sont doués pour fouiner dans les écosystèmes de paquets, et ils sont devenus très habiles pour repérer le maillon le plus faible de cette chaîne. Installer du code plus vite que quiconque ne peut le réviser entraîne une multitude de problèmes.
La plupart des attaques visant la chaîne logistique logicielle fonctionnent ainsi. Elles n'ont pas besoin de franchir un périmètre de sécurité ; elles exploitent un chemin de confiance exactement comme il a été conçu pour être utilisé.
Voici un bref aperçu des différentes façons dont la confiance peut être trahie, et de ce qui permet réellement de la préserver.
Fautes de frappe dans les paquets
L'astuce la plus ancienne qui ne nécessite aucun exploit. Il suffit simplement de taper vite et de ne pas regarder de trop près. Un pirate publie un paquet dont le nom comporte une lettre ou un trait d'union en trop, puis attend que quelqu'un l'installe par erreur.
Le crossenv Ce package en est un exemple bien connu. En juillet 2017, un utilisateur de npm se faisant appeler « hacktask » a publié une quarantaine de packages ressemblant les uns aux autres ; le plus marquant était cross-env sans le trait d'union. Si ce seul caractère venait à manquer, le programme que vous aviez installé lisait vos variables d'environnement et les envoyait à un serveur contrôlé par le pirate.
{
"name": "crossenv",
"scripts": {
"postinstall": "node package-setup.js"
}
}
Rapport d'incident de npm donne un nom au script de charge utile package-setup.js. En résumé, voici comment cela fonctionnait :
// Simplified illustration of what the script did.
// Reads the environment variables available to the
// install, then sends them to the attacker's server.
const vars = process.env;
sendToAttackerServer(vars);
Sur une machine de développement ou un serveur d'intégration continue, ces variables contiennent souvent des jetons de registre, des identifiants de cloud et des mots de passe de bases de données ; c’est pourquoi il vaut la peine de les récupérer. npm a enregistré 679 téléchargements pour crossenv mais on estimait qu'il n'y en avait pas plus d'une cinquantaine environ qui étaient de véritables installations et non des miroirs.
Si le typosquatting fonctionne, c'est parce que la solution ne réside pas dans un correctif. La véritable « solution » consiste à être vigilant, et nous savons tous que la vigilance ne s'applique pas à grande échelle. Il faut donc transférer cette tâche vers un domaine où cela est possible : fixez les dépendances, examinez les modifications apportées au fichier de verrouillage avec autant de soin que celles apportées au code, et effectuez les résolutions à partir d'une source interne approuvée.
Squat avec l'IA (Slopsquatting)
Le « slopsquatting » est la même astuce exploitant une erreur différente. Au lieu de votre faute de frappe, cette technique repose sur le fait qu’un assistant de codage recommande un paquet qui n’existe pas, ce qui offre à un attaquant la possibilité d’enregistrer ce nom avant qu’un véritable projet ne le fasse.
Dans l'étude « USENIX Security 2025 » consacrée aux « hallucinations de paquets », 16 modèles ont généré 576 000 échantillons de code faisant référence à 2,23 millions de paquets. Au cours de ces tests, 19,7 % des références, soit 440 445 d'entre elles, désignaient des paquets inexistants, couvrant 205 474 noms inventés uniques. Lorsque les chercheurs ont relancé des invites ayant déjà produit une hallucination, 43 % des noms inventés sont réapparus lors des dix exécutions, ce qui justifie leur enregistrement préalable.
Le cas de « huggingface-cli » en est un bon exemple. Bar Lanyado, chercheur en sécurité chez Lasso Security, a remarqué que certains modèles recommandaient ce nom alors qu’aucun paquet de ce nom n’existait sur PyPI ; il en a donc enregistré un vide en décembre 2023. Celui-ci a été téléchargé plus de 30 000 fois en trois mois, et la commande d’installation erronée est apparue dans le fichier README d’un dépôt de recherche public d’Alibaba.
# What an AI assistant might confidently suggest
pip install huggingface-cli
# What actually installs the official Hugging Face CLI
pip install -U "huggingface_hub[cli]"
La deuxième option installe la version officielle huggingface_hub paquet avec son extension CLI. Le premier installe n’importe quelle distribution enregistrée sous un nom inventé par un modèle. Celui de Lanyado était volontairement inoffensif. Ce ne serait pas forcément le cas pour celui d’un autre. Il faut donc vérifier que le projet existe bel et bien, contrôler l’éditeur et l’historique des versions, puis le faire valider avant qu’il n’atteigne un fichier de verrouillage.
Confusion des dépendances
La confusion de dépendances est un échec de résolution. Elle survient lorsqu'une entreprise utilise à la fois des paquets privés et publics, et que la compilation peut trouver une correspondance pour un nom donné à plusieurs endroits différents.
Les détails varient selon l'écosystème. pip en est l'exemple le plus parlant : il suffit de lui fournir un index supplémentaire avec --extra-index-url Il examine les candidats issus de chaque index configuré, puis sélectionne celui qui correspond le mieux parmi tous, ce qui peut signifier qu’un paquet public de version supérieure l’emporte. npm effectue la résolution en fonction de la portée et du registre configuré plutôt qu’en fusionnant les flux ; ainsi, l’exposition provient généralement d’un nom interne sans portée, ou d’un proxy qui fusionne les paquets publics et privés en une seule vue, sans politique de nom d’espace ni de priorité sous-jacente.
Alex Birsan a mis en évidence ce problème en 2020 et 2021 après avoir découvert que des noms de paquets internes apparaissaient dans des dépôts publics et des messages d'erreur. Il a publié sur npm, PyPI et RubyGems des paquets inoffensifs portant ces noms et dotés de numéros de version très élevés, puis a observé qu'ils étaient intégrés dans les versions internes de plus de 35 entreprises, dont Apple, Microsoft et PayPal, ce qui lui a permis de gagner plus de 130 000 dollars en primes de sécurité.
{
"name": "internal-auth-tool",
"version": "99.0.0"
}
Le danger ne vient pas du JSON. Il s'agit d'une configuration qui n'associe jamais ce nom à une source fiable. Pour npm, limitez la portée aux paquets internes et fixez cette portée au registre privé.
# .npmrc
registry=https://registry.npmjs.org/
@your-org:registry=https://your-private-registry.example.com/
Ce modèle est propre à npm et ne s'applique que lorsque le routage du registre, la propriété de l'espace de noms et les autorisations de publication coïncident. Les écosystèmes dépourvus de « scopes » doivent disposer de leur propre mécanisme de fixation des sources ; pour pip, cela signifie ne pas traiter --extra-index-url en tant que solution de secours privilégiant le domaine privé.
Responsables détournés
Avec cette technique, les faux-semblants disparaissent. Un attaquant prend le contrôle du compte ou du processus de publication associé à un paquet auquel vous faites déjà confiance, puis diffuse une mise à jour malveillante via l'infrastructure de publication habituelle du projet. Il s'agit d'une exploitation de la confiance, et non d'une exploitation du code.
Le 4 août 2026, c'est ce qui est arrivé à keyv ainsi que le reste de sa famille de caches. Des pirates ont compromis le compte GitHub du responsable du projet, ont poussé des commits malveillants vers la branche principale et ont laissé le workflow « Actions » du projet lui-même le compiler et le publier. Le ver, surnommé ChainDrop, a collecté toutes les informations d’identification exposées par les environnements compromis, puis a utilisé des jetons npm volés pour se republier dans les paquets d’autres responsables. StepSecurity a recensé 444 paquets et 2 212 versions corrompues en l'espace de quatre heures ; d'autres ont enregistré des totaux plus élevés par la suite.
Les versions corrompues comportaient une traçabilité valide, et il ne s’agissait pas d’une défaillance de cette traçabilité. Celle-ci attestait, à juste titre, que l’artefact avait été généré par le workflow du projet à partir des données d’entrée déclarées. Ce qu’elle ne permet toutefois pas de déterminer, c’est si la modification enregistrée était légitime, ni si le compte à l’origine de celle-ci appartenait toujours à son propriétaire.
De nombreuses équipes n'ont jamais eu à choisir keyv pas du tout. flat-cache et file-entry-cache relèvent d'ESLint, et keyv se trouve en dessous de ceux-là.
Scripts de pré-installation et de post-installation
Les scripts d'installation constituent moins une catégorie d'attaque distincte qu'une voie d'exécution utilisée par plusieurs autres catégories. Un paquet peut déclarer preinstall, installou postinstall fonctionner, et, historiquement, cela s'exécutait lors de l'installation, avant même que quiconque n'en ait lu une seule ligne.
Ce timing est crucial. Le script s'exécute avec les privilèges du développeur ou du robot d'intégration continue, alors que l'installation semble tout à fait banale. ChainDrop a utilisé un hook d'installation malveillant de la même manière.
L'année 2021 ua-parser-js « Compromise » en est l'exemple type. Après le piratage du compte npm du responsable du projet, trois versions malveillantes ont été distribuées avec un preinstall un hook qui installait un mineur de cryptomonnaie sous Linux et, sous Windows, à la fois le mineur et un logiciel malveillant destiné à voler des identifiants. Ils ont été retirés en l’espace d’environ quatre heures, ce qui est rapide selon les normes en matière de réponse aux incidents. Le paquet était téléchargé des millions de fois par semaine ; les builds automatisés les récupéraient donc sans que personne n’ait à taper son nom.
{
"scripts": {
"preinstall": "node setup.js"
}
}
Les impayés commencent enfin à évoluer. pnpm n'a pas exécuté de scripts de compilation des dépendances sans autorisation explicite depuis la version 10, les versions actuelles consignant ces décisions dans un allowBuilds carte, et npm 12 scripts d'installation de dépendances non pris en charge par allowScripts. Il faut savoir comment ce comportement par défaut se manifeste. Le script est ignoré avec un avertissement et l'installation aboutit quand même ; ainsi, une dépendance native peut être intégrée sans que son binaire ait jamais été compilé. Une fois que vous avez validé le paquet, npm rebuild exécute les tâches d'installation qui ont été ignorées, et dans le cadre de l'intégration continue (CI) --strict-allow-scripts transforme un script non vérifié en une installation ratée plutôt qu’en une installation incomplète qui passerait inaperçue.
Les anciennes versions de npm ne sont pas sans défense. Elles se comportent simplement différemment par défaut.
npm install --ignore-scripts
C'est un peu brutal. Ça empêche le fonctionnement de paquets qui se compilent pourtant correctement au moment de l'installation, et ça ne fait rien contre le code malveillant qui attend que votre application l'importe. Considérez une liste blanche explicite comme la véritable solution.
Pourquoi tout cela continue de fonctionner
Aucune de ces attaques ne peut être résolue par une seule personne ou un seul outil. Elles deviennent plus difficiles à contrer lorsqu’une équipe élimine toute ambiguïté du processus : paquets privés bien délimités, routage déterministe du registre, installations figées à partir d’un fichier de verrouillage validé, scripts d’installation autorisés par exception plutôt que par défaut, et suggestions de dépendances générées par l’IA considérées comme non vérifiées jusqu’à ce que quelqu’un les valide.
Mais attendre de chaque ingénieur qu'il détecte tous les paquets malveillants lors de chaque installation ne constitue pas une politique de sécurité. Le paquet doit bien provenir de quelque part, et cet endroit fait partie de votre périmètre de sécurité.
C'est précisément pour cela que SecureChain a été conçu. Au lieu de récupérer directement les artefacts depuis un registre public, vous configurez votre gestionnaire de paquets pour qu'il pointe vers une source contrôlée où les paquets sont recompilés à partir de code source vérifié dans des environnements isolés, analysés pour détecter les logiciels malveillants et mis à jour dès l'apparition de nouvelles vulnérabilités (CVE), avec des versions signées et une traçabilité à des fins d'audit. La solution prend en charge JavaScript, Python, Java, Go, Rust et PHP, et les développeurs continuent d'utiliser le gestionnaire de paquets qu'ils connaissent déjà.
Lorsque le problème provient d'un paquet non maintenu plutôt que d'un paquet malveillant, le service Endless Lifecycle Support (ELS) effectue des retroportages des correctifs de sécurité vers les versions que vous utilisez déjà, ce qui vous permet de continuer à recevoir des correctifs même après la fin du support en amont.
Mes dernières réflexions
La confiance est un pilier du développement logiciel moderne, mais c’est la confiance aveugle qui est exploitée. Les pirates cherchent à s’introduire en tirant parti des fautes de frappe, de l’IA, des comptes piratés ou du fait que l’on part du principe qu’un logiciel familier est toujours celui que l’on croyait. Nous ne pouvons pas contrôler tout ce qui se passe sur le Web ouvert.
Vous avez toutefois le contrôle sur ce qui se retrouve dans votre environnement. Comprenez comment ces attaques fonctionnent, sécurisez votre pipeline de build, gérez les autorisations d'accès à votre registre et à vos scripts, et mettez en place une source vérifiée entre les registres publics et l'environnement de production. Le code que vous déployez doit être celui que vous aviez prévu d'exécuter.
