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

Sie haben die Adresse geändert, nicht das Paket

Vierzehn Stunden lang wurden am 31. August 2026 manche Anfragen an registry.coder.com von Servern beantwortet, die ein Angreifer in Coders eigenen Cloudflare-Origin-Pool eingetragen hatte. Der Hostname stimmte, das Zertifikat war gültig, die Module trugen die Namen und Versionen, die sie immer trugen, und der zurückgelieferte Code machte sich auf die Suche nach SSH-Keys, OIDC-Tokens und Datenbankpasswörtern. Coder hat die Logs des Angreifers nicht, deshalb bittet das Advisory Sie, Ihren eigenen ausgehenden Verkehr zu prüfen. Das ist eine Frage, die die meisten Maschinen mit einem Coding-Agenten nicht beantworten können.

Der Hostname war Coders eigener. Das Zertifikat war gültig. Das Modul trug den Namen und die Version, die Ihr Template ohnehin gepinnt hatte. Vierzehn Stunden lang gehörten am 31. August einige der antwortenden Server jemand anderem.

Eine Plattform-Ingenieurin aktualisiert an einem Montagmorgen ein Workspace-Template. terraform init holt die Module, die das Template nennt (Docker, Zed, aider), von registry.coder.com, genau dort, wo sie schon im letzten Monat herkamen. Terraform druckt seine übliche grüne Wand und der Build läuft durch. Kein Werkzeug meldet irgendetwas, weder an diesem Morgen noch in diesem Monat.

Am 1. September veröffentlichte Coder GHSA-vx42-ghc9-gw65, eingestuft als kritisch mit CVSS 9.0. In seinen Worten: „Ein nicht identifizierter böswilliger Akteur verschaffte sich Zugang zu Coders Cloudflare-Infrastruktur und fügte dem Pool, den Coders Modul-Registry nutzt, nicht autorisierte IP-Adressen hinzu.“ Zwischen 07:35 und 21:45 UTC am Montag, dem 31. August, wurden manche Anfragen nach Coder-Modulen von Maschinen beantwortet, die der Angreifer kontrollierte. Coder baut selbst gehostete Cloud-Entwicklungsumgebungen; BleepingComputer nennt Dropbox, Palantir, Square, Mercedes-Benz, KKR, EnBW, US-Regierungsstellen und Rüstungsunternehmen unter seinen Nutzern.

Niemand hat das Paket angefasst

Jede Lieferketten-Gewohnheit eines sorgfältigen Teams ist eine Prüfung des Artefakts. Ist der Name richtig geschrieben. Ist der Herausgeber der, für den er sich ausgibt. Wurde diese Version gestern veröffentlicht. Passt der Hash im Lockfile noch. Lesen Sie das Diff, bevor Sie hochziehen.

Der Angreifer änderte, welche Maschinen antworteten, als Ihr Client nach der Adresse fragte, und ließ das Artefakt in Ruhe. Er veröffentlichte kein Paket, übernahm kein Maintainer-Konto, registrierte keinen Look-alike-Namen und bearbeitete nichts in Coders Registry. Der Name löste auf, wie er immer auflöst. Der TLS-Handshake kam gegen ein gültiges Zertifikat für den echten Hostnamen zustande. Das Modul hieß docker und zed und aider, weil das die Module sind, die ein Coder-Template anfragt, und es trug die Versionsnummer, die das Template ohnehin gepinnt hatte.

Was zurückkam, war ein anderes. Die vergifteten Kopien lieferten ein Shell-Skript namens dlp.sh mit, mit einem eigenen Build für die Module docker, aider, rstudio-server, windows-rdp und zed und einem eigenen Hash für jedes im Advisory. Das Advisory beschreibt die Nutzlast als Code, der „darauf ausgelegt ist, eine Reihe von Zugangsdaten zu identifizieren und diese an einen entfernten Server mit einem Domainnamen zu exfiltrieren, der einem von Coder regelmäßig genutzten ähnelt.“ Diese Domain ist coder-infra[.]com, registriert am 28. August, drei Tage bevor sie jemand benutzte, aufgelöst auf 199.91.220.205, mit dem Rückruf auf http://www[.]coder-infra[.]com/cli/check.

Die Liste dessen, worauf es abzielte, liest sich wie eine Inventur des Vertrauens einer Entwicklungsplattform: Provisioner-Umgebungsvariablen und -Secrets, OIDC-Tokens der Nutzer, SSH-Keys, externe Authentifizierungsdaten und coderd-Konfigurationsvariablen samt Datenbankpasswörtern und Zugangsdaten des Auth-Providers.

Sie mussten nichts Ungewöhnliches tun, um im Fenster zu sein. Laut Advisory: „Nutzer, die die Haupt-Modul-Registry von Coder (registry.coder.com) als Quelle ihrer Templates verwenden und Komponenten in dem Zeitraum aktualisiert haben, in dem dieser Code verfügbar war, könnten die bösartigen Pakete installiert haben.“ Ein Template anlegen, eine Template-Version hochziehen, einen Dry Run starten oder einen Workspace mit ausgeschaltetem Modul-Cache bauen ist ganz gewöhnliche Montagsarbeit.

31. August, 07:35–21:45 UTC — das vierzehnstündige Fensterder Build fragt anterraform initneues Template, Versions-sprung oder ein Dry Runder Name löst aufregistry.coder.comCloudflare, die echteEingangstüreine IP im Poolvom Angreifer ergänzt,liefert verändertesdlp.shder Rückrufcoder-infra[.]comregistriert am 28.8.,genutzt am 31.8.Was der Client prüfte, und was er vorfandder HostnameCoders eigener, keineLook-alike-Domaindas Zertifikatgültig, für den echtenNamen, an der echten Edgeder Modulnamedocker, zed, aider —die, die Sie angefragt habendie Versiondie, die im Templateschon stand. Nichts NeuesAlle vier Prüfungen bestehen. Welcher Server geantwortet hat, prüft auf der Client-Seite niemand.
Der Angreifer trug eigene IP-Adressen in den Origin-Pool hinter registry.coder.com ein, also waren Hostname, Zertifikat, Modulname und gepinnte Version allesamt echt, während die Bytes von woanders kamen. Ein vergifteter Build unterscheidet sich von einem sauberen in genau einer beobachtbaren Weise: einer ausgehenden Verbindung zu einer Domain, die drei Tage zuvor registriert wurde.

„Prüfen Sie Firewall-, Proxy-, DNS- und VPC-Flow-Logs“

Das ist Schritt fünf von Coders Gegenmaßnahmen und die lehrreichste Zeile des Advisories. Die Schritte eins bis vier sind Dinge, bei denen Coder helfen kann: SQL, um im Fenster gecachte Module zu finden, SQL, um sie zu löschen, gepatchte Releases (2.37.0, 2.36.4, 2.35.7 und 2.34.9, alle am 1. September draußen), und eine Liste zu rotierender Zugangsdaten. Schritt fünf greift über all das hinaus, weil die Server, die sagen würden, wen es getroffen hat, dem Angreifer gehören. Coder kann sie nicht sehen und sagt das auch: Es kann nicht abschließend jede kompromittierte Installation benennen.

Also kommt die Frage zu Ihnen zurück, und es ist eine Frage nach Belegen. Hat irgendetwas auf der Maschine, die dieses Modul ausgeführt hat, sich zu coder-infra[.]com verbunden? Für eine Produktions-VPC können die meisten Teams das innerhalb einer Stunde beantworten, weil im Plattform-Team schon jemand die Flow-Logs besitzt.

Stellen Sie die Frage jetzt für die Maschine, auf der Ihr Coding-Agent arbeitet. Ein Agent führt terraform init, npm install, make, einen Container-Build, eine Testsuite, ein im Repository gefundenes Skript und ein von einem Modul heruntergeladenes Binary aus. Jedes davon öffnet Verbindungen, und niemand schreibt sie dort mit, wo Sie sie eine Woche später durchsuchen könnten. Das Transkript des Agenten enthält die Tool-Calls, die das Modell gemacht hat. Es hat keine Zeile dafür, was ein Shell-Skript innerhalb eines Terraform-Moduls mit dem Netzwerk getan hat, weil das Modell das nie gesehen hat.

Eine Antwort auf Schritt fünf lässt sich im Nachhinein nicht kaufen. Entweder hat etwas die Anfrage aufgezeichnet, als sie die Maschine verließ, oder es gibt nichts zu durchsuchen.

Was das Skript gefunden hätte

Nehmen Sie die Nutzlast beim Wort und lassen Sie sie laufen. Sie durchkämmt die Umgebung nach Zugangsdaten, liest die SSH-Keys, greift die OIDC- und External-Auth-Tokens ab, sammelt die Konfigurationsvariablen mit dem Datenbankpasswort darin ein und postet alles zu coder-infra[.]com.

Bromure Agentic Coding gibt jedem Workspace seine eigene Ubuntu-VM unter Apples Hypervisor, mit einem host-seitigen Proxy als einzigem Weg des Gasts ins Netz. Lassen Sie dort dieselbe Suche laufen, und sie kommt mit einem vollständigen Satz Zugangsdaten zurück, allesamt Attrappen.

ANTHROPIC_API_KEY enthält einen sk-ant-api03-brm-…-Köder, GH_TOKEN einen ghp_-String der richtigen Länge, und ~/.git-credentials, ~/.docker/config.json und ~/.kube/config enthalten passende Platzhalter. Die Formate bleiben erhalten, also akzeptieren gh, docker und kubectl sie klaglos. Der Proxy hält Ihre echten Werte verschlüsselt auf Ihrem Mac und tauscht sie erst auf der Leitung ein, nachdem die Anfrage die VM verlassen hat, und nur dann, wenn sie an den Host geht, für den dieses Credential ausgestellt wurde. Kein Schalter schaltet das ab; so funktionieren Zugangsdaten in der App.

Die SSH-Keys sind der sauberste Fall: In der VM gibt es keine zu lesen. SSH_AUTH_SOCK zeigt auf eine Brücke über einen virtuellen Socket zu einem Agenten auf dem Host, und dieses Protokoll hat eine Anfrage, die „signiere diese Challenge“ bedeutet, und keine Anfrage, die „gib mir den Key“ bedeutet. ls ~/.ssh/id_* kommt leer zurück, während git push weiter funktioniert.

AWS verdient einen eigenen Absatz, weil Terraform genau dagegen läuft. Die ~/.aws/config der VM zeigt auf einen Helper, der Ihre echte Access-Key-ID zusammen mit einem falschen Secret Key ausgibt. Terraform, die AWS-SDKs, boto3 und die aws-CLI lesen diesen Helper ohne jede Einrichtung, signieren ihre Anfragen dann mit dem falschen Key und erzeugen eine wohlgeformte Signatur, die AWS ablehnen würde. Der Host entfernt diese Signatur und signiert auf dem Weg nach draußen mit dem echten Secret neu. Ein Skript, das die Umgebung nach AWS-Material abgrast, geht mit einer Key-ID und vierzig Zeichen nach Hause, die nichts authentifizieren.

Dann muss es seine Beute irgendwohin schicken, und genau dort sitzt die Egress-Firewall des Workspace. Stellen Sie Unmatched traffic auf Deny, und die VM erreicht die Hosts, die Sie gelistet haben, und sonst nichts. Durchgesetzt wird zweimal, an zwei Stellen, die der Gast nicht erreicht: Der virtuelle Netzwerk-Switch prüft jeden Flow nach Ziel-IP und per DNS mitgelesenem Hostnamen über alle Protokolle hinweg, und der Proxy prüft erneut nach dem TLS-Servernamen. Eine drei Tage alte Domain hat niemand auf diese Liste gesetzt. Regeländerungen erreichen laufende Sitzungen ohne Neustart, Sie können also deny any coder-infra[.]com ergänzen, während der Build noch läuft.

Falls der Verkehr doch hinausgeht, weil der Workspace in der voreingestellten Alles-erlaubt-Haltung läuft oder weil das Ziel gewöhnlich aussah, erledigen die Köder ihre zweite Aufgabe. Der Proxy durchsucht jede ausgehende Anfrage, Header und Body, nach einem gefälschten Credential, das irgendwohin unterwegs ist, wofür es nie ausgestellt wurde. Ein sk-ant-api03-brm-…-String, adressiert an coder-infra[.]com, ist nicht mehrdeutig. Der Proxy blockiert ihn mit HTTP 451, bevor ein Byte das Ziel erreicht, pausiert die VM und löst einen Alarm aus, der das Credential und beide Hosts benennt. Bromure markiert den Workspace als kompromittiert, und er startet erst wieder, wenn Sie seine Platte und sein Home gelöscht haben.

Sie bekommen Schritt fünf beantwortet, während er passiert, statt ihn eine Woche später aus Logs zu rekonstruieren.

Auf der Maschine, die das Modul ausführteterraform apply → dlp.shläuft mit den Rechten des Buildswas es einsammeltProvisioner-Env-Vars und -SecretsSSH-Keys, externe Auth-CredentialsOIDC-Tokens der Nutzercoderd-Config — Datenbankpasswortausgehend: ein HTTPS-POST von einem Build-Host, der den ganzen Tag HTTPS spricht„sind wir betroffen?“ → Logs, die es evtl. nicht gibtEchte Zugangsdaten gingen raus, nichts hielt die Anfrage fest.In einem Bromure-Agentic-Coding-Workspaceterraform apply → dlp.shläuft trotzdem — als ubuntu, im GastWegwerf-VM · der Host-Proxy ist der einzige Ausgangjede Env-Var und jedes Config-Token ist ein Köderkeine privaten Key-Bytes — der Host signiert per vsockAWS: echte Key-ID, falsches Secret, Host signiert neuEgress-Firewall Default-Deny — Rückruf abgelehntein Köder außerhalb seines Zwecks → HTTP 451,VM pausiert, Workspace als kompromittiert markiertjede Anfrage bekommt eine Zeile, egal welcher ProzessEs sammelte Köder, der Proxy lehnte ab und behielt die Zeile.
Dasselbe Modul, dasselbe dlp.sh, auf zwei Maschinen. Links findet es echte Umgebungsvariablen, echte SSH-Keys und echte Tokens, und sein Rückruf ist eine gewöhnliche HTTPS-Anfrage, die niemand mitgeschrieben hat. Rechts findet es Attrappen, keine privaten Key-Bytes und ein Signier-Secret, das nichts authentifiziert, sein Rückruf trifft auf eine Egress-Firewall im Default-Deny, und ein Köder, der an einen Host außerhalb seines Zwecks geht, stoppt die VM.

Ein Befehl beantwortet das Advisory

Der Host-Proxy schreibt für jede Anfrage aus der VM einen Metadatensatz, gleich welcher Prozess sie gestellt hat: Zeit, Host, Port, Methode, Pfad, Status, Latenz und Bytes, gemessen vor jedem Credential-Tausch. Stellen Sie den Session trace des Workspace auf Activity only, und Sie bekommen diesen Datensatz, ohne dass irgendein Request-Body gespeichert wird. Dann druckt bromure-cli trace hostnames my-workspace jeden einzelnen Host, den der Workspace kontaktiert hat, mit Zählern. Coders Schritt fünf, in einer Zeile, für die Maschine, auf der der Code lief.

Was die Engines entschieden haben

Die Security Timeline (Window → Security Timeline…) ist die separate Sicht darauf, was Bromures Engines entschieden haben: jedes Urteil der Egress-Firewall, jedes vom Proxy vermittelte Credential, jede Lieferketten-Blockade, farbcodiert und filterbar. Eine abgelehnte Verbindung zu einer vor drei Tagen registrierten Domain taucht dort als rote Firewall-Zeile auf, während der Build noch läuft, und nicht als Grep durch einen Monat Flow-Logs, nachdem Ihnen das Advisory eines anderen gesagt hat, wo Sie hinschauen sollen.

Der Gast erreicht genau eine Sache auf Ihrem Mac: einen geteilten Ordner, ein lebendiges Lese-Schreib-Fenster in Ihr echtes Dateisystem, das die Kompromittierungs-Löschung bewusst in Ruhe lässt. Teilen Sie das Projektverzeichnis statt Ihres Home-Verzeichnisses, und ein Skript auf der Jagd nach id_rsa und .env läuft durch ein Linux-Home voller Checkouts und Paket-Caches.

Wenn Sie die Quelle nicht prüfen können, beobachten Sie den Ausgang

Alle Provenienzprüfungen laufen auf einen Zug hinaus: vergleichen, was angekommen ist, mit etwas, dem Sie schon vertrauen. Ein Name, ein Herausgeber, eine Signatur, ein Hash im Lockfile. Der Zug funktioniert, solange der Angreifer etwas in den Kanal legen muss, mit dem Sie vergleichen können. Ein Angreifer, der ein Stück des Kanals hält, legt gar nichts hinein, und Coders Origin-Pool war ein Stück des Kanals. Die BdThemes-Kompromittierung im August hatte dieselbe Form: ein gekaperter Storage-Bucket veränderte einen JSON-Feed, den 350.000 WordPress-Seiten abriefen, ohne Release und ohne geänderte Datei.

Vierzehn Stunden sind ein Arbeitstag. Coder hat es bemerkt, am nächsten Morgen Patches ausgeliefert, Hashes und Indikatoren veröffentlicht und den Lesern gesagt, was zu prüfen ist. Das Advisory endet trotzdem damit, jeden Leser zu bitten, den eigenen ausgehenden Verkehr anzuschauen, weil die Aufzeichnung auf der anderen Seite weg ist.

Rotieren Sie die Keys, die das Advisory nennt, löschen Sie die gecachten Module, nehmen Sie den Patch. Entscheiden Sie dann, was der nächste erreichen darf, denn der nächste kommt durch einen Kanal, an dem Sie keinen Grund zum Zweifeln hatten. Installieren Sie Bromure Agentic Coding, geben Sie dem Build seine eigene Maschine, und halten Sie die Antwort bereit, wenn das Advisory erscheint.