Knapp vorbei: Der Bitwarden-CLI-Vorfall und was wir daraus mitnehmen

Am 22. April 2026 wurde für gut 90 Minuten eine manipulierte Version des Bitwarden-CLI-Pakets über npm ausgeliefert. Warum wir knapp vorbeigekommen sind – und welche unspektakulären Dinge in unserem Setup dafür verantwortlich sind.

Am Abend des 22. April 2026 wurde ein kompromittiertes npm-Paket unter dem Namen @bitwarden/cli@2026.4.0 ausgeliefert. Laut dem offiziellen Statement von Bitwarden war die Version zwischen 17:57 und 19:30 Uhr (ET) auf npm verfügbar, bevor Bitwarden den Zugriff entzogen, das Release zurückgezogen und eine saubere Version 2026.4.1 nachgeschoben hat. Der Vorfall steht im Zusammenhang mit dem breiteren Supply-Chain-Vorfall rund um Checkmarx. Ein CVE für die betroffene Version wurde angekündigt.

Bitwarden selbst schreibt klar, dass weder Vault-Daten von Endnutzern noch die eigenen Produktionssysteme kompromittiert wurden. Betroffen ist ausschließlich, wer in dem rund 90-minütigen Fenster die CLI-Version 2026.4.0 frisch über npm installiert hat.

Bei Moselwal sind wir knapp vorbeigeschrammt – und das hat weniger mit Glück als mit ein paar sehr unspektakulären Entscheidungen in unserem Build- und Deployment-Setup zu tun. Dieser Post ordnet den Vorfall ein und beschreibt, was konkret gegriffen hat.

Was genau passiert ist

Kurz und ohne Dramatik: Über den Checkmarx-Angriffsvektor haben Angreifer ein Release des offiziellen Bitwarden-CLI-Pakets auf npm manipulieren und unter der regulären Versionsnummer 2026.4.0 veröffentlichen können. Das Paket blieb etwa eine Stunde und 33 Minuten verfügbar. Bitwardens Empfehlungen an betroffene Systeme lauten sinngemäß:

Das ist, sachlich betrachtet, ein Lehrbuchfall für Supply-Chain-Risiken in Paketregistries: Der legitime Code-Stand im Repository war nie kompromittiert, der Distributionspfad hingegen schon.

Warum wir die CLI überhaupt nutzen

Wir bei Moselwal betreiben einen eigenen Vaultwarden-Server – serverseitig API-kompatibel zu Bitwarden – und nutzen für automatisierbare Szenarien die Bitwarden-CLI. Dass wir dort überhaupt klassische Passwörter und API-Keys vorhalten, ist kein ideologischer Rückfall, sondern eine pragmatische Realität.

Bei einer ganzen Reihe von Kundensystemen und Drittdiensten gibt es schlicht keinen Weg um das geteilte Geheimnis herum: Ältere Content-Management-Systeme, Hosting-Panels, FTP/SFTP-Zugänge, Legacy-APIs, vereinzelte SaaS-Dashboards und Datenbank-Tools, die weder OPKSSH noch Passkeys unterstützen. Wo möglich ziehen wir auf OIDC, OPKSSH oder WebAuthn/Passkeys um. Wo nicht, bleibt das Passwort – und die beste Antwort ist dann ein zentral verwalteter, nachvollziehbarer Tresor statt eines Textfiles auf einem Entwicklerlaptop.

Konkret greifen wir auf zwei Wegen auf Vaultwarden zu: lokal auf Entwicklermaschinen, wenn Scripts oder Tooling Credentials brauchen, die nicht sinnvoll über Single Sign-On abbildbar sind, und in CI, um bei Deployments oder Wartungsaufgaben kurzlebige Zugriffe auf Kundensysteme zu ermöglichen, bei denen keine moderne Authentifizierung verfügbar ist. Genau an diesen beiden Stellen hätte das manipulierte @bitwarden/cli-Release potenziell richtig weh getan.

Warum Moselwal nicht getroffen wurde

Der zentrale Punkt ist einfach: In dem betroffenen Zeitfenster hat bei uns niemand npm install -g @bitwarden/cli auf einem Produktions- oder CI-Pfad ausgeführt. Das ist aber keine Zufallsbeobachtung, sondern Folge davon, wie unsere Pipelines gebaut sind.

Kein „latest“ in CI

Unser CI installiert nicht „irgendein“ Bitwarden-CLI. In den Repositories, die die CLI brauchen, ist sie als Dev-Dependency in package.json mit exakter Version eingetragen. Installiert wird ausschließlich über:

 

npm ci

 

npm ci installiert strikt aus package-lock.json und weigert sich, wenn Lockfile und package.json voneinander abweichen. Neue oder geänderte Versionen kommen damit nicht über einen spontanen Mirror-Drift in den Build, sondern nur über einen expliziten Commit im Lockfile. Zum Zeitpunkt des Vorfalls stand in unseren Lockfiles eine ältere, längst geprüfte Version – die kompromittierte 2026.4.0 hätte erst nach einem aktiven Update-Commit gezogen werden können.

Ein definierter Einstieg – lokal und in CI

Lokal läuft der Einstieg über ein Makefile. Das ist unspektakulär, hat aber einen angenehmen Nebeneffekt: Es gibt exakt einen dokumentierten Weg, wie Tooling installiert und aufgerufen wird. Ein typischer Auszug sieht bei uns etwa so aus:

 

.PHONY: tools bw-login bw-export

NODE_BIN := ./node_modules/.bin

tools:
	npm ci

bw-login: tools
	$(NODE_BIN)/bw config server vault.moselwal.internal
	$(NODE_BIN)/bw login --apikey

bw-export: bw-login
	$(NODE_BIN)/bw sync
	$(NODE_BIN)/bw get item "$(ITEM)"

 

In CI nutzen wir denselben Gedanken, nur anders verpackt: statt Makefiles kommen GitLab-CI-Components zum Einsatz. Die Components kapseln die gleiche Installation über npm ci und den gleichen kontrollierten Aufruf der CLI, sodass Entwickler- und Pipeline-Seite strukturell dasselbe machen. Das Ergebnis ist in beiden Welten identisch: ein definierter, versionsgebundener Aufruf statt einer spontanen Installation.

Updates ausschließlich über Renovate

Dependency-Updates passieren bei uns nicht ad hoc aus der Shell heraus. Wir lassen sie von Renovate fahren. Renovate öffnet Pull Requests gegen unsere Repositories, wenn eine neue Version eines Pakets erscheint, und aktualisiert package.json und package-lock.json zusammen. Gemerged wird in der Regel automatisch, sobald die Pipeline grün ist – der Clou liegt in der Karenzzeit davor: Neue Versionen halten wir eine Weile zurück, bevor Renovate sie überhaupt als Update anbietet. Ausnahme ist, wenn für die aktuell installierte Version ein kritischer oder hoher CVE bekannt wird; dann wird schnell aktualisiert.

Der Effekt im konkreten Fall: Selbst wenn Renovate die Version 2026.4.0 theoretisch hätte sehen können, wäre sie durch die Karenzzeit nicht als Update geflossen. Bis die Minimum-Age abgelaufen gewesen wäre, hatte Bitwarden die Version längst deprecated und 2026.4.1 nachgeliefert. Kein Automatismus hätte die kompromittierte Version unbemerkt in Produktion getragen.

Zusammengefasst

Die Schutzebenen, die gegriffen haben, sind bewusst prosaisch: Reproduzierbare Installationen über npm ci gegen ein committetes Lockfile. Ein definierter Einstieg – lokal per Makefile, in CI über GitLab-CI-Components. Versionsänderungen laufen über Renovate mit Karenzzeit, nicht aus der Shell. Keine globalen npm install -g-Aufrufe in Runnern. Nichts davon ist spektakulär, und genau das ist der Punkt.

Was wir trotzdem anpassen

Auch wenn wir nicht betroffen sind, nehmen wir ein paar Dinge aus dem Vorfall mit. Wir werden unsere npmrc-Einstellungen in Build-Umgebungen so härten, dass ignore-scripts=true zumindest für Tooling-Installationen der Default ist, außer wir brauchen Install-Skripte explizit. Wir prüfen, in welchen Repositories noch globale Installationen (npm install -g) in Dokumentation oder Skripten auftauchen, und ziehen diese auf lokale Dev-Dependencies um. Und wir schärfen unsere Renovate-Karenzzeit für besonders sensible Pakete nach – dort, wo bislang ein paar Stunden reichen, darf es ruhig „mindestens 48 Stunden alt“ sein, um Fälle wie diesen strukturell abzufangen.

Außerdem arbeiten wir kontinuierlich daran, die Zahl der Szenarien zu reduzieren, in denen überhaupt noch langlebige Passwörter nötig sind. OPKSSH und Passkeys dort, wo sie funktionieren, OIDC und kurzlebige Tokens dort, wo die Gegenseite es hergibt. Der Tresor bleibt – aber er soll so leer wie möglich sein.

Fazit

Der Vorfall vom 22. April 2026 ist ein guter, unaufgeregter Anlass, die eigenen Build- und Distributionswege anzuschauen. Wenn du Pakete global und ohne Lockfile installierst, Updates direkt aus der Shell fährst oder brandneue Releases ungefiltert automatisch merged, hattest du an diesem Abend echtes Risiko. Wenn du Lockfiles, reproduzierbare CI-Installationen und eine Karenzzeit zwischen Registry und Produktion einziehst, hat dich an diesem Abend strukturell sehr Unspektakuläres geschützt.

Bei uns hat dieser boring stack – npm ci, Makefile bzw. GitLab-CI-Components, Renovate mit Karenzzeit – seinen Zweck erfüllt. Es ist nicht besonders clever, aber es reicht, und das ist im Security-Kontext meistens das Beste, was man über ein Setup sagen kann.

Quelle des Vorfalls: Bitwarden Statement on Checkmarx Supply Chain Incident, Bitwarden Community Forums, 23. April 2026.