Shai-Hulud ist ein sich selbst verbreitender Wurm, der npm-Pakete infiziert, ein Software-Lieferkettenangriff, der jedes Opfer dazu bringt, ihn weiterzuverbreiten. Er läuft, wenn ein Paket installiert wird, stiehlt npm-Tokens, GitHub-Tokens und Cloud-Schlüssel vom Rechner und nutzt das npm-Token, um infizierte Versionen der anderen Pakete des Opfers zu veröffentlichen. Seit der ersten Welle im September 2025 ist er immer wieder zurückgekehrt, und im Mai 2026 veröffentlichten seine Urheber den Quellcode, sodass hinter jeder weiteren Welle eine andere Gruppe stecken kann.

Der größte Schaden entsteht im CI. Bei der Welle im November 2025 stellte Wiz fest, dass nur 23 % der Infektionen auf Entwicklerrechnern lagen: Die übrigen 77 % befanden sich in CI/CD-Pipelines, am häufigsten in GitHub Actions. Ein Build installiert Hunderte von Paketen, hält die Secrets zum Veröffentlichen und Deployen bereit und führt jedes Install-Skript aus, ohne dass jemand zusieht.

Wird eine neue Welle bekannt, stellt jedes Team dieselbe Frage: Hat einer unserer Builds eine infizierte Version installiert? Dieser Beitrag erklärt, was der Wurm tut, warum die übliche Antwort („das Lockfile prüfen“) unvollständig ist und wie Sie die Frage richtig beantworten.

Die bisherigen Wellen

WannNameWas anders war
September 2025Shai-HuludEin postinstall-Skript führte ein 3,6 MB großes bundle.js aus, nutzte TruffleHog, um Secrets zu finden, und veröffentlichte bis zu 20 Pakete des Maintainers neu. Mehr als 500 Pakete waren betroffen, darunter @ctrl/tinycolor und 18 @crowdstrike-Pakete.
November 2025Sha1-Hulud: The Second ComingWechselte zu preinstall, installierte die Bun-Runtime, um eine 10 MB große Schadsoftware auszuführen, versah bis zu 100 Pakete pro Maintainer mit einer Hintertür und registrierte infizierte Rechner als selbst gehostete GitHub-Runner. Pakete von Zapier, ENS, PostHog und Postman wurden getroffen; Wiz zählte mehr als 25.000 Repositories mit abgeflossenen Secrets.
Dezember 2025„3.0“-TestEine veränderte Version in einem einzigen Paket, ohne nennenswerte Verbreitung.
Mai 2026TanStack84 bösartige Versionen von 42 @tanstack-Paketen. Der Angreifer stahl nie ein npm-Token: Ein pull_request_target-Workflow, ein vergifteter Actions-Cache und ein aus dem Runner-Speicher gelesenes OIDC-Token ermöglichten die Veröffentlichung über TanStacks eigenes Trusted Publishing.
Mai 2026AntV639 Versionen von 323 Paketen. Auf Linux-Runnern von GitHub Actions las die Schadsoftware Secrets aus dem Speicher des Runner-Prozesses und umging so das Log-Masking. GitHub machte 61.274 npm-Schreib-Tokens ungültig.
Juni 2026Miasma32 @redhat-cloud-services-Pakete, veröffentlicht über injizierte Workflows mit gültiger Provenienz. Jede Infektion trug eine individuell verschlüsselte Schadsoftware, sodass hash-basierte Erkennung versagte.
August 2026CHAINDROPBeliebte Caching-Pakete wie keyv, cacheable und cache-manager, insgesamt waren Hunderte von Paketen betroffen. Der Wurm setzte sich über Konfigurationsdateien für Editoren und KI-Assistenten im Repository fest.

Die Schadsoftware kehrte im September 2026 erneut zurück, in vier Paketen, die den neuen Malware-Scan von npm beim Veröffentlichen überwanden.

Was der Wurm in einem CI-Job tut

Die Details ändern sich von Welle zu Welle, aber das Muster bleibt:

  1. Er läuft bei der Installation. Ein preinstall- oder postinstall-Skript wird ausgeführt, sobald npm install oder npm ci die infizierte Version erreicht. Nichts in Ihrem Code muss sie importieren.
  2. Er sammelt Secrets. Umgebungsvariablen, .npmrc-Tokens, Cloud-Zugangsdaten aus dem Metadatendienst und aus Secret Managern sowie alles, was TruffleHog auf der Festplatte findet. Spätere Wellen lasen direkt den Speicher des GitHub-Actions-Runners aus und erbeuteten so Secrets, die dem Schritt nie zugänglich waren und die das Masking in Logs verborgen hätte.
  3. Er schafft sie nach draußen. Frühe Wellen legten öffentliche GitHub-Repositories an, die nach dem Wurm benannt waren, und pushten einen Workflow, der jedes Repository-Secret an webhook.site sendete. Spätere Wellen nutzten Server der Angreifer und Messaging-Netzwerke.
  4. Er verbreitet sich und bleibt. Mit einem gestohlenen npm-Token veröffentlicht er infizierte Versionen anderer Pakete. Mit einem GitHub-Token fügt er Workflows hinzu, registriert selbst gehostete Runner oder platziert Konfigurationsdateien, die ausgeführt werden, wenn jemand das nächste Mal das Repository öffnet.
  5. Er verhält sich im CI anders. Die Schadsoftware vom November 2025 prüfte auf GITHUB_ACTIONS, BUILDKITE, CIRCLE_SHA1 und ähnliche Variablen und lief im CI synchron, sodass sie ihre Arbeit beendete, bevor der Build weiterging.

Warum das Lockfile die Frage nicht klärt

Der übliche Rat lautet, package-lock.json, yarn.lock oder pnpm-lock.yaml nach den betroffenen Versionen zu durchsuchen. Tun Sie das zuerst, aber ein sauberes Lockfile beweist keinen sauberen Build:

  • Außerhalb des Lockfiles installierte Pakete. npx some-tool installiert ein Paket, das keine Projektabhängigkeit ist, in den npm-Cache. npm install -g in einem Setup-Schritt, ein in einem Build-Skript installiertes Tool und ein npm install in einem Dockerfile umgehen alle das Lockfile des Projekts.
  • Das heutige Lockfile des Branches ist nicht das, das lief. Ein Build vor drei Wochen verwendete, was damals im Lockfile stand, auf dem Branch, der ihn ausgelöst hat. Pull Requests für Abhängigkeits-Updates lassen das CI gegen neue Versionen laufen, bevor jemand sie prüft.
  • Caches stellen wieder her, was zuvor installiert war. Ein wiederhergestelltes node_modules oder ein npm-Cache kann eine Version enthalten, die das aktuelle Lockfile nicht mehr nennt.
  • npm ci installiert, was im Lockfile steht. War die infizierte Version bereits festgeschrieben, übernimmt eine strikte Installation sie getreu.
  • Auch eine korrekt veröffentlichte Version kann bösartig sein. TanStack und Miasma wurden über ihre eigenen Release-Workflows kompromittiert, und die infizierten Versionen von Miasma trugen gültige Provenienz. Die Prüfung, ob eine Version ordnungsgemäß veröffentlicht wurde, sagt nichts darüber aus, ob sie sauber ist.

Was Sie tatsächlich brauchen, ist eine Aufzeichnung, welche Paketversionen jeder Build geladen hat, über jedes Repository und jeden Branch hinweg, für den gesamten Zeitraum, in dem eine infizierte Version in der Registry verfügbar war.

So prüfen Sie, ob Sie betroffen waren

  1. Besorgen Sie sich die Liste der betroffenen Versionen für die jeweilige Welle. Sicherheitsanbieter veröffentlichen sie innerhalb weniger Stunden; Datadog zum Beispiel pflegt ein Repository mit Indicators of Compromise und Listen von Paketen und Versionen.
  2. Durchsuchen Sie jedes Lockfile auf jedem Branch, auf dem im betreffenden Zeitraum das CI lief, nicht nur auf dem Default-Branch.
  3. Durchsuchen Sie die Builds selbst. Suchen Sie in CI-Logs nach setup_bun.js, bun_environment.js oder anderen in der Sicherheitsmeldung genannten Dateinamen sowie nach Installationsausgaben, die die betroffenen Versionen nennen. Wenn Sie SBOMs pro Build aufbewahren, durchsuchen Sie sie nach den Versionen.
  4. Prüfen Sie Ihre GitHub-Organisation auf neue, nach dem Wurm benannte Repositories, Workflow-Dateien, die Sie nicht geschrieben haben (zum Beispiel shai-hulud-workflow.yml, discussion.yaml oder formatter_*.yml), und selbst gehostete Runner, die Sie nicht registriert haben.
  5. Leeren Sie die Caches. Löschen Sie GitHub-Actions-Caches und den npm-Cache auf Entwicklerrechnern und selbst gehosteten Runnern, damit der nächste Build saubere Versionen installiert.
  6. Hat ein Build eine betroffene Version installiert, behandeln Sie den Job als kompromittiert. Rotieren Sie jedes Secret, das er lesen konnte: npm-Tokens, GitHub-Tokens, Cloud-Zugangsdaten und Deploy Keys. Weil spätere Wellen gestohlene Secrets in den Konten anderer Opfer veröffentlichten, sollten Sie nicht davon ausgehen, sicher zu sein, nur weil in Ihren eigenen nichts aufgetaucht ist.

So begrenzen Sie den Schaden beim nächsten Mal

  • Deaktivieren Sie Install-Skripte, wo Sie können. npm v12, im Juli 2026 erschienen, deaktiviert Install-Skripte standardmäßig und lässt Sie sie pro Paket erlauben. pnpm und Yarn bieten ähnliche Kontrollen.
  • Warten Sie, bevor Sie neue Versionen übernehmen. Die meisten infizierten Versionen werden innerhalb von Stunden oder Tagen gefunden und entfernt. Eine Wartezeit für Abhängigkeits-Updates, die sowohl Dependabot als auch Renovate unterstützen, hält sie aus Ihren Builds heraus.
  • Veröffentlichen Sie mit Trusted Publishing und kurzlebigen Tokens. npm hat im Dezember 2025 alle Classic Tokens widerrufen. Verwenden Sie OIDC-basiertes Trusted Publishing aus dem CI statt langlebiger Tokens, und trennen Sie Publish-Workflows von Build- und Test-Workflows.
  • Härten Sie die Workflows, die der Wurm missbraucht. Vermeiden Sie pull_request_target, pinnen Sie Actions auf Commit-SHAs und geben Sie GITHUB_TOKEN nur die Berechtigungen, die jeder Job braucht. Siehe GitHub Actions Secrets: Wie sie abfließen und wie Sie sie absichern.
  • Beschränken Sie, wohin sich Builds verbinden können. Ein Build braucht seine Paket-Registries und einige APIs. Kann er nur diese erreichen, schlagen die Aufrufe des Wurms an webhook.site oder seine eigenen Server fehl. Siehe wie eine Egress-Richtlinie die Exfiltration aus GitHub Actions stoppt. Das stoppt keine Exfiltration über einen Host, den der Build legitim nutzt, etwa GitHubs eigene API, auf die sich einige Wellen stützten.

Wo CRACI ins Spiel kommt

CRACI führt Ihre GitHub-Actions-Jobs auf eigenen Runnern aus (runs-on: craci), und ein paketbewusster Proxy auf dem Runner zeichnet jedes Paket auf, das jeder Job tatsächlich von npm und anderen Registries lädt, einschließlich aus Caches wiederhergestellter Pakete. Diese Aufzeichnung deckt die oben genannten blinden Flecken ab: npx-Tools, globale Installationen und Installationen in Build-Skripten erscheinen alle, weil die Aufzeichnung aus dem stammt, was der Job heruntergeladen hat, nicht aus dem, was ein Lockfile deklariert hat.

Wird eine neue Welle bekannt, schlagen Sie die betroffenen Versionen in den von CRACI aufgezeichneten Builds nach, statt sie aus Lockfiles und Logs zu rekonstruieren. Befunde werden über Builds und Repositories hinweg zusammengeführt, das Inventar von CRACI zeigt, welche Versionen in welche Produkte eingeflossen sind, und der Netzwerk-Trace jedes Jobs zeigt, wohin er sich verbunden hat, sodass Sie sehen, ob ein Job, der eine infizierte Version installiert hat, auch ein Ziel erreicht hat, das er nicht hätte erreichen sollen.

Die Egress-Richtlinie von CRACI begrenzt, wohin sich ein Job verbinden kann, wird vor dem Start des Jobs validiert, blockiert im Zweifel und alarmiert Sie, wenn ein Job versucht, ein Ziel zu erreichen, das die Richtlinie nicht erlaubt. CRACI bewertet Pakete nicht auf bösartiges Verhalten; kombinieren Sie es mit einem Tool zur Paketreputation, wenn Sie infizierte Versionen vor der Installation blockieren möchten.

Buchen Sie eine Demo, um die Paketaufzeichnung eines Ihrer eigenen Builds zu sehen.