Sie zogen zuerst die sicheren Versionen zurück
Am 20. August veröffentlichte jemand ein vergiftetes arrayref auf crates.io und zog sechzehn Sekunden später die sicheren Versionen dahinter zurück. Cargos eigene Warnung zeigte danach auf die einzige moderne Version, die noch stand. Die Nutzlast steckte in einem Build-Skript, ein Type-Check des Projekts genügte also, um sie auszuführen. Bromure Agentic Coding macht aus der ganzen Operation ein Stoppuhr-Problem, das sie verliert: die vergifteten Crates lebten 86 Minuten, und die voreingestellte Altersschwelle liegt bei zwei Tagen.
Der Angreifer musste niemanden dazu überreden, Schadsoftware zu installieren. Er ließ die sauberen Versionen verschwinden und überließ den Rest dem Update-Rat der Registry selbst.
Um 07:15:00 UTC am 20. August erschien [email protected] auf crates.io unter dem Konto
seines echten Maintainers. Vierundzwanzig Sekunden später begann der Angreifer, die
Versionen 0.3.5 bis 0.3.9 zurückzuziehen: sechzehn Sekunden geskriptete Arbeit, um das
Regal dahinter zu leeren.
Ein Yank ist der Rückruf-Mechanismus von crates.io, der Hebel, den ein Maintainer zieht, wenn ein Release sich als kaputt herausstellt. Cargo reagiert darauf genau so, wie man es sich wünscht. Es warnt, dass eine Version in Ihrer Lockfile zurückgezogen wurde, und es weigert sich, bei einer frischen Auflösung eine zurückgezogene Version zu wählen. Jeder Ratschlag, der auf so eine Warnung folgt, sagt Ihnen: aktualisieren.
Also zog der Angreifer die sicheren Versionen zurück, ließ eine moderne Version stehen und ließ das Richtige zu tun Sie geradewegs hineinlaufen.
Neunzig Minuten, von Anfang bis Ende
Das Advisory der Rust Security Response WG und die von StepSecurity rekonstruierte Zeitleiste beschreiben eine Operation, die der Angreifer zwei Tage lang vorbereitet hatte.
Am 18. August um 01:25:58 registrierte er auf crates.io ein Konto namens dtolney, einen
Buchstaben neben David Tolnay, dessen Name auf den meisten Proc-Macro-Crates des
Ökosystems steht. Eine halbe Stunde später veröffentlichte er ein sauberes
[email protected], einen Typosquat von proc-macro2, das überhaupt nichts tat. Dieses
Release existierte nur, um dem Konto eine Historie zu geben.
Das echte kam fünf Stunden später. [email protected] ging am 20. August um 07:11:15
online, mit einer bösartigen build.rs an Bord. Vier Minuten danach ging
[email protected] vom Konto des rechtmäßigen Eigentümers hinaus, mit genau einer Änderung
darin: einer neuen Abhängigkeitszeile, die auf proc-macro1 ^1.0.107 zeigte. Die Security
Response WG geht behutsam mit dem Maintainer um, und das zu Recht. Sie „glauben nicht, dass
der Autor von arrayref böswillig handelt“, und merken an, dass „sein Rechner oder seine
Zugangsdaten wahrscheinlich kompromittiert sind“. [email protected] folgte um 07:34:07,
[email protected] um 07:37:49.
Dann war es fast so schnell vorbei, wie es begonnen hatte. Das Forschungsteam von Nextron
Systems fand die bösartige Crate und meldete sie. crates.io zog arrayref nach 86 Minuten,
internment nach 90 und append-only-vec nach 107 zurück, löschte sechs Crates des
Angreifers und sperrte das kompromittierte Konto. RustSec verzeichnete keinen Hinweis
darauf, dass jemand gegen eine vergiftete Version gebaut hat.
Neunzig Minuten sind eine schnelle Reaktion. Für alle, deren Build innerhalb des Fensters lief, kam sie trotzdem danach.
Nichts in der Bibliothek musste aufgerufen werden
Die Nutzlast lebt in einem Build-Skript, und Build-Skripte laufen zur Compile-Zeit mit
Ihren Rechten. The Hacker News berichtet, dass die Nutzlast bei
cargo build, cargo check und cargo test
feuerte. Das mittlere ist die vorsichtige Option: das, was Ihr Editor beim Speichern
ausführt, das, was Sie tippen, wenn Sie die Meinung des Compilers wollen, ohne etwas
laufen zu lassen. Hier genügte es.
Das Skript selbst ist klein und alles andere als subtil, sobald man es vor sich hat. Es
setzt https://23.254.165[.]112:9089/ aus Base64-Fragmenten wieder zusammen, schaltet die
TLS-Zertifikatsprüfung ab, lädt eine zu Ihrem Betriebssystem und Ihrer Architektur passende
Nutzlast herunter und startet sie abgekoppelt. Der nette Kniff ist
std::mem::forget(child), das den Handle fallen lässt, damit der Prozess Cargos Job Object
entkommt. Das Implantat läuft weiter, und der Compiler beendet sich mit einem grünen Build.
Was als Nächstes herunterkommt, ist ein Infostealer.
Wiz' Analyse
beschreibt, wie er per HTTPS POST an /49890878 funkt, Hostname, Benutzername,
Betriebssystem und installierte Anwendungen sammelt und die SQLite-Login-Datenbanken von
Chrome, Brave und Edge nach gespeicherten Zugangsdaten abfragt. Er persistiert als
Registry-Run-Schlüssel unter Windows, als LaunchAgent unter macOS und als
systemd-Benutzerdienst unter Linux, und wenn sein C2 verstummt, fällt er auf einen
Domain-Generation-Algorithmus zurück, der alle fünf Tage zehn .com-Domains erzeugt. Wiz
verbindet die Infrastruktur mit DPRK-Aktivität: derselbe /49890878-Endpunkt wie bei der
Mastra-npm-Kampagne, ein gemeinsamer Zertifikatsaussteller und der wiederholte Einsatz
eines Hostwinds-Adressbereichs.
Die ausgelieferten Builds decken x86_64-Linux, Windows und macOS ab, dazu aarch64-macOS. Letzteres ist ein Apple-Silicon-Laptop. Jemand hat darüber nachgedacht, wer Rust kompiliert.
Die Abhängigkeit, die Sie nie gewählt haben
arrayref hat 245 Millionen Downloads über die Zeit, und fast niemand hat es in eine
Cargo.toml geschrieben. StepSecurity zählte 406 abhängige Crate-Versionen, wobei
arrayref unter winit sitzt und damit unter egui und iced, unter blake3 und
blake2b_simd und unter einem guten Teil des Ethereum- und Solana-Werkzeugkastens.
internment und append-only-vec bringen zusammen weitere 19 Millionen Downloads mit.
Der Rat, Abhängigkeiten vor dem Hinzufügen zu prüfen, greift hier also nicht. Niemand hat sie hinzugefügt. Sie kam vier Ebenen tiefer an, unter einer Crate, die Sie tatsächlich hinzugefügt haben, in einem Lockfile-Diff, das vorbeiscrollt. Den Bibliothekscode zu lesen hätte ebenfalls nichts geholfen. Niemand hat den Quellcode der drei vergifteten Crates angefasst; die gesamte Änderung war eine Abhängigkeitszeile, die eine zwei Tage alte Crate nannte, veröffentlicht von einem Konto, dessen Name richtig aussah.
Warum ein Blog über agentisches Coden das angeht
Sehen Sie sich den Auslöser noch einmal an. Eine Yank-Warnung erscheint in der
Build-Ausgabe. Etwas beseitigt sie, indem es cargo update ausführt.
Dieses Etwas ist oft kein Mensch mehr. Eine Warnung wegzuräumen ist der Haushalt, den ein Agent auf dem Weg zu dem erledigt, worum Sie ihn gebeten haben: Er sieht eine zurückgezogene Version, er kennt die Behebung, er wendet die Behebung an. Er arbeitet, während Sie schlafen, in einer Schleife, über einen ganzen Rückstand hinweg. Er frischt Lockfiles auf, weil ein Build fehlgeschlagen ist, und fügt Crates hinzu, weil ein Feature eine brauchte.
Ein unbeaufsichtigt arbeitender Agent ist der Akteur, der am wahrscheinlichsten in einem 86-Minuten-Fenster war, und am unwahrscheinlichsten wegen einer unbekannten transitiven Abhängigkeit innehält, die von einem Namen veröffentlicht wurde, den er wiedererkennt. Geben Sie ihm ein kompromittiertes Maintainer-Konto, einen Typosquat einen Buchstaben neben David Tolnay und eine Registry-Warnung, die das Upgrade empfiehlt, und in der Kette bleibt nichts mehr übrig, was Urteilsvermögen abfangen könnte.
Das können Sie überleben, denn Urteilsvermögen ist ohnehin ein schlechter Ort, um das zu entscheiden.
Bromure macht daraus ein Stoppuhr-Problem
Jedes Bromure-Agentic-Coding-Profil kommt mit eingeschalteter Altersschwelle, gesetzt auf zwei Tage. Der MITM-Proxy auf dem Host sieht jeden Paketabruf, den die VM macht, über npm, PyPI, Cargo, RubyGems, Maven, NuGet, Go-Module und Packagist hinweg, und Versionen, die jünger als der Grenzwert sind, kommen nicht zurück.
Cargo bekommt die volle Behandlung. Bromure erkennt index.crates.io, crates.io und
static.crates.io und schreibt dann die Crate-Metadaten im Durchlauf um: Es entfernt zu
frische Versionen aus der Versionsliste und setzt max_version, max_stable_version und
newest_version auf das neueste Release, das den Filter überlebt. Cargos Resolver erfährt
nie, dass eine neuere Version existiert.
Jetzt legen Sie die Zahlen nebeneinander.
Nichts in dieser Operation wurde zwei Tage alt. [email protected] war vier Minuten alt,
als arrayref es als Abhängigkeit nannte. Keine der beiden Crates ist für einen Resolver
hinter der Schwelle sichtbar, also findet cargo update nichts Neues.
Sie werden das auf Ihrem Bildschirm sehen. Mit zurückgezogenen 0.3.5 bis 0.3.9 und
herausgefiltertem 0.3.10 kann eine frische Auflösung von arrayref mit der Unfähigkeit
zurückkommen, eine Version zu wählen — was ein Fehler ist statt eines stillen Erfolgs. Eine
bestehende Cargo.lock, die auf 0.3.9 festgenagelt ist, baut weiter, denn ein Yank
blockiert nur die neue Auflösung. So oder so verbringen Sie diesen Donnerstagmorgen damit,
eine Resolver-Meldung zu lesen, statt jede Zugangsberechtigung zu rotieren, die Sie
besitzen.
Bromure prüft ein zweites Mal, für den Fall, dass der Resolver seine Antwort schon hatte.
Es gleicht jeden Artefaktabruf, die .crate-Datei selbst, mit dem erfassten
Veröffentlichungszeitpunkt ab und liefert ein 451, das das Alter benennt: vor 41 Minuten
veröffentlicht, die Richtlinie verlangt mindestens 2 Tage. Ein warmer Metadaten-Cache
bringt dem Angreifer nichts.
Der erste Sprung des Droppers führt ins Leere
Nehmen wir an, Sie hätten die Uhr abgeschaltet. Jemand nimmt ein Paket aus, oder senkt den
Grenzwert für einen Nachmittag. Das Build-Skript muss immer noch 23.254.165.112 auf Port
9089 erreichen.
Jedes Bromure-Profil trägt seine eigene Egress-Richtlinie, geschrieben als kurzes Regelwerk im pf-Stil:
allow web crates.io
allow web static.crates.io
allow web index.crates.io
allow tcp api.anthropic.com:443
default deny
Ihr Mac setzt sie auf zwei Ebenen durch, die sich ein Regelwerk teilen: der virtuelle
Switch, der über alle Protokolle hinweg auf Ziel-IP und per DNS mitgelesenen Hostnamen
prüft, und der Proxy, der auf TLS-SNI und HTTP-Methode prüft. Eine nackte IP auf Port 9089
passt auf keine Regel in dieser Datei, fällt also auf default deny und die Verbindung
stirbt auf der Host-Seite der Grenze. Der Stage-2-DGA, zehn frische .com-Domains alle
fünf Tage, trifft auf dieselbe Wand, denn ein generierter Name ist immer noch ein Name, den
niemand auf die Liste gesetzt hat. Jede Ablehnung landet als egress.firewall-Zeile in
Window → Security Timeline, mit Host, IP, Port und Verdikt, geschrieben dorthin, wo
Code innerhalb der VM nicht hinreicht.
Da drin gibt es nichts zu stehlen
Lesen Sie die Wiederherstellungs-Checkliste, die mit diesem Vorfall kam: SSH-Schlüssel, Cloud-Token, API-Token und Signaturschlüssel rotieren. Den Registry-Cache leeren. CI-Cache-Layer löschen. Vendor-Verzeichnisse neu bauen. Auf jeder Maschine, die während des Fensters gebaut hat.
Ein Bromure-Workspace beantwortet den größten Teil dieser Liste mit einem leeren Raum.
Die echten Zugangsdaten betreten die VM nie. Der ANTHROPIC_API_KEY in diesem Gast ist ein
brm_…-Platzhalter. Die kubeconfig enthält Wegwerf-Client-Zertifikate. Die Auth für die
Container-Registry ist ein abgeleiteter Basic-String. Das GitHub-Token ist eine Fälschung,
und der SSH-Schlüssel ist ein pro Profil erzeugter ed25519-Schlüssel für diesen Workspace,
nicht der auf Ihrem Laptop. Der MITM-Proxy auf dem Host setzt den echten Wert in die
ausgehende Anfrage ein, und bei AWS geht er noch einen Schritt weiter: Der Host signiert die
SigV4-Anfrage mit dem echten Material neu, sodass alles, was am Proxy vorbeiläuft, von
Amazon eine InvalidSignatureException zurückbekommt statt eines funktionierenden Aufrufs.
Ein Infostealer, der diesen Gast nach Zugangsdaten durchkämmt, findet Zugangsdaten. Sie lassen sich parsen, sie sehen richtig aus, und außerhalb der Maschine sind sie nichts wert.
Der Rest seines Einkaufszettels liegt auf der falschen Seite des Hypervisors. Chrome, Brave
und Edge halten ihre Login-Datenbanken auf Ihrem Mac, in einem Benutzerprofil, zu dem der
Gast keinen Pfad hat. Der macOS-LaunchAgent, den er schreiben will — der Grund, warum er
einen aarch64-macOS-Build mitbrachte —, hat kein macOS, in das er schreiben könnte. Er kann
einen systemd-Benutzerdienst im Home des Gasts installieren, und diese Schicht hat einen
Knopf: Erase home… setzt das /home/ubuntu des Profils auf den Zustand direkt nach dem
Klonen zurück, und Reset to base… klont die System-Disk des Workspace erneut aus dem
schreibgeschützten Basis-Image.
Und die Inventur haben Sie schon
Die letzte Zeile jedes Remediation-Leitfadens zu diesem Vorfall ist ein Grep: jede
Cargo.lock auf jeder Maschine nach sechs Crate-Namen und drei Versionsstrings durchsuchen
und dann ~/.cargo/registry/cache nach den Tarballs. Das ist eine gute Anweisung und ein
elender Nachmittag, und sie funktioniert nur auf den Maschinen, die Sie noch haben.
Bromure hält die Antwort fest, während sie entsteht. Jeder Paketabruf durch den Proxy
schreibt eine supply_chain.fetch-Zeile in Window → Security Timeline mit Ökosystem,
Paket, Version, Ergebnis und dem Grund hinter dem Ergebnis, host-seitig, neben jedem
Zugangsdaten-Tausch und jedem Firewall-Verdikt. Wenn das nächste Advisory eine Version und
ein Zwei-Stunden-Fenster benennt, ist die Frage „hat hier irgendetwas das abgerufen?“ eine
Suche statt einer Expedition.
Bromure Enterprise Manager rollt denselben Strom über eine ganze Flotte auf, und genau in dieser Form kommt die Frage an: hat eine der neunzig Maschinen, auf denen über Nacht Agenten liefen, das gebaut.
Der Teil, den man behalten sollte
crates.io hat sich hier gut geschlagen. Neunzig Minuten von der Meldung bis zur Entfernung, sechs Angreifer-Crates weg, das kompromittierte Konto gesperrt, ein klares Advisory und kein Hinweis darauf, dass jemand getroffen wurde. Das Rust-Team hat sich außerdem Mühe gegeben, einem Maintainer, dessen Zugangsdaten gestohlen wurden, keine Schuld zu geben, was der richtige Instinkt ist und nicht immer der verbreitete.
Jemand wird den Mechanismus dennoch wiederverwenden, weil er billig ist und weil er clever ist. Ein Yank ist ein öffentlicher, sofortiger Ein-Zeilen-Hebel, der eine Registry dazu bringt, ein Upgrade zu empfehlen, und jedes Ökosystem hat einen. Was auch immer Ihre Builds ausführt, wird diese Empfehlung sehen und danach handeln, und dem, was heute danach handelt, fehlt oft ein Donnerstagmorgen an Kontext darüber, welche Crate welche ist.
Legen Sie die Entscheidung also irgendwohin, wo sie keinen Kontext braucht. Zwei Tage Tageslicht zwischen der Veröffentlichung einer Version und Ihrem Build sind kein Urteil über ein Paket, einen Maintainer oder einen Kontonamen, der einen Buchstaben danebenliegt. Sie sind eine Uhr, und diese Operation hatte neunzig Minuten.
Quellen: Rust Security Response WG, „Supply chain attack on arrayref“ (20. August 2026) · Wiz, „Rust Supply Chain Attack on arrayref: Significant Overlap with DPRK Campaigns“ (20. August 2026) · StepSecurity, „arrayref, internment, and append-only-vec Poisoned by the proc-macro1 Build-Time Dropper“ (20. August 2026) · The Hacker News, „Rust Supply Chain Attack Puts Build-Time Malware in Crates with 245 Million Downloads“ (20. August 2026) · BleepingComputer, „Hackers poison arrayref Rust crate to push infostealer malware“ (20. August 2026) · Semgrep, „Rust crates arrayref & append-only-vec compromised“ (20. August 2026)