L’autre jour, en parlant d’une supply chain attack récente avec un autre développeur, il m’a répondu : “J’étais pas concerné, je ne travaillais pas à minuit 30 quand ça s’est produit”. C’est là que je me suis dit qu’il fallait expliquer pourquoi, en réalité, on est beaucoup plus facilement concerné qu’on ne le pense.

Le sujet est relativement nouveau. Jusqu’à présent, quand on parlait de sécurité applicative, on pensait d’abord à son propre code : injections SQL, XSS, mauvaises configurations, secrets exposés. C’est ce qu’on apprend en formation, c’est ce qu’on retrouve dans le top 10 OWASP, c’est ce que les outils d’analyse statique vont chercher en priorité.

Mais une part croissante des incidents ne vient pas du code qu’on écrit. Elle vient du code qu’on importe, sans le lire, sans le comprendre, parfois sans même savoir qu’on en dépend. C’est ce qu’on appelle une supply chain attack, et c’est devenu l’un des vecteurs d’attaque les plus difficiles à anticiper. Et le pire ? Probablement que vos outils habituels n’y voient que du feu.

Ce premier article ouvre une série dédiée au sujet. L’idée est simple : poser le décor, regarder quelques cas réels qui ont marqué l’écosystème, et démonter quelques réflexes qui paraissent évidents mais qui se retournent souvent contre nous.


C’est quoi, concrètement

Une supply chain attack, c’est lorsqu’une dépendance que vous utilisez (directement ou indirectement) devient malveillante. Soit parce que son mainteneur a été compromis, soit parce qu’un attaquant a réussi à s’introduire dans la chaîne de publication, soit, plus rarement, parce que la personne qui maintient le projet est elle-même malveillante.

Le piège, c’est que cette dépendance n’a pas besoin d’être visible dans votre composer.json ou votre package.json. Elle peut être trois niveaux plus bas dans l’arbre. Vous n’avez jamais entendu son nom, vous ne l’avez jamais auditée, mais elle s’exécute dans votre processus, avec vos permissions, à côté de vos secrets.

Les deux dernières années ont été particulièrement chargées. Quelques cas qui valent le détour, parce qu’ils illustrent bien la diversité des scénarios :

  • Axios (mars 2026) : compromission du compte npm du mainteneur principal de la librairie HTTP la plus utilisée de l’écosystème JavaScript (70 millions de téléchargements par semaine). L’attaquant a changé l’email du compte, contourné le workflow OIDC de publication en utilisant un token legacy longue durée, et publié les versions 1.14.1 et 0.30.4 au milieu de la nuit UTC (d’où la fameuse réplique “j’étais pas concerné”). Le payload installait un RAT multi-plateforme sur macOS, Windows et Linux. Microsoft et Google ont attribué l’attaque à un acteur étatique nord-coréen.
  • Shai-Hulud (septembre 2025) : le premier ver auto-réplicatif jamais observé sur npm. Le point de départ est @ctrl/tinycolor. Le payload installé scannait l’environnement pour récupérer les tokens npm de la victime, puis se republiait automatiquement dans tous les packages que celle-ci maintenait. Plus de 500 packages contaminés en quelques jours, dont certains de CrowdStrike. C’est la première fois qu’on voit la propagation virale appliquée à un registre de packages.
  • Nx Console (mai 2026) : l’extension VS Code Nx Console (nrwl.angular-console, plus de 2,2 millions d’installations) a été compromise. Une version malveillante 18.95.0 est restée en ligne 11 minutes sur le marketplace avant d’être retirée. Largement suffisant : dès qu’un développeur ouvrait son IDE, le payload récupérait ses tokens GitHub, npm, AWS, HashiCorp Vault, Kubernetes et 1Password, et installait sur macOS un backdoor Python persistant utilisant l’API GitHub Search comme dead-drop. Bilan : environ 3 800 dépôts privés exfiltrés. Attribué au groupe TeamPCP. À noter, c’est la deuxième attaque visant l’écosystème Nx en moins de neuf mois.
  • Nx, alias “s1ngularity” (août 2025) : un workflow GitHub Actions mal configuré (un pull_request_target qui s’exécutait sur une branche obsolète encore vulnérable) a permis à un attaquant d’extraire le token de publication npm du projet. Pendant les quatre heures où les packages malveillants étaient en ligne, le post-install scannait la machine des développeurs pour récupérer tokens GitHub, credentials cloud, clés SSH et wallets crypto. Particulièrement créatif : il invoquait aussi les CLI Claude et Gemini installés localement pour leur demander de chercher d’autres secrets. Résultat : 2 349 credentials volés, et 5 500 dépôts privés rendus publics par les attaquants sur les comptes des victimes. On y reviendra dans la partie CI.
  • XZ Utils (2024) : un attaquant patient s’est introduit dans le projet pendant plus de deux ans, gagnant progressivement la confiance du mainteneur, avant d’injecter une backdoor dans une bibliothèque de compression utilisée par à peu près toutes les distributions Linux. Découverte par hasard, par un ingénieur de chez Microsoft qui s’interrogeait sur 500 millisecondes de latence anormale lors d’une connexion SSH.
  • polyfill.io (2024) : un nom de domaine de confiance, utilisé par des centaines de milliers de sites, racheté discrètement. Les scripts servis depuis ce domaine se sont mis à injecter du code malveillant ciblé.
  • event-stream (2018) : le cas historique. Un mainteneur fatigué a cédé son package npm à un inconnu serviable. Quelques semaines plus tard, une dépendance ajoutée par ce nouveau mainteneur ciblait spécifiquement une application de portefeuille Bitcoin pour exfiltrer des clés privées.

Ce qui rend ces attaques particulièrement vicieuses, c’est qu’aucun outil de sécurité applicative classique ne les détecte au moment où elles arrivent. Le code malveillant est signé, publié sur le registre officiel, installé via la commande habituelle. Tout est nominal. Il n’y a rien à voir dans les logs, parce que le mal s’exécute exactement là où il doit s’exécuter. Et le rythme accélère : on est passé d’un incident majeur tous les deux ans à un par trimestre, parfois plus.


Le piège des mises à jour

Le premier réflexe, quand on entend “vulnérabilité dans une dépendance”, c’est de tout mettre à jour. C’est logique : si la version 1.2.3 est vulnérable, la 1.2.4 corrige le problème, donc on update.

Sauf que dans le cas d’une supply chain attack, c’est précisément la nouvelle version qui est compromise, et l’update est exactement ce qui vous expose. Pour Axios, les utilisateurs qui avaient laissé un caret (^1.14.0 ou ^0.30.0) et qui ont lancé un npm install au mauvais moment ont intégré le RAT sans rien faire de plus. Ceux qui avaient une version épinglée n’ont pas été touchés.

La conclusion à tirer n’est pas “n’updatez jamais”. Ce serait pire, parce que vous accumulez les CVE connues. Elle est plus nuancée : séparez les updates de l’installation.

// package.json : versions flottantes, source de problèmes
"dependencies": {
  "express": "^4.18.0"
}

Tel quel, chaque npm install peut résoudre une version mineure différente. Sur la machine d’un nouveau développeur, en CI, en prod, vous ne tournez pas exactement les mêmes binaires.

// package-lock.json : la vraie source de vérité
"node_modules/express": {
  "version": "4.18.2",
  "resolved": "https://registry.npmjs.org/...",
  "integrity": "sha512-..."
}

Le lock file, c’est la photo exacte de ce qui a été installé, avec les hashs d’intégrité. Tant que vous le respectez (npm ci plutôt que npm install, composer install plutôt que composer update), vous installez bit pour bit le même arbre de dépendances, partout.

Updater devient alors un acte volontaire et auditable : une PR dédiée, une diff de lock file lisible, un changelog à parcourir. Pas un effet de bord d’un install quelconque.

C’est un changement de mentalité plus qu’un changement d’outillage. Mais c’est la première vraie ligne de défense.

Pour revenir au développeur du début : si ce premier réflexe suffisait, il aurait raison. Il serait bien protégé, et il pourrait dormir tranquille. Mais malheureusement, ça va plus loin.


Les pièges de la CI

Une fois qu’on a compris qu’il faut auditer ses updates, on délègue souvent ça à un outil : Renovate, Dependabot, Snyk. Ils ouvrent des PR proprement, avec le changelog, parfois le résultat des tests. C’est bien. Mais c’est aussi un nouvel angle d’attaque, et il est souvent mal configuré.

Premier piège : la CI qui s’exécute sans validation du commiteur. En open source, c’est fréquent. Quelqu’un ouvre une pull request, et le workflow GitHub Actions tourne automatiquement pour vérifier que les tests passent. Si ce workflow utilise un secret (un token de déploiement, une clé d’API, un mot de passe de registre) et que l’attaquant peut modifier le code exécuté, le secret fuit. C’est exactement le scénario du pull_request_target mal utilisé : le workflow tourne avec les permissions du repo d’origine, mais exécute le code de la PR.

C’est précisément ce qui s’est passé sur Nx en août 2025. Une PR ouverte par n’importe qui, ciblant une branche obsolète qui contenait encore un workflow vulnérable, a permis d’extraire le NPM_TOKEN du projet. Pendant les quatre heures qui ont suivi, des versions malveillantes étaient publiées et installées sur des milliers de machines. La vulnérabilité avait été identifiée et corrigée sur la branche principale presque immédiatement, mais elle subsistait sur une branche oubliée. L’attaquant n’a eu qu’à la viser explicitement.

La règle de base : les secrets ne doivent jamais être disponibles dans un workflow déclenché par une PR externe. GitHub Actions propose un mode pull_request qui isole correctement les permissions. Le pull_request_target doit être réservé à des cas très précis, et le code exécuté doit être celui de la branche par défaut, pas celui de la PR. Et tant qu’on y est, n’oubliez pas vos vieilles branches : un workflow corrigé sur main mais resté vulnérable sur une branche de release, c’est une porte ouverte.

Deuxième piège : les outils de mise à jour automatique configurés en auto-merge. L’argument paraît raisonnable : “si les tests passent, on merge”. Mais ça revient à dire : “si une dépendance publie une version qui passe nos tests, on l’installe en prod sans intervention humaine”. Pour un patch sécurité critique, peut-être. Pour 100% des updates de dépendances, sûrement pas.

Renovate et Dependabot offrent des configurations fines : auto-merge pour les patchs de dev-dependencies internes uniquement, regroupement par écosystème, délai de quarantaine après la publication (très utile : la plupart des compromissions sont détectées dans les 48 heures). Quelques lignes de configuration suffisent à transformer un risque en garde-fou.

// renovate.json : extrait avec un délai de quarantaine
{
  "minimumReleaseAge": "3 days",
  "packageRules": [
    {
      "matchUpdateTypes": ["patch"],
      "matchDepTypes": ["devDependencies"],
      "automerge": true
    }
  ]
}

Ces trois jours de délai ne paraissent rien, mais ils suffisent souvent à laisser la communauté détecter une version compromise avant qu’elle n’arrive chez vous.

Maintenant, imaginons qu’on combine les deux. Un projet d’entreprise tourne avec des permissions de workflow laxistes parce que “c’est interne, on se fait confiance”. Derrière, un Renovate ouvre automatiquement les PR d’update à minuit UTC et lance toutes les CI internes. Et ce n’est probablement pas un hasard si Axios et Nx ont publié leurs versions piégées en pleine nuit : c’est exactement la fenêtre où les automatisations tournent sans personne pour les regarder, et où le code malveillant a quelques heures devant lui avant que quelqu’un ne s’en aperçoive.

C’est le moment où notre développeur du début change de regard, et se demande s’il ne devrait pas aller jeter un œil aux logs de la nuit dernière.


Et ça ne concerne pas que vos projets

On a tendance à voir la supply chain comme un sujet purement applicatif. Vos applications ont des package.json, des composer.json, des requirements.txt, et c’est là qu’il faut être vigilant.

C’est vrai, mais c’est très loin d’être suffisant. Tout ce que vous installez sur votre machine de développeur ou sur vos serveurs participe à la même chaîne, avec souvent des permissions plus larges encore.

Les extensions VS Code sont un excellent exemple. Une extension installée a accès à tout votre workspace, peut lire vos fichiers .env, vos clés SSH, vos historiques Git. Elle s’exécute avec votre identité, sans sandbox. Le marketplace VS Code a déjà hébergé plusieurs extensions malveillantes : clones d’extensions populaires, comptes de mainteneurs rachetés, fonctionnalités prometteuses puis détournées. L’attaque sur Nx Console en mai 2026 (citée plus haut) illustre exactement le scénario : 11 minutes en ligne suffisent à siphonner les credentials de milliers de développeurs. Le réflexe “j’installe l’extension recommandée” devrait être bien plus mesuré qu’il ne l’est généralement.

Les outils en ligne de commande installés en global (via npm install -g, pip install --user, cargo install, brew install) suivent la même logique. Ils tournent avec les permissions de votre utilisateur. Beaucoup ajoutent des hooks dans votre shell, lisent votre configuration, font des appels réseau. Un CLI compromis a accès à tout ce que vous avez ouvert dans votre terminal, y compris ces credentials que vous avez sourcés pour cinq minutes “juste le temps de tester un truc”.

Les images Docker méritent leur propre article, et elles l’auront dans cette série. Mais retenons déjà qu’un FROM node:latest est une décision de confiance, et pas une commande anodine.

L’idée n’est pas de devenir paranoïaque. Vous allez continuer à installer des extensions, des CLI, des images Docker, et c’est très bien. L’idée est de savoir où sont vos zones de confiance, et d’arrêter de les étendre par défaut.


Ce qui arrive ensuite

Cette série va creuser ces sujets un à un, avec un angle pratique. Pas de checklist générique de cabinet de conseil, plutôt des cas réels et des configurations qu’on peut mettre en place dès le prochain commit.

Au programme :

  1. Cet article : le décor, les exemples, les premiers réflexes
  2. S’en protéger dans l’univers Laravel et JS : Composer, npm, audits, signatures, lock files
  3. S’en protéger en Docker : images, registres, scan, builds reproductibles
  4. Protéger sa propre machine : extensions, CLI globaux, hygiène de développeur

L’objectif n’est pas de tout sécuriser à 100%, ce n’est pas possible. C’est d’élever sensiblement le coût d’une attaque pour qu’elle aille viser quelqu’un d’autre. Et au passage, de pouvoir répondre autre chose que “j’étais pas concerné” la prochaine fois qu’on en parle autour d’un café.

LLM-friendly This post is available as raw Markdown for LLMs and AI tools. The full site index is at /llms.txt.