Die Installation war sauber
npm führt seit Juli die Install-Skripte von Abhängigkeiten standardmäßig nicht mehr aus. Am 17. September hat Checkmarx eine Kampagne veröffentlicht, die darüber gar nicht erst streitet: Der Loader sitzt in `BTree.prototype.set`, also in genau der Methode, für die es die Bibliothek gibt, und feuert beim ersten Mal, wenn dort ein Schlüssel mit dem Wert 100 abgelegt wird. Elf Wochen lief das bei zwei Millionen Downloads pro Woche, und der Steuerkanal ist ein Smart Contract auf Ethereum Sepolia. Ein Coding-Agent ist das ideale Opfer, denn Code auszuführen ist die Arbeit. Bromure Agentic Coding entscheidet vor der Ausführung, was dieser Code erreichen darf.
Install-Skripte zu blockieren war richtig, und das npm-Ökosystem hat diese Entscheidung im Juli getroffen. Diese Kampagne hat ihre Ausführung auf die erste Benutzung des Pakets verlegt: Die Installation bleibt sauber, und Ihre Testsuite drückt ab.
Ihr Agent braucht eine sortierte Map. Er greift sich ein B-Baum-Paket von npm,
trägt es in die package.json ein und installiert es. Nichts läuft. npm 12
führt Install-Skripte von Abhängigkeiten nicht mehr aus, also gibt es kein
postinstall, kein preinstall, keinen Build-Hook, nichts, was eine Richtlinie
für Lifecycle-Skripte verweigern könnte. Der Agent schreibt den Code, der die
neue Abhängigkeit benutzt, schreibt einen Test, der ein paar hundert Zeilen
einfügt, und führt ihn aus.
Das hundertste Einfügen startet einen zweiten Node-Prozess.
Checkmarx hat die Kampagne am 17. September veröffentlicht,
recherchiert von Bruno Dias.
BleepingComputer berichtete am 20.
und InfoWorld am 21.
Das Paket heißt indexed-btree und ist so gebaut, dass man es für das echte
sorted-btree hält; als npm es am 3. September entfernte, zog es knapp zwei
Millionen Downloads pro Woche. In seiner bösartigen Form war es rund elf Wochen
online.
Die Verteidigung, die gerade erst fertig gebaut war
Im Juni hat die npm-CLI ihre Voreinstellung für Install-Skripte umgedreht, und
npm 12 hat diese Änderung im Juli ausgeliefert:
preinstall, install und postinstall einer Abhängigkeit laufen nur noch,
wenn das Projekt sie namentlich erlaubt, und auch Git- und Remote-URL-
Abhängigkeiten werden standardmäßig nicht mehr aufgelöst. Die dort zitierten
Zahlen von JFrog verorten diese drei Vektoren in rund 53 % der bösartigen
npm-Pakete des Vorjahres. Agent-Harnesses haben dieselbe Haltung eingenommen:
--ignore-scripts oder ein Proxy, der die Hooks aus dem Tarball entfernt, bevor
der Paketmanager ihn überhaupt sieht. Rechnet man vom Entfernungsdatum elf
Wochen zurück, fällt die erste bösartige Version von indexed-btree mitten in
diesen Rollout.
Also haben die Betreiber aufgehört, Install-Skripte zu benutzen. Der Loader lebt
in BTree.prototype.set, der Methode, für die es die Bibliothek gibt. Ist der
übergebene Schlüssel 100, startet der Code aus extended/sharedLoad.min.js
einen obfuskierten First-Stage-Loader als abgekoppelten Node-Prozess. Checkmarx
benennt präzise, warum man das schwer kommen sieht: Der Loader versteckt sich in
der eigenen Methode der Bibliothek, was in ihren Worten „ein gut gebauter Weg
ist, an den üblichen Taint-Analyse-Werkzeugen vorbeizukommen“.
Sehen Sie sich an, was ihn scharf macht: ein Schlüssel mit dem Wert 100, in einer Bibliothek, deren Zweck das Halten von Schlüsseln ist. Eine Bestenliste erreicht ihn, ein paginierter Index erreicht ihn, und ein Test, der Zeilen in einer Schleife einfügt, erreicht ihn in der hundertsten Runde. Benutzen Sie das Paket so, wie es die README sagt, und Sie machen es scharf.
Elf Wochen, und ein Repository, das sich gut las
Der Rest des Handwerks zielt auf die Prüfungen, die Sie selbst vornehmen würden.
Der Steuerkanal ist ein Smart Contract auf Ethereum Sepolia. Der Loader liest dort seine nächste Adresse, erzeugt ein X25519-Schlüsselpaar, nimmt den öffentlichen Schlüssel des Betreibers aus dem Contract, leitet per ECDH einen AES-Schlüssel ab und entschlüsselt zwei on-chain gespeicherte Chiffrate, um die zweite Stufe zusammenzusetzen. Checkmarx ist deutlich, was das bringt: „Diese Technik ist gegenüber Domain- oder IP-Takedowns widerstandsfähiger als klassische C2-Ansätze, da sie den Smart Contract als Zeiger auf eine neue Adresse nutzt, sobald die alte abgeschaltet wird.“ Nichts daran gibt Ihnen eine schlechte Domain für die Blockliste. Sie bekommen einen öffentlichen RPC-Endpunkt und eine Contract-Adresse, und die Betreiber können den Contract umlenken, ohne eine einzige installierte Kopie des Pakets anzufassen.
Das Projekt wirkte echt. Die Betreiber haben ein GitHub-Repository mit plausibler Commit-Historie samt passendem Entwicklerkonto aufgesetzt, und der Code in diesem Repository enthält den bösartigen Teil nicht. Der steckt nur im npm-Tarball. Den Quelltext auf GitHub zu lesen, der Rat, den alle geben, hätte bestätigt, dass mit dem Paket alles in Ordnung ist.
Auch das Alter hat für sie gearbeitet. Elf Wochen machen ein Paket bis zu Ihrer
Installation zu einer etablierten Abhängigkeit, ganz anders als das gekaperte
Release, das binnen Stunden zurückgezogen wird. Checkmarx zählte neun
Geschwisterpakete mit demselben Verhalten, btree-core allein über 1,9 Millionen
Downloads und die übrigen je in den Hunderttausenden, und schreibt, die Betreiber
kontrollierten eine Wallet mit 109 ETH.
Und nachdem die zweite Stufe gelandet ist, löscht der Loader seine eigenen Dateien und entfernt den Auslöser wieder aus dem Prototyp. Das Paket auf der Platte sieht wieder aus wie eine B-Baum-Bibliothek.
Code auszuführen ist die Arbeit
Jede Verteidigung oben setzt einen Moment voraus, in dem ein Mensch entscheidet, etwas auszuführen. Ein Agent nimmt diesen Moment weg.
Ein Coding-Agent installiert eine Abhängigkeit, schreibt Code dagegen, startet die Testsuite, liest den Fehler, ändert und startet erneut, dutzende Male pro Stunde, während Sie in einem Meeting sitzen, weil genau diese Schleife das ganze Wertversprechen ist. Sie lesen das hundertste Einfügen nicht. Der Agent hat auch mehr Anlass als Sie, genau die Daten zu erzeugen, die diesen Auslöser treffen: Er schreibt Fixtures in Schleifen, befüllt Tabellen mit fortlaufenden IDs und misst Performance mit ein paar tausend Zeilen. Lautet die Scharfschaltbedingung „jemand legt den Schlüssel 100 ab“, kommt ein Agent früher und öfter dorthin als ein Mensch, in einem Prozess, der alles hält, was die Sitzung hält.
Schutz zur Installationszeit deckt den Moment ab, in dem ein Paket ankommt. Ihr Agent verbringt dort eine Minute am Tag und den Rest damit, das Angekommene auszuführen.
Was der Loader braucht
Reduziert man die Kampagne, braucht sie zwei Dinge: irgendwohin zu kommen und etwas, das auf dem Rechner, auf dem sie aufgewacht ist, zu holen lohnt. Sie entscheiden beides, bevor sie läuft, und keine dieser Entscheidungen verlangt, das Paket zu erkennen.
Auf einem Entwickler-Laptop gehen beide zugunsten des Angreifers aus. Die
Aufklärung, die er verschickt — Hostname, Architektur, CPU, Speicher, Uptime —
beschreibt Ihren Rechner in Ihrem Netz. Der Prozess, den er startet, läuft als
Sie, mit Ihrer Shell, Ihren ~/.git-credentials, Ihrem GH_TOKEN, Ihrem
~/.aws, Ihrem SSH-Agenten. Seine drei Ziele sind slack.com,
api.telegram.org und ein öffentlicher Ethereum-RPC-Anbieter: zwei SaaS-Hosts,
mit denen die Hälfte Ihres Werkzeugkastens ohnehin spricht, und ein
Blockchain-Endpunkt, der nach Infrastruktur aussieht. Nichts an dieser Liste
liest sich in einem Netflow-Log bösartig.
Bromure Agentic Coding beantwortet beide Hälften auf dem Host, außerhalb der VM, in der der Agent läuft.
Die Zugangsdaten kommen zuerst, denn sie sind das Ziel. Jedes Geheimnis, das Sie
konfigurieren, existiert innerhalb der Workspace-VM als strukturerhaltender
Platzhalter (ein GitHub-Token ist ghp_ plus 36 Zeichen, ein Anthropic-Key
beginnt mit sk-ant-api03-brm-), abgeleitet aus dem echten Wert mit HKDF-SHA256
und einem Salt pro Installation, sodass gh, git und claude ihn akzeptieren
und er zwischen Sitzungen nie zu rotieren scheint. Die echten Werte bleiben
verschlüsselt auf Ihrem Mac, und der Host-Proxy setzt sie erst auf der Leitung
ein, nachdem die Anfrage die VM verlassen hat, und nur für den Host, für den das
Zugangsdatum geprägt wurde. Das Handbuch nennt die Invariante: Keine Datei, keine
Umgebungsvariable und kein Prozess innerhalb der VM enthält jemals einen echten
API-Key, ein echtes OAuth-Token, ein echtes AWS-Secret oder einen echten privaten
SSH-Schlüssel. Ein Stealer, der die Umgebung ausliest, sammelt Platzhalter ein.
Einen Platzhalter dorthin zu tragen, wo er nicht hingehört, ist selbst der Alarm. Der Proxy durchsucht jede ausgehende Anfrage nach Fälschungen, die an einen Host gehen, für den sie nicht geprägt wurden. Taucht eine auf, blockt der Proxy die Anfrage mit einem 451, das Ziel bekommt nichts, und Bromure pausiert die VM. Er hängt eine abgetrennte Sitzung zwangsweise wieder an, färbt den Rahmen rot und löst einen Alarm aus, der die Token-Vorschau, das Zugangsdatum, den zugehörigen Host und den Zielhost benennt. Sie können den Workspace herunterfahren oder Save for Investigation wählen, was die Systemplatte, das Home-Verzeichnis und jeden geteilten Ordner exportiert, bevor gestoppt wird. Dieser Loader löscht nach dem Handshake seine eigenen Dateien, wer also später nachsieht, findet eine aufgeräumte Platte. Das Pausieren mitten in der Anfrage gibt Ihnen die Maschine, bevor sie aufräumt.
Dann die Reichweite. Die Guardrails bringen pro Workspace eine Egress-Firewall mit: ein geordnetes Regelwerk im pf-Stil über jede Verbindung, die die VM öffnet, nach Host, CIDR, Protokoll, Port und, bei Web-Regeln, nach einzelner HTTP-Methode, mit einer Einstellung Unmatched traffic auf Allow oder Deny. Stellen Sie sie auf Deny, listen Sie die Registries und APIs auf, die die Arbeit braucht, und ein Loader, der nach einer Telegram-Bot-API oder einem Ethereum-RPC-Anbieter greift, bekommt auf der Leitung eine Absage. Zwei Schichten setzen das durch, damit der Gast an keiner vorbei kann: Der virtuelle Switch gleicht Regeln nach Ziel-IP und per DNS mitgelesenem Hostnamen ab, über TCP und UDP, und der Proxy gleicht sie erneut nach TLS-Servername und Methode ab. Der Switch leitet die Port-80- und Port-443-Flüsse der VM in den Proxy selbst um, es gibt also keine Proxy-Variable, die eine Payload abschalten könnte. Jedes Urteil landet als Firewall-Zeile in der Security Timeline, und Regeländerungen erreichen laufende Sitzungen ohne Neustart.
Wo der Kalender nicht hinreicht
Bromures Altersgrenze, standardmäßig bei zwei Tagen aktiv, gibt es für das Kaperungsmuster: ein kompromittiertes Maintainer-Konto, eine vergiftete Version veröffentlicht, das Ganze binnen Stunden entdeckt und zurückgezogen. Sie hat mehrere dieser Geschichten getragen. Elf Wochen überschreiten jede Grenze, die Sie setzen würden, hier fällt die Arbeit also anderen Schichten zu.
Der Supply-Chain-Bereich ist genau deshalb ein Stapel. Die Schicht für Pakete, die von Anfang an bösartig waren, ist die Paketfilterung: Der Block für kompromittierte Pakete von socket.dev deckt als Malware markierte Pakete, Typosquats, dubiose Install-Skripte und obfuskierten Code ab, und Depi ersetzt die npm-Registry durch einen vorgeprüften Spiegel. Beide laufen host-seitig, ihre API-Keys bleiben auf Ihrem Mac und werden nie in die VM exportiert, und beide stehen vor einer Installation statt dahinter. Eine Blockade kommt als HTTP 451 zurück, dessen Klartext-Body npm wortwörtlich ausgibt, sodass der Agent den Grund liest und darum herum arbeitet, statt hängen zu bleiben.
Kein Reputationsdienst erwischt alles, und ein Typosquat, der elf Wochen sauber lief, ist für alle der schwere Fall, und genau dort verdient sich der Rest des Stapels seinen Platz. Die Platzhalter-Zugangsdaten, der Kompromittierungsmelder und das Egress-Regelwerk entscheiden, was ein Paket nach der Installation erreichen darf, und keines davon muss das Paket erkennen. Sie decken Code ab, den noch niemand eingeordnet hat, einschließlich Code, den noch elf Wochen lang niemand einordnen wird.
Rechnen Sie mit der ersten Nutzung
Lifecycle-Skripte zu blockieren lohnt sich, und diese Kampagne umgeht es. Planen Sie mit einer Payload, die aufwacht, wenn Ihre Testsuite die Bibliothek aufruft.
Der Auslöser sind gültige Daten
Ein Schlüssel mit dem Wert 100 in einer Key-Value-Bibliothek. Keine Eingabe-Allowlist hilft, und ein Agent erzeugt diese Daten schneller als Sie.
Lassen Sie nichts zu stehlen übrig
Aufklärung, die eine VM beschreibt, und Tokens, die stromaufwärts nichts wert sind, machen aus einer geglückten Ausführung eine vergeudete.
Ziele im Voraus festlegen
Slack, Telegram und ein öffentlicher Ethereum-RPC-Endpunkt sind per Reputation nicht blockierbar. Eine Allowlist muss sie nicht erkennen.
Install-Skripte zu blockieren hat rund die Hälfte der bösartigen npm-Pakete aus
dem Spiel genommen. Deshalb mussten die Betreiber etwas derart Aufwendiges bauen
— ein gefälschtes Repository, einen als Arithmetik getarnten Auslöser und einen
Steuerkanal auf einer Blockchain —, um Code unterzubringen, für den vor einem
Jahr vier Zeilen in einer package.json genügten. Die Verteidigung hat
gewirkt, und der Preis des Angriffs ist gestiegen.
Die Annahme darunter hat sich nie bewegt. Sie behandeln eine Abhängigkeit weiterhin als sicher, sobald sie installiert ist, und ihr Ausführen als neutralen Akt. Für einen Coding-Agenten ist das Ausführen die Arbeit, tausendfach am Tag, ohne jemanden im Raum. Entscheiden Sie, was dieser Prozess halten und wohin er sich verbinden darf, bevor er läuft, und das hundertste Einfügen Ihres Agenten endet als blockierte Firewall-Zeile in der Security Timeline.