Un "bump" Dependabot a publié un ver. 22 paquets.
Un bot ouvre une pull request.
Un bot ouvre une pull request. Un humain la fusionne 24 minutes plus tard. 95 minutes après cela, un ver a publié 110 versions malveillantes de ses 22 paquets npm, sous son nom, avec un jeton qu'il a trouvé dans son CI.
Lire l'édition écrite (anglais) ↗
Ce que couvre cette vidéo
- Un bot, un humain, un ver
- Comment c'est arrivé, pourquoi npm l'a permis, à qui la faute
- Chronologie : le cache empoisonné
- 21:48 — le "bump" Dependabot
- Pourquoi ça marche
Transcription traduite
Traduit de la narration originale en anglais. L'audio et les sous-titres disponibles sont gérés par YouTube.
Un bot, un humain, un ver
0:00 Un bot ouvre une pull request ; un humain la fusionne vingt-quatre minutes plus tard. Quatre-vingt-quinze minutes après, un ver a publié cent dix versions de ses paquets, comme lui. Le postmortem de TanStack a les horodatages en amont, celui du mainteneur en aval a le reste, et Hacker News lui donne mille cent points et un nom : Mini Shai-Hulud. Comment ça arrive, pourquoi npm le permet, à qui la faute.
Comment c'est arrivé, pourquoi npm l'a permis, à qui la faute
0:21 Ceci est The Daily Diff, postmortem. 11 mai, matin. Un fork renommé ouvre une pull request contre TanStack Router. Elle se ferme dans l'heure, mais un workflow de benchmark a déjà exécuté son code et
Chronologie : le cache empoisonné
0:33 enregistré un cache empoisonné sous la clé que le workflow de publication utilisera. Dix-neuf heures vingt. Une fusion légitime exécute la publication. Le cache revient, un binaire lit la mémoire de l'exécuteur, extrait le jeton de publication, et expédie quatre-vingt-quatre versions à travers quarante-deux paquets, avec une provenance valide. Les tests échouent. Il publie quand même. Dix-neuf heures quarante-six, un chercheur de StepSecurity dépose le problème ; à neuf heures UTC tout est déprécié et l'avis est publié.
0:55 Déprécié ne signifie pas disparu : npm refuse de dépublier quoi que ce soit avec des dépendants, donc ça reste installable pendant des heures. Vingt-et-une heures quarante-huit. Dans un projet de données d'aviation à neuf étoiles, Dependabot ouvre sa pull request de routine : mettre à jour le groupe de dev-dépendances, treize mises à jour. Deux sont des versions TanStack empoisonnées. Vingt-deux heures douze : fusionné. Le workflow de publication exécute npm C-I avec le jeton en
21:48 — le "bump" Dependabot
1:15 portée ; un script de préparation le lit, et à vingt-deux heures dix-sept le ver publie comme lui. Cinq versions de chaque paquet que le jeton atteint, même un ancien projet parallèle : un jeton classique pour tout. Cent dix versions en quatre-vingt-quinze minutes. Il le découvre par e-mail, après minuit. Pourquoi ça marche. Un : npm install exécute les scripts de cycle de vie d'étrangers par défaut, et le script de préparation d'une dépendance git compte.
1:39 Deux : le ver veut une chose : un jeton qui publie sans deuxième facteur. Ensuite, il demande au registre ce que ce mainteneur possède d'autre, et republie tout avec lui-même à l'intérieur. Trois : rien dans la chaîne n'est une personne.
Pourquoi ça marche
1:51 Un bot propose, un pipeline installe, et le ver renvoie la pareille : ses branches de "dead-drop" sont nommées dependabot slash github-actions slash fremen. git blame. Le modèle d'installation de npm, cinquante-cinq pour cent : les scripts s'exécutent à l'installation, les versions dépréciées restent installables, les jetons de contournement à double facteur existent. CI de TanStack, vingt-cinq : un workflow de ciblage de pull request non audité exécutant du code de fork avec un accès en écriture au cache. L'habitude de la mise à jour, quinze : treize mises à jour fusionnées en vingt-quatre minutes, jeton dans la pièce.
2:17 Dependabot, cinq : si fiable que le ver porte son uniforme.
git blame
2:20 Rayon d'action : quarante-deux paquets TanStack. Vingt-deux en aval, à partir d'un projet avec neuf étoiles. Plus de cent soixante à l'échelle de l'écosystème une fois que le ver atteint Mistral. Chaque version en amont portait une signature valide : construite par le pipeline officiel. Vrai. Verdict, postmortem : SHIP IT. Les deux mainteneurs publient des postmortems horodatés en un jour ; en trois, le pipeline en aval s'installe avec ignore-scripts, sépare la construction de la publication, et supprime le jeton de longue durée.
Rayon d'action
2:47 Les paramètres par défaut de npm n'ont pas changé. Lundi : ignore-scripts à l'installation, et le jeton de publication hors de la tâche qui l'exécute. Envoyez-moi l'incident dont vous n'êtes toujours pas autorisé à parler, dans les commentaires, ou à thedailydiff.dev. Et c'est la différence pour aujourd'hui.
Verdict
3:00 Je suis Niko d'Axrisi. Fusionnez de manière responsable.
Sources
- TanStack postmortem (Tanner Linsley, May 11, refined May 15)tanstack.com
- TanStack "Hardening TanStack After the npm Compromise" (May 12)tanstack.com
- TanStack/router#7383 — the StepSecurity detection issuegithub.com
- Downstream incident post, @squawk/* (May 12)github.com
- Downstream hardening post (May 14)github.com
- The Dependabot PR #246 (opened 21:48 UTC, merged 22:12)github.com
- @tan_stack advisory post, 21:19 UTCx.com
- StepSecurity — worm internals, bypass_2fa search, Dependabot-style branch nameswww.stepsecurity.io
- Socketsocket.dev
- Aikido — "over 160 packages, including Mistral"www.aikido.dev
- Hacker News (1,097 points)news.ycombinator.com



