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

Das Paket war wirklich Red Hats

Zwischen Ende Mai und dem 1. Juni 2026 schob ein Wurm namens Miasma credential-stehlenden Code in 32 Pakete unter dem npm-Scope @redhat-cloud-services — Red Hats eigener Namespace, ~117.000 wöchentliche Downloads, signiert von Red Hats echter Publishing-Pipeline. Es gab keinen Typosquat zu erwischen und keinen unbekannten Maintainer zu markieren. Das Vertrauenssignal war der Name des Anbieters auf dem Scope, und der Name des Anbieters ist genau das, worauf der Angreifer hereinritt. Hier ist, warum „bevorzuge seriöse Publisher“ aufhörte, eine Verteidigung zu sein, und was sich ändert, wenn der Agent, der die Installation ausführt, in einer Bromure-VM pro Profil lebt.

Die Supply-Chain-Angriffe, die wir im Mai aufschrieben, hatten ein Erkennungsmerkmal. Eine Git-URL, wo ein Versionsbereich stehen sollte. Eine Bun-Laufzeit, die aus dem Nichts auftauchte. Eine optionale Abhängigkeit, die absichtlich fehlschlug. Miasma hat kein Erkennungsmerkmal. Die Pakete waren @redhat-cloud-services/sources-client und einunddreißig seiner Geschwister — echt Red Hats, unter Red Hats eigenem npm-Scope, veröffentlicht von Red Hats eigener Pipeline mit gültiger Signatur. Nichts war gefälscht. Der Angreifer musste nichts fälschen. Der Name auf dem Scope war der ganze Exploit.

Ein Coding-Agent, der @redhat-cloud-services/vulnerabilities-client auflöst, zögert nicht, und Sie würden es auch nicht. Es ist eine First-Party-Abhängigkeit von einem der größten Enterprise-Anbieter der Branche. Es gibt keinen Maintainer zu prüfen, denn der Maintainer ist Red Hat. Es gibt keinen Namen, bei dem man nach einem vertauschten Buchstaben schielen müsste, denn der Name ist genau so geschrieben, wie er sein sollte. Jede Heuristik, die ein sorgfältiger Entwickler oder ein sorgfältiger Agent anwendet, bevor er npm install ausführt, gibt grün zurück. Also läuft die Installation, und ein preinstall-Hook feuert, und der Hook ist ein 4,2 Megabyte großer Blob aus obfuskiertem JavaScript, der anfängt, das Dateisystem nach Schlüsseln zu durchsuchen.

Der gesamte Vorfall ist eine Demonstration einer unbequemen Tatsache: seriöser Publisher ist keine Sicherheitskontrolle. Es fühlt sich wie eine an. Der Großteil der Supply-Chain-Ratschläge der letzten drei Jahre lehnt sich daran an. Und am 29. Mai kaufte es exakt nichts.

Was Miasma tat.

Die Kampagne — der String Miasma: The Spreading Blight taucht zuerst in einem Commit vom 29. Mai 2026 auf, laut OX Security — wurde von Aikido und OX Security erwischt und später von Socket, JFrog, Wiz, ReversingLabs, Microsoft und anderen analysiert. BleepingComputer und The Hacker News berichteten beide am 1. Juni darüber.

Die Form, auf ihre Mechanik heruntergebrochen:

  • Der Einstiegspunkt war ein kompromittierter GitHub-Account eines Red-Hat-Mitarbeiters, genutzt, um bösartige Commits in die @redhat-cloud-services-Quell-Repositories zu schieben.
  • Ein GitHub-Actions-Workflow trug ein _index.js-Script, das sich gegenüber npms Trusted-Publishing-Endpunkt mit einem OIDC-Token authentifizierte — derselbe schlüssellose Mechanismus, den npm jetzt gegenüber langlebigen Publish-Tokens empfiehlt. Aus npms Sicht veröffentlichte Red Hats CI Red Hats Pakete. Die Signatur war echt.
  • Die veröffentlichten Pakete trugen einen "preinstall": "node index.js"-Hook und eine obfuskierte Nutzlast von rund 4,2 MB.
  • Bei der Installation durchsuchte die Nutzlast nach GitHub-Actions- Secrets, AWS-Credentials, Google-Cloud-Credentials, Azure-Service- Principals, HashiCorp-Vault-Tokens, Kubernetes-Service-Account-Tokens, npm- und PyPI-Publishing-Tokens, SSH-Schlüsseln, Docker-Credentials, GPG-Schlüsseln und .env-Dateien — dann verschlüsselte und exfiltrierte sie, was immer sie fand.
  • Sie propagierte sich selbst, indem sie den gestohlenen Zugang nutzte, um über die GitHub-API zu committen, action.yml-Dateien über GraphQL las und neue Workflows über Mutations zurückschrieb, sodass die Änderungen, in Red Hats eigenen Worten im Commit-Log, verified und signed erschienen.

Insgesamt wurden 32 Pakete über 96 Versionen getroffen, Pakete mit rund 117.000 wöchentlichen Downloads, und die breitere Kampagne berührte 309 GitHub-Repositories. Sockets Einschätzung war, dass dies „effektiv eine Mini-Shai-Hulud-Kampagne ist: sie nutzt dieselben Kerntaktiken — Ausführung zur Installationszeit, Credential-Ernte, CI/CD-Targeting, verschlüsselte Exfiltration und potenzielle Downstream-Propagation." Red Hats Stellungnahme war, dass „die Pakete streng auf interne Entwicklung beschränkt sind und der bösartige Code nie zur Nutzung durch Kunden veröffentlicht wurde" — was wahr ist, und auch nicht viel Trost für die Entwickler und CI-Runner, die @redhat-cloud-services/* als transitive Abhängigkeit in dem Zeitfenster zogen, bevor die Pakete zurückgezogen wurden.

Die Verteidigung, die jeder empfiehlt, ist die, die brach.

Wir schrieben über einen ähnlichen Wurm vor drei Wochen — die TanStack-Kompromittierung, bei der das verräterische Zeichen ein prepare-Script war, das an einer gepinnten Git-URL hing, und eine Bun-Laufzeit, die aus dem Nichts auftauchte. Die ehrliche Lehre aus jenem Beitrag war: vertraue nicht der Lockfile, vertraue nicht der Signatur. Miasma ist die nächste Drehung derselben Schraube, und es lohnt sich, präzise zu sein, was anders ist, denn der Unterschied ist der ganze Punkt.

Der Standard-Supply-Chain-Hygiene-Stack hat drei Sprossen. Pinne deine Versionen. Verifiziere die Provenienz. Bevorzuge seriöse Publisher. Miasma marschiert geradewegs durch alle drei. Pinning bewirkt nichts, denn die bösartigen Versionen sind die veröffentlichten Versionen. Provenienz bewirkt nichts, denn die Provenienz ist gültig — der OIDC-Trusted-Publish-Flow war echt Red Hats CI, und downstream prägte der Wurm Workflow-Commits, die GitHub selbst als verified und signed markierte. Und die dritte Sprosse, bevorzuge seriöse Publisher, ist hier nicht nur besiegt — sie ist die Angriffsfläche. Die Reputation des @redhat-cloud-services-Scopes ist der Grund, warum die Pakete ohne zweiten Blick gezogen werden. Je vertrauenswürdiger der Namespace, desto nützlicher ist er für denjenigen, der ihn übernimmt.

Es gibt keine Variante von „lies das Paket sorgfältiger", die das erwischt. Das Paket ist in Ordnung. Das Paket ist Red Hats. Das Problem ist, dass Code-Ausführung zur Installationszeit mit den Ambient- Credentials des Entwicklers der Vertrag ist, und ein vertrauenswürdiger Name nichts daran ändert, was dieser Code anfassen kann, sobald er läuft.

ENTWICKLER-LAPTOP / CI-RUNNER — Host-Secrets sichtbar für alles, was die Installation ausführtRED HATS EIGENE PIPELINEkompromittierter MitarbeiterGitHub-Account → pushGH Actions: OIDC publishSignatur: GÜLTIGProvenienz: echtnpm: @redhat-cloud-servicessources-clientvulnerabilities-clientrbac-client … (32 Pakete)~117k wöchentl. Downloadspreinstall: node index.jsCODING-AGENT — kein Grund zu zögerntool: bashnpm i @redhat-cloud-services/sources-clientFirst-Party-Anbieter-Scope ⇒ grün↳ preinstall führt node index.js aus↳ 4,2 MB obfuskierte NutzlastHOST-DATEISYSTEM & ENV — alles echt, alles lesbar durch den preinstall-Hook~/.aws, GCP, Azure-SPNsCloud-Schlüssel (echt)Vault-Tokens, kube-SA-TokensCluster-Zugang (echt)~/.npmrc, PyPI-Tokenhier publishen (echt)~/.ssh, GPG-Keyringsignieren + pushen (echt)$GITHUB_TOKEN, .env-DateienCI-Secrets (echt)tar | encrypt | exfiltrateSELBST-PROPAGATIONgestohlener GitHub-Zugangaction.yml lesen (GraphQL)vergiftete Workflows committenerscheinen als: verified, signed309 Repos in der Kampagneder nächste Scope erbt das Vertrauen
Miasma auf einer Entwicklermaschine oder einem CI-Runner, der @redhat-cloud-services direkt auflöst. Ein kompromittierter Mitarbeiter-Account schiebt in Red Hats Repos; der OIDC-Trusted-Publish-Flow signiert die bösartigen Versionen als echt Red Hats; npm liefert sie mit gültiger Signatur aus. Der Agent installiert eine First-Party-Abhängigkeit ohne Grund zu zögern. Der preinstall-Hook führt node index.js aus — einen 4,2-MB-Blob — der den Host nach AWS, GCP, Azure, Vault, Kubernetes, npm/PyPI-Tokens, SSH- und GPG-Schlüsseln und .env-Dateien durchsucht, sie verschlüsselt, hinausschickt und gestohlenen GitHub-Zugang nutzt, um frische vergiftete Workflows zu committen, die als verified und signed erscheinen. Kein Typosquat, kein gefälschter Maintainer, kein Git-URL-Trick. Das Vertrauenssignal ist der Namespace, und der Namespace ist echt.

Dieselbe Installation innerhalb von Bromure Agentic Coding.

Bromure Agentic Coding lässt Ihren Coding-Agent innerhalb einer Linux-VM pro Profil laufen — eigener Kernel, eigenes Dateisystem, eigener Netzwerk-Stack, auf Apples Virtualization-Framework. Ein Profil ist ein kohärenter Arbeits-Scope: dieser Kunde, dieses interne Produkt, diese Open-Source-Bibliothek. Der Agent erledigt sein npm install-en dort drin, und der Host — Ihre echte Keychain, Ihre echten Cloud-Credentials, Ihre echten SSH-Schlüssel — liegt auf der anderen Seite einer hardware-erzwungenen Grenze, die der preinstall-Hook nicht überqueren kann.

Die Credentials leben nicht im Profil. Sie leben auf dem Host, hinter einem Credential-Broker. Wenn der Agent einen Commit pushen oder ein Paket veröffentlichen muss, liest er kein Token vom Gast-Dateisystem — es gibt keines zu lesen. Er bittet den Broker, über einen Unix-Domain-Socket, ein Credential in seinem Namen zu verwenden. Der Broker hält den echten Private Key der GitHub App, prägt ein kurzlebiges Installation-Token, gescopt auf das Repo, an dem der Agent ohnehin bereits arbeitete, und — für ein Profil, das so konfiguriert ist, dass es das verlangt — blendet einen Autorisierungs-Prompt ein, den der Entwickler auf dem Host beantwortet, bevor die Anfrage hinausgeht. Das Token wird vollständig auf der Host-Seite geprägt und an die ausgehende Anfrage angehängt; es betritt nie den Speicher oder die Disk des Gasts. Das Prinzip, das so alt ist wie ssh-agent: vermittle die Verwendung des Credentials, niemals seinen Wert.

Also gehen Sie Miasmas Durchsuchung durch diese Grenze. node index.js liest ~/.aws/credentials und findet einen Stub oder nichts. Es liest ~/.npmrc und findet kein Publish-Token. Es liest die Umgebung nach $GITHUB_TOKEN und findet nichts zu stehlen — das Installation-Token wird auf dem Host geprägt und verbraucht, nie in den Gast geschrieben, und der Broker prägt überhaupt nur eines, wenn der Entwickler den Autorisierungs-Prompt beantwortet. Der GPG-Keyring, der private SSH-Schlüssel, das Vault-Token, die kubeconfig: host-seitig, vermittelt, falls überhaupt exponiert, abwesend, falls nicht. Die 4,2-MB-Nutzlast läuft bis zum Ende durch, genau wie konstruiert. Sie exfiltriert nur einen Gast, der nie die Schlüssel des Entwicklers hielt.

AGENT AUF DEM HOSTnpm i @redhat-cloud-services/sources-clientpreinstall → node index.jsliest den Host direktECHTE CREDENTIALS — im Klartext gelesen~/.aws/credentialsAKIA… echt~/.npmrc _authTokennpm_… echt$GITHUB_TOKENghp_… echt~/.ssh/id_ed25519privater SchlüsselVault, kube-SA, GPGalle echt→ verschlüsseln + exfiltrieren→ neu veröffentlichen, propagierenAGENT IN EINER BROMURE-VM PRO PROFILGAST — was der preinstall-Hook sehen kann~/.aws, ~/.npmrcStub / abwesend~/.ssh, GPG, Vaultnicht auf Diskpushen nötig?→ Broker über Unix-Socket fragenAutorisierungs-Prompt → host-seitig genutzt(„gib mir den Schlüssel“ ist kein Verb)HOST — Broker hält die echten SchlüsselPrivate Key der GitHub Appprägt kurzlebige Tokens, host-seitigWert überquert nie die Grenze
Links: der Agent läuft auf dem Host, also liest der preinstall-Hook echte Schlüssel direkt — die Miasma-Kette vollendet sich. Rechts: der Agent läuft in einer Bromure-VM pro Profil. Die echten Credentials sitzen auf dem Host hinter einem Broker. Wenn der Agent legitim pushen muss, bittet er den Broker über einen Unix-Socket, und der Broker prägt ein kurzlebiges, repo-gescoptes Installation-Token, das host-seitig verwendet wird — gated hinter einem Autorisierungs-Prompt, den der Entwickler auf dem Host beantwortet — und das Token landet nie im Gast. Der bösartige preinstall-Hook, der das Gast-Dateisystem und die Umgebung liest, findet Stubs und überhaupt kein Token. Der Explosionsradius ist ein Profil, nicht die ganze Keychain des Entwicklers.

Der Push, den der Proxy weiterzuleiten verweigert.

Den Schlüssel zu stehlen ist nur die Hälfte von Miasma. Die andere Hälfte ist Propagation: er nutzte gestohlenen GitHub-Zugang, um vergiftete Workflows über die API zurückzucommitten, und das ist es, was eine schlechte Installation in 309 Repositories verwandelte. Der Broker behandelt den Diebstahl. Guardrails, hinzugefügt in Bromure Agentic Coding 2.0, behandeln den Missbrauch.

Guardrails sind eine host-seitige Policy-Engine, die innerhalb desselben MITM-Proxys lebt, durch den der vermittelte Traffic ohnehin schon fließt, sodass ein kompromittierter Agent im Gast nicht um sie herum routen kann. Jede Anfrage wird danach klassifiziert, was sie tatsächlich mit der Ressource tut, und jede Ressource — GitHub, AWS, Kubernetes, Docker-Registries, DigitalOcean, GitLab, Bitbucket, gehostete Datenbanken — kann auf aus, destruktive blockieren oder read-only gesetzt werden. Setzen Sie das GitHub-Guardrail eines Profils in den read-only-Modus, und ein git push — das git-receive-pack, das der Wurm braucht, um seinen Workflow zurückzuschreiben — gibt ein hartes 403 zurück, während git fetch weiter funktioniert. Ein DELETE gegen die Kubernetes-API, eine Manifest-Löschung in einer Registry, ein Terminate*-Aufruf an EC2: dieselbe Behandlung. Der Agent sieht einfach einen normalen API-Fehler. Miasmas Propagationsschritt ist ein Write, den der Proxy abzulehnen weiterzuleiten verweigert, ob der Agent je in die Nähe eines echten Credentials kam oder nicht.

Was ist mit der Persistenz?

Hier ist das Modell pro Profil ehrlich über einen Preis. Miasma ist ein Wurm; sein ganzer Ehrgeiz ist es, zurückzukommen. In einer Wegwerf-Disk-Fantasie wischt man das weg — die Disk ist nach der Aufgabe weg. Ein Bromure-Profil ist langlebig, also kann eine Nutzlast, die sich selbst in ein Startup-Script innerhalb des Profils schreibt, in die nächste Agent-Session in diesem Profil überleben. Wir werden nicht so tun, als wäre es anders.

Was diese Persistenz allerdings erbt, ist ein Gast ohne Host-Schlüssel und ein Broker, der nur in kurzlebigen, scope-limitierten Tokens spricht. Der Wurm wacht in derselben Sandbox auf, in der er starb. Er kann dieselben Stubs lesen. Er kann den Broker bitten, ein Credential für das eine Repo zu verwenden, für das dieses Profil autorisiert ist — und der Entwickler muss immer noch den Autorisierungs-Prompt beantworten, damit das irgendwohin führt, wobei Guardrails frei sind, den Push rundheraus zu verweigern. Er kann die Host-Keychain, die anderen Profile oder die Cloud-Credentials des Entwicklers nicht erreichen, denn die waren von Anfang an nie innerhalb der Grenze. Persistenz kauft fortgesetzte Präsenz in einer Box, in der nichts drin ist.

Und jedes bisschen davon — das preinstall, das feuert, node index.js, das einen mehrere Megabyte großen Blob lädt, die Datei, die in einen Startup-Pfad geschrieben wird, der Egress-Versuch — landet im Session-Trace auf Hypervisor-Ebene. Wenn Aikido am nächsten Morgen die Indikatoren veröffentlicht, ist die Frage „hat dieses Profil je Miasma ausgeführt?" ein grep, kein Incident-Response-Einsatz.

Wo dich das nicht rettet.

Der Broker-Scope ist das ganze Spiel.

Wenn ein Profil heute bereitgestellt ist, um in deinen npm-Scope zu veröffentlichen, und dieses Profil heute Miasma installiert, wird der Broker es veröffentlichen lassen. Brokering funktioniert, weil der Grant schmal und kurzlebig ist. Ein Profil, das nicht veröffentlichen muss, sollte es nicht können. Scope es mit Absicht.

Es begutachtet das Diff nicht.

Miasma propagierte, indem es Workflows committete, die verified und signed erschienen. Ein read-only GitHub-Guardrail blockiert den Push rundheraus — aber ein Profil, das legitim pushen muss, läuft im destruktive-blockieren-Modus, und Guardrails klassifizieren nach Methode, nicht danach, was im Diff steht. Weder Isolation noch ein Guardrail auf Methodenebene hält einen Agent davon ab, dazu überredet zu werden, einen vergifteten Workflow zu committen, den er pushen darf. Lies das Diff. Der Trace sagt dir, welche Diffs zu lesen sind.

Die Zwischenablage ist standardmäßig geteilt.

Bromure wird mit aktiviertem Host/Gast-Zwischenablage-Sharing geliefert, weil das Einfügen eines Stack-Traces in einen Chat etwas ist, was Menschen den ganzen Tag tun. Für ein sensibles Profil isolieren Sie die Zwischenablage. Die Kontrolle existiert; sie ist nur nicht der Standard.

Der Trace ist ein Audit-Log, kein IDS.

Der Session-Trace zeichnet das preinstall, den Blob, den Egress auf. Er entscheidet nicht von sich aus, dass das Ziel feindlich ist. Er erfasst genug, dass, sobald jemand den Indikator benennt, deine Antwort zwei Sekunden entfernt ist.

Der nächste Scope ist bereits vertrauenswürdig.

Die Lehre des TanStack-Wurms war, dass die Lockfile und die Signatur keine Verteidigungen sind. Miasma fügt das unbequeme Korollar hinzu: der Publisher auch nicht. Der @redhat-cloud-services-Scope tat nichts falsch, indem er vertrauenswürdig war — vertrauenswürdig zu sein ist der ganze Zweck eines Anbieter-Namespace, und es ist genau das, was ihn des Angriffs würdig machte. Die nächste Kampagne wird auf einem Scope hereinreiten, dem du genauso sehr vertraust, genauso gültig signiert, von einer Pipeline, die echt des Anbieters war, bis sie es nicht mehr war.

Das kannst du nicht beheben, indem du sorgfältiger vertraust. Du behebst es, indem du die Dinge so arrangierst, dass „welcher Scope hat das veröffentlicht" aufhört, die Frage zu sein, von der deine Keychain abhängt. Bromure Agentic Coding ist die Konfiguration, in der der Agent sein Installieren innerhalb einer VM pro Profil erledigt, die echten Credentials auf dem Host hinter einem Broker bleiben, und das Schlimmste, was ein preinstall-Hook anrichten kann, ist, eine Box zu exfiltrieren, die nie deine Schlüssel hielt. Es ist kostenlos, Open Source und heute ausgeliefert.