Illustration technique abstraite sur un fond bleu marine sombre : un petit cube de paquet en vecteur plat au centre-gauche, avec une fine ligne ambrée qui descend en crochet vers une représentation compacte d'une station de travail de développeur ; un éventail de fins filaments cyan et bleu acier se déploie vers la droite depuis la station de travail en direction de nombreux petits nœuds circulaires sur une grille lâche, deux d'entre eux remontant en courbe vers le cube de paquet, et un nœud à l'extrémité droite se trouve seul et déconnecté. Aucune personne, aucun logo ni texte n'apparaît. Le logo OrcaRouter est incrusté dans le coin inférieur droit.
AI Safety Incidents

Le SDK npm de Tensorlake a été empoisonné pour voler les identifiants d’agents IA — et pour effacer votre répertoire personnel si vous révoquiez le jeton

Auteur

Alistair Wren

Date de publication

Derniers modèles · 20Voir tous les modèles →
Benchmarks : Artificial Analysis · mis à jour quotidiennement
Retour à tous les articles

Le 8 octobre 2026, une version empoisonnée de tensorlake — le SDK npm de Tensorlake, qui exécute du code LLM non fiable dans des sandboxes isolés — a atteint le registre npm. La version 0.5.144 a été publiée à 01:12:07 UTC ; les chercheurs de Socket indiquent l’avoir signalée environ onze minutes plus tard. Il ne s’agissait pas d’une version autorisée. Tensorlake déclare que l’attaquant a poussé du code directement dans le dépôt GitHub de l’entreprise et l’a publié via le workflow de publication propre au projet, de sorte que le paquet portait une provenance npm valide, une attestation signée indiquant où il a été construit, pour un code source qui avait déjà été empoisonné. L’entreprise affirme que la version est restée en ligne pendant environ 1 heure 40 minutes, de 01:12 à 02:54 UTC, avant que npm ne la retire.

La charge utile n'avait besoin d'aucun import ni d'aucun agent en cours d'exécution. Un preinstall hook dans le manifeste du paquet exécutait node lib/setup.mjs, un loader obfusqué qui déposait le runtime Bun sur la machine et l'utilisait pour exécuter lib/Math_Symbol.js, un voleur d'identifiants et un ver auto-propagateur de 856 501 octets. Quiconque effectuait une simple installation de cette version l'exécutait. Parce qu'il s'agissait du tag latest du paquet, cette version était celle que résolvait une installation par défaut pendant qu'elle était active.

Ce qui rend cet enregistrement digne d'être conservé, ce n'est pas la taille de la charge utile — la famille Shai-Hulud a déjà été documentée. C'est la liste des cibles. Aux côtés des jetons npm et GitHub, des identifiants AWS, des secrets Kubernetes et Vault, des clés SSH, .env, des mots de passe de navigateur et des portefeuilles de cryptomonnaies, cette version a spécifiquement collecté la configuration des outils de développement IA et les fichiers MCP — ceux associés à Claude Code, Cursor, Kiro, Windsurf et Zed, selon Socket. Il a aussi écrit ses propres .claude/settings.json et .vscode/tasks.json dans les dépôts auxquels il pouvait accéder, afin que le code s'exécute à nouveau la prochaine fois qu'un développeur ouvrirait ce projet dans Claude Code ou VS Code. Il s'agit d'une attaque de chaîne d'approvisionnement visant précisément les machines sur lesquelles les gens construisent des agents.

Comment la version est sortie du compte d’un mainteneur

Socket, SafeDep et Aikido ont chacun publié leur propre analyse le 8 octobre, et leurs descriptions du mécanisme concordent. La note de confiance de l'archive pour cet enregistrement est A, il n'est pas marqué comme contesté, et rien de ce qui a été trouvé dans les sources ne contredit l'une ou l'autre de ces analyses.

L'analyse de SafeDep retrace huit commits sur la branche main du dépôt entre 01:20 et 03:57 UTC le 7 octobre, attribués à un compte administrateur du dépôt et effectués via l'interface web de GitHub. Le commit que l'archive cite comme le premier commit malveillant est 41b38f0, une révision de quatre fichiers du 7 octobre à 03:44:48 UTC, dont le message est "Add files via upload" et qui porte la signature verified de GitHub — GitHub signe les commits effectués dans son propre éditeur web, donc « verified » atteste de l'interface d'édition, pas de la personne. Ce commit n'a touché aucun fichier au-delà de typescript/lib/Math_Symbol.js et typescript/lib/setup.mjs, selon l'API GitHub : la charge utile, et rien d'autre.

SafeDep propose une lecture du comportement de l’opérateur, présentée comme sa propre inférence : la première modification du hook de préinstallation a cassé la syntaxe JSON du fichier, et un commit de réparation a suivi dix-neuf minutes plus tard, ce qu’elle décrit comme cohérent avec une personne modifiant des fichiers dans un navigateur plutôt qu’avec un pipeline scripté. Rien de ce qui a été publié par Socket, SafeDep, Aikido, StepSecurity ou Tensorlake n’indique la méthode par laquelle le compte du mainteneur a été détourné.

L'attaquant n'a pas eu besoin d'un jeton npm. SafeDep rapporte que le compte a lancé à la main le workflow publish_npm.yaml du projet le 8 octobre à 00:08 UTC ; le journal de compilation montre les fichiers de charge utile dans l'archive tar publiée. La publication utilisait npm trusted publishing, et les paquets frères tensorlake-native-* publiés lors de la même exécution portent une provenance SLSA valide — une provenance véritablement valide pour une compilation d'un code source empoisonné. La publication a eu lieu douze minutes après le lancement du workflow.

L’archive indique que le dépôt a été compromis pendant environ 20 heures avant que la publication ne se déclenche — l’écart que la chronologie ci-dessous présente commit par commit — ce qui concorde avec la déclaration de Tensorlake elle-même selon laquelle l’attaquant a publié « via notre propre workflow de publication ».

Ce que la charge utile a fait une fois exécutée

A screenshot of a card titled 'A credential stealer with an AI-tooling list', kicked 'tensorlake@0.5.144 · what the payload collected', subtitled 'Per Socket, SafeDep and Tensorlake's advisory — grouped by what each collector reached for'. Three columns of paired labels sit side by side. 'Publishing and source control' lists npm tokens from .npmrc, the token API and the OIDC exchange; GitHub tokens with a repository and workflow permission check; GitHub Actions secrets read from Runner.Worker memory; and cryptocurrency browser wallets across 14 extensions. 'AI agent tooling' lists ~/.claude.json for Claude Code settings; ~/.kiro/settings/mcp.json for Kiro MCP config; .cursor, Windsurf and Zed config and MCP files; and .claude/settings.json and .vscode/tasks.json written back into reachable repositories. 'Cloud, cluster and endpoint' lists AWS via STS, SSM Parameter Store and Secrets Manager; Kubernetes service-account tokens and kubeconfigs; HashiCorp Vault at 127.0.0.1:8200; SSH and GPG keys, .env files and Docker and CLI files; the Wallets row, wallet.dat, Exodus, Electrum and MetaMask; and saved passwords via a downloaded HackBrowserData binary. A footer reads 'Collector list per Socket's initial assessment, SafeDep's decoded payload analysis and the Tensorlake advisory (GHSA-8g63-53c9-65j2). Socket flags AI development tooling for Claude Code, Cursor, Kiro, Windsurf and Zed by name.'

L'évaluation de Socket identifie une collecte dans les fichiers locaux, les environnements CI, ainsi que dans les sources Kubernetes et Vault, plus l'exfiltration, la persistance et l'exécution de code fourni à distance. SafeDep a reproduit la charge utile décodée : lib/setup.mjs télécharge Bun 1.3.13 depuis la release officielle sur GitHub et l'utilise pour exécuter le ver, et il s'arrête sur les runners CI — la cible est une machine de développeur, pas un agent de build. SafeDep a aussi découvert que la charge utile contient une copie simple de ce même loader, qu'elle implante dans d'autres paquets et dépôts.

La charge utile exécute trois couches d'obfuscation, que SafeDep a décodées sans exécuter l'échantillon : un tableau de chaînes d'un obfuscateur, un chiffrement personnalisé utilisant le sel svksjrhjkcejg — identique à celui signalé dans la précédente keyv compromission — et AES-256-GCM sur onze fichiers intégrés, dont des scripts, des hooks et deux clés publiques RSA. Sa première ligne porte l'étiquette de build WORMTAG='tensrlake', mal orthographiée, ce qui permet à SafeDep de distinguer cette version d'une réinfection en cours.

Au-delà de la configuration des outils d'IA, la liste de collecte est vaste et spécifique : ~/.claude.json et ~/.kiro/settings/mcp.json pour les paramètres d'agent ; les clés SSH, les fichiers CLI cloud, .npmrc, .env, les fichiers Docker et Kubernetes, et l'historique du shell ; wallet.dat ainsi que les données Exodus, Electrum et Ledger Live et les extensions de portefeuille de navigateur telles que MetaMask et Phantom ; AWS STS, SSM Parameter Store et Secrets Manager ; les secrets GitHub Actions lus depuis Runner.Worker, la mémoire du processus ; et les jetons de compte de service Kubernetes et les jetons Vault. Il a récupéré un binaire par plateforme depuis iseekaigogo[.]com/hbd/<platform> et l'a exécuté avec les arguments "dump -b all -c password -f json" — des arguments qui correspondent à l'outil HackBrowserData, bien que SafeDep note que le binaire lui-même n'a pas été récupéré et qualifie l'identification d'inférence. Le ver, ajoute SafeDep, s'arrête sur les locales russes.

A screenshot of a card titled 'Persistence, resolution, and the wipe trigger', kicked 'tensorlake@0.5.144 · staying power', subtitled 'The release was removed in under two hours; the implant it installed was built to outlive that.' Three columns sit side by side. 'Persistence: gh-token-monitor' lists a systemd user service on Linux, a LaunchAgent com.user.gh-token-monitor on macOS, a scheduled task with a PowerShell monitor on Windows, and files left behind in ~/.local/bin, ~/.config and %LOCALAPPDATA%. 'Endpoint resolution order' lists, in numbered order, the primary endpoint iseekaigogo[.]com:443/router; an Ethereum contract 0xb614155Fd88...1Df091B9; the public RPC endpoints that contract is read through, given as ~30 by Socket and 35 by SafeDep; signed GitHub commits found by the string thebeautifulmarchoftime; and public dead-drop repositories described as 'Shai-Hulud: Here We Go Again'. 'The dead-man switch' lists a poll of api.github.com/user every 60s, for 24 hours, firing on a 40x response to that token, with the effect rm -rf ~/ (advisory: home dir), armed only if the account has no orgs, and the order that matters: remove monitor, then revoke. A footer reads 'Per Socket's blog and SafeDep's analysis, with the C2 endpoint, the contract and the gh-token-monitor service confirmed in Tensorlake's advisory. Socket and SafeDep give different counts of the public RPC endpoints the contract is read through.'

La persistance est délibérée. Socket a écrit à propos de la charge utile : « Cette combinaison étend le risque au-delà d'une simple clé API volée », ajoutant que « la persistance peut maintenir l'accès de l'attaquant après la suppression de la dépendance concernée. » SafeDep rapporte que le ver commit les fichiers de hook de Claude Code et VS Code en tant qu'auteur git claude, et déploie un workflow « Run Copilot » qui extrait tous les secrets du dépôt puis supprime l'exécution et la branche. L'avis de Tensorlake indique aux développeurs qui consultent leurs journaux de rechercher une branche nommée dependabot/github_actions/format/setup-formatter et des commits dont l'auteur est cette même identité.

Spread se propage grâce à des jetons volés plutôt qu'à une seconde vulnérabilité. Socket indique que le ver énumère les paquets associés à l'identité de publication d'une victime, construit une provenance Sigstore et republie des versions empoisonnées. SafeDep ajoute le détail qu'à l'intérieur de la CI, il ajoute une dépendance git et signe sa sortie avec Fulcio et Rekor — de sorte que la provenance du paquet qu'il publie ensuite est aussi valide que l'était celle de 0.5.144.

Les vingt heures avant la publication

L’écart entre le premier commit malveillant et la version publiée est la partie de la chronologie qui explique comment un paquet empoisonné a pu porter une provenance valide. Tous les horaires sont en UTC ; les heures de commit sont celles de SafeDep et de l’API GitHub, et les heures de publication, de retrait et d’avis sont celles de Tensorlake.

A screenshot of a card titled 'Compromised source in the repo, then one manual publish', kicked 'tensorlake@0.5.144 · the ~20-hour window', subtitled 'All times UTC. Commit times per SafeDep's timeline and the GitHub API; publish and removal times per the Tensorlake advisory.' Three columns sit side by side. '7 October 2026 — source, main branch' lists 01:20 setup.mjs and Math_Symbol.js added; 01:24 the preinstall script added; 01:43 package.json JSON repaired; 02:13 to 03:44 four loader and payload revisions; 03:44 commit 41b38f0, the payload commit; 03:57 the version set to 0.5.144. '8 October 2026 — release and removal' lists 00:08 publish_npm.yaml started by hand; 01:12:07 version 0.5.144 published to npm; 01:22:02 issue #1014 opened by StepSecurity; 01:23:10 flagged by Socket, about eleven minutes in; 02:54 the version removed, after roughly 1h 40m live; 20:24 version 0.5.145 published next. '9 October 2026 — disclosure closed' lists advisory GHSA-8g63-53c9-65j2; pull request #1016, source reverted and 0.5.145 cut; issue #1014 closed as completed; 0.5.143 as the last version stated unaffected; and no usage seen, per Tensorlake's user-agent telemetry. A footer reads 'The ~20-hour gap between the first rogue commit and the publish is SafeDep's commit timeline, and matches the archive's own upstream record.'

En recoupant les faits : les fichiers de charge utile ont atterri dans la branche principale du dépôt le 7 octobre, la version a été fixée à 0.5.144 à 03 h 57 ce matin-là, et rien n'a été publié avant qu'un compte ne déclenche manuellement le workflow de publication à 00 h 08 le lendemain. Le code source compromis est resté dans la branche pendant environ vingt heures avant que le pipeline du projet lui-même ne le compile. Voilà tout ce qui constitue l'attaque sur la chaîne de publication — aucune attestation falsifiée, aucun tarball republié. L'attestation de provenance décrivait fidèlement la compilation d'un code qui n'aurait jamais dû se trouver dans la branche.

Le commutateur homme mort

La partie de cet incident qui n'a aucun précédent dans les compromissions npm antérieures est ce qui se produit après qu'une victime a réagi correctement. Si le jeton GitHub volé appartient à un compte sans organisation, rapporte SafeDep, le ver installe gh-token-monitor : un service qui interroge api.github.com/user avec ce jeton volé toutes les 60 secondes, pendant 24 heures. Lorsque GitHub renvoie un 40x — comme il le fait dès que le jeton est révoqué —, le moniteur exécute un gestionnaire fourni par l'attaquant et supprime le répertoire personnel de l'utilisateur.

Socket décrit le même composant que l'utilisation d'un moniteur PowerShell qui persiste via une tâche planifiée ONLOGON, exécutant un gestionnaire fourni par l'attaquant via Invoke-Expression, et note que le code contient la chaîne littérale « IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner », observée dans les vagues précédentes de Shai-Hulud. SafeDep nomme le gestionnaire rm -rf ~/. La formulation de Socket est explicite : cela se déclenche indépendamment de l'endroit où la révocation a lieu ; le moniteur est local, et la révocation ne l'est pas. Les quatre sources — Socket, SafeDep, StepSecurity et l'avis de Tensorlake lui-même — donnent le même ordre d'opérations : supprimer d'abord le moniteur, puis révoquer.

Qu'est-ce qui est établi, et qu'est-ce qui n'est qu'inféré ?

L'archive ne recense aucun préjudice confirmé pour cette entrée, et les sources l'étayent plutôt que de simplement ne pas le contredire.

• Tensorlake déclare qu'aucune utilisation de cette version n'a été constatée.Son avis indique que l'entreprise surveille l'utilisation du SDK au moyen des en-têtes user-agent et qu'elle « n'a constaté aucune utilisation de cette version depuis sa publication ». Il s'agit d'une déclaration du fournisseur concernant sa propre télémétrie, et non d'un décompte audité.

• Le chiffre de téléchargements n'est pas un décompte d'infections. Socket indique que ce paquet totalise environ 12 000 téléchargements par semaine et précise que cela décrit l'usage global du paquet, et non les téléchargements de la version malveillante ; SafeDep cite environ 106 000 téléchargements par mois pour ce même paquet. Ni l'un ni l'autre ne prouve que la version 0.5.144 ait été exécutée quelque part. La fenêtre de version signalée par Socket et la fenêtre de deux heures de Tensorlake concordent avec une version retirée avant de s'être propagée.

• Les bugs inhérents à la charge utile ont pu la limiter. SafeDep en a trouvé deux : le chemin de propagation npm nécessite ./dist/Math_Symbol.js sur le disque et s’arrête sans lui, et le loader implanté cherche ai_init.js alors que le ver écrit math_init.js. SafeDep qualifie ces constatations de limites à la propagation, et l’article les présente comme la lecture du code par une analyse, et non comme une garantie que rien ne s’est propagé.

• L'attestation de provenance est réelle. Ce n'est pas une affirmation contestée : la provenance npm enregistre où un paquet a été construit et par quel workflow, et dans ce cas elle a enregistré la vérité — le propre workflow de publication du projet, construisant du code source empoisonné. L'avis de Tensorlake le dit en ces termes : « le paquet porte une provenance npm valide ». Socket formule le même point clairement lorsqu'il note que la provenance « indique où un paquet a été construit. Elle ne dit pas que le code est sûr. »

Plusieurs détails qui circulent sont des affirmations issues d’une seule analyse et doivent être lues comme telles. Le mécanisme précis par lequel l’attaquant a obtenu le compte du mainteneur n’a été publié par aucune des sociétés d’investigation ni par Tensorlake. L’adresse du contrat Ethereum et son rôle dans la résolution du point de terminaison figurent dans les comptes rendus de SafeDep et de Socket — Socket indique que la charge utile résout son point de terminaison via un contrat Ethereum utilisant environ 30 points de terminaison RPC publics avec un repli GitHub, tandis que SafeDep estime le nombre de RPC à 35 et rapporte que, le 8 octobre, le contrat renvoyait iseekaigogo.com — et Tensorlake confirme le point de terminaison et le contrat dans son avis. Le nombre exact de RPC diffère entre les deux analyses ; la conception, elle, ne diffère pas.

Une lacune dans les informations publiques mérite d'être énoncée clairement. Aucune des sources examinées pour cet article ne signale d'infection en aval confirmée — un autre paquet ou dépôt empoisonné par le ver à l'aide d'identifiants volés à celui-ci. Le code de propagation existe et SafeDep l'a lu ; on ignore s'il a fait une seconde victime.

Qu’est-ce qui a changé par la suite ?

La réponse a été plus rapide et mieux coordonnée que l’incident.

• La version a disparu. Elle a été en ligne de 01:12 à 02:54 UTC le 8 octobre, selon Tensorlake ; SafeDep indique que npm l'a retirée. En interrogeant directement le registre npm le 10 octobre pour cet article, 0.5.144 n'apparaît plus parmi les versions publiées du paquet, et le tag le plus récent du paquet est 0.5.148. L'avis de Tensorlake oriente les développeurs vers 0.5.143 comme la dernière version non affectée.

• L'avis est publié avec un correctif. L'avis GHSA-8g63-53c9-65j2 de Tensorlake indique que les paquets PyPI et crates.io de tensorlake et l'interface tensorlake en ligne de commande n'ont pas été affectés — seul le paquet npm l'a été — et que les versions 0.5.143 et antérieures ne sont pas affectées. SafeDep rapporte que les mainteneurs ont rétabli le code source dans la demande de tirage #1016 et publié 0.5.145.

• La discussion de divulgation est exceptionnellement complète.Le chercheur de StepSecurity, Ashish Kurmi, a ouvert le ticket GitHub #1014 à 01:22:02 UTC le 8 octobre — environ dix minutes après la publication — en pointant vers l'analyse de StepSecurity elle-même de ce qu'elle appelle le ver « hostage token ». Oliver Smith d'Aikido a commenté que la charge utile avait été introduite dans le commit 41b38f0 et l'a identifiée comme un nouveau voleur d'informations Shai-Hulud. Le mainteneur David Lorenzo — l'identité de mainteneur indiquée sur le paquet npm sous le nom de calavera — a répondu le 9 octobre en confirmant l'avis de sécurité et en clôturant le ticket comme terminé le jour même. Le titre du ticket lui-même suit les preuves : il a commencé comme un signalement « potentiellement malveillant » et a été renommé, au moment de sa clôture, pour décrire la charge utile comme malveillante sans réserve.

• La campagne à laquelle cela appartient est plus vaste que ce paquet. Socket, SafeDep et Aikido classent tous la publication de Tensorlake dans la vague ChainDrop / Mini Shai-Hulud documentée pour la première fois au début du mois d'août 2026, dont les victimes npm incluaient keyv, cacheable et des paquets associés. L'avis de Tensorlake dit la même chose, citant Socket, SafeDep et Aikido.

Socket conseille à toute personne ayant installé cette version de considérer l'hôte comme compromis : vérifier les fichiers de verrouillage, les manifestes et les journaux de build pour cette version ; supprimer le gh-token-monitorservice avant de révoquer tout jeton ; puis renouveler les identifiants depuis une machine propre. L'avis de Tensorlake ajoute le même ordre et suggère de vérifier les registres de paquets et les dépôts dans lesquels les jetons exposés pourraient écrire afin de détecter des versions inattendues. L'article d'Aikido conseille de définir ignore-scripts=true dans .npmrc pour bloquer les hooks d'installation, et considérer toute machine où le hook a été exécuté comme entièrement compromise.

Il s'agit d'une fiche, rapportée par des tiers et citée ici en leur nom. Les autres incidents documentés impliquant des agents, chacun avec ses propres sources, son degré de gravité, sa note de confiance et son indicateur de contestation, se trouvent dans l'archive des incidents liés à l'IA — ainsi, une compromission de la chaîne d'approvisionnement visant l'outillage des agents peut être lue aux côtés du reste de ce qui s'est réellement produit.