Ihr Projekt hat 847 direkte und transitive npm-Abhängigkeiten. Haben Sie letzte Woche genau geprüft, wie viele davon von Maintainern mit aktivierter 2FA stammen? Die meisten Teams auch nicht, bis ein vergiftetes Paket namens keyv sich in einen Wurm verwandelte, der innerhalb von Stunden über 440 npm-Pakete infizierte.
Der "Shai-Hulud"-Angriff (benannt nach den Sandwürmern aus Dune, und das aus gutem Grund, er hat sich durch alles gefressen, was ihm im Weg stand) ist nicht der erste npm-Supply-Chain-Angriff, aber einer der sich am schnellsten verbreitenden. Der Wurm kompromittierte nicht nur ein Paket; er replizierte sich selbst, indem er bösartigen Code in Pakete injizierte, die denselben Entwicklerkonten gehörten, und verwandelte so einen einzelnen Kompromittierungspunkt in ein kaskadenartiges Versagen über Hunderte von Paketen, von denen Millionen von Projekten abhängen.
Wenn Ihr Node.js- oder TypeScript-Projekt npm verwendet, ist das kein theoretisches Problem. Hier ist genau, was passiert ist, wie Sie prüfen können, ob Ihr Projekt betroffen ist, und die konkreten Schritte, um Ihre Supply Chain zu härten, bevor der nächste Wurm kommt.
Was tatsächlich passiert ist: Anatomie des Shai-Hulud-Wurms
Der Angriff zielte auf keyv, eine weit verbreitete Key-Value-Speicherbibliothek mit über 12 Millionen wöchentlichen Downloads auf npm. Aber keyv war nicht das einzige Opfer, es war der Einstiegspunkt.
So funktionierte die Kettenreaktion:
Schritt 1: Erste Kompromittierung
Der Angreifer erlangte Zugriff auf das npm-Konto eines Maintainers des keyv-Pakets. Die genaue Methode wurde öffentlich nicht bestätigt, aber npm-Kontokompromittierungen passieren typischerweise durch Credential-Reuse, Phishing oder geleakte Tokens in CI-Logs.
Schritt 2: Bösartige Payload veröffentlicht
Eine neue Version von keyv wurde veröffentlicht (der Wurm veränderte nicht die bestehende Git-Historie, was es schwerer machte, ihn bei einer oberflächlichen Prüfung zu entdecken). Diese Version enthielt ein Postinstall-Skript, einen Shell-Befehl, den npm automatisch nach npm install ausführt.
Schritt 3: Selbstreplikation (Das "Wurm"-Verhalten)
Das Postinstall-Skript exfiltrierte nicht nur Daten. Es tat etwas Schlimmeres: Es durchsuchte den lokalen Rechner nach npm-Authentifizierungstokens und nutzte diese Tokens, um neue bösartige Versionen jedes Pakets zu veröffentlichen, das das kompromittierte Konto kontrollierte. Das ist das "Wurm"-Verhalten, es machte aus einem Opfer 440+.
// Simplified reconstruction of the worm's logic (NOT actual malicious code)
// This illustrates conceptually how the attack spread
// Step A: Read the npm token from the environment or local config
const npmToken = process.env.NPM_TOKEN || readNpmrcToken();
// Step B: Fetch all packages owned by this token's account
const ownedPackages = await fetch(`https://registry.npmjs.org/-/user/.../package?...`, {
headers: { Authorization: `Bearer ${npmToken}` }
});
// Step C: Inject the same malicious postinstall into NEW versions
for (const pkg of ownedPackages) {
await publishNewVersion(pkg, maliciousPostinstall);
}
Schritt 4: Credential-Harvesting und Exfiltration
Die Payload durchsuchte den Host-Rechner außerdem nach:
.npmrc-Dateien (mit npm-Tokens)- Umgebungsvariablen mit Zugangsdaten (
NPM_TOKEN,NODE_AUTH_TOKEN) - CI/CD-Secrets (GitHub Actions, GitLab CI)
- Lokale Git-Konfigurationen und SSH-Keys
Diese Zugangsdaten wurden an einen Remote-Server exfiltriert, was dem Angreifer Zugriff auf weitere Konten verschaffte, und der Zyklus wiederholte sich.
Warum dieser Angriff so effektiv war
Die Geschwindigkeit war die Waffe. Traditionelle Supply-Chain-Angriffe erfordern, dass der Angreifer jedes Paket manuell kontrolliert. Der Shai-Hulud-Wurm automatisierte die Verbreitung, was bedeutete, dass die Angriffsfläche mit jedem neu kompromittierten Konto exponentiell wuchs. Innerhalb von Stunden waren über 440 Pakete infiziert.
Postinstall-Skripte werden still ausgeführt. Wenn Sie npm install ausführen, wird das Feld scripts.postinstall in der package.json eines Pakets automatisch ausgeführt. Die meisten Entwickler prüfen diese Skripte nicht, und die meisten CI-Pipelines führen sie ohne Rückfrage aus.
So prüfen Sie, ob Ihr Projekt betroffen ist
Warten Sie nicht auf einen Dependabot-Alert. Führen Sie diese Prüfungen jetzt durch.
Prüfung 1: Durchsuchen Sie Ihre Lockfile nach betroffenen Paketen
Ihre package-lock.json zeichnet die exakt installierten Versionen auf. Der Angriff betraf bestimmte Versionsbereiche, daher sind Lockfiles die Quelle der Wahrheit.
# Search for keyv and related packages in your lockfile
grep -i "keyv" package-lock.json
# More comprehensive: check the npm advisory list
npm audit --json 2>/dev/null | grep -i "supply\|keyv\|worm\|shai"
# If you use yarn
yarn why @keyv/redis
yarn why keyv
Wenn Ihre Lockfile eine betroffene Version enthält und Ihr Projekt seit der Kompromittierung installiert wurde (auch lokal), behandeln Sie den Rechner selbst als potenziell kompromittiert.
Prüfung 2: Überprüfen Sie die Integrität Ihrer installierten Pakete
npm pflegt einen Integritäts-Hash (SHA-512) für jedes installierte Paket. Wenn die veröffentlichte Version durch eine bösartige ersetzt wurde, die inzwischen entfernt wurde, stimmen Ihre Integritäts-Hashes möglicherweise nicht mit der Registry überein.
# Verify installed packages against the registry
npm ls --all
# Check for any "extraneous" packages not in your lockfile
npm ls --all 2>&1 | grep "extraneous"
# Force a clean reinstall to pull only current (clean) versions
rm -rf node_modules
npm ci # NOT npm install, ci respects the lockfile exactly
Kritischer Unterschied: npm ci installiert exakt das, was Ihre Lockfile vorgibt. npm install kann neue Versionen auflösen und die Lockfile aktualisieren, wodurch möglicherweise eine kompromittierte Version gezogen wird, wenn gerade eine veröffentlicht wurde.
Prüfung 3: Prüfen Sie Ihre CI/CD auf Token-Exposition
Wenn der Wurm in Ihrer CI-Umgebung lief, hat er möglicherweise Tokens aus Umgebungsvariablen geerntet.
# GitHub Actions: check your repository's Secrets and Environment variables
gh secret list
gh var list
# GitLab CI: review masked variables
# Navigate to Settings → CI/CD → Variables
# Check if any npm token was rotated after the attack date
# If not: rotate it NOW, no matter what
Das während des Angriffs veröffentlichte npm-Advisory enthielt spezifische Versionsnummern. Ihr Sicherheitsteam sollte diese gegen jedes Projekt in Ihrer Organisation abgleichen, nicht nur gegen die, die Sie aktiv entwickeln, sondern auch gegen archivierte Repositories, interne Tools und CI-Skripte, die möglicherweise noch laufen.
Das eigentliche Problem: Warum npm-Supply-Chain-Angriffe immer wieder passieren
Der keyv-Wurm ist ein Symptom, keine Anomalie. Dies ist der dritte große npm-Supply-Chain-Angriff der letzten Jahre (nach den Vorfällen um ua-parser-js, colors und event-stream), und das Muster ist immer dasselbe:
-
Abhängigkeit mit einem einzigen Maintainer und massiver Verbreitung.
keyvhatte 12M+ wöchentliche Downloads, wurde aber von ein oder zwei Personen gepflegt. Ein kompromittiertes Konto bedeutet, dass Tausende von Downstream-Projekten gefährdet sind. -
Automatische Postinstall-Ausführung. npm führt Skripte mit denselben Rechten wie Ihre Shell aus, einschließlich Zugriff auf Umgebungsvariablen, Dateisysteme und Netzwerk. Das ist by design, kein Bug, aber es ist eine massive Angriffsfläche, die die meisten Teams nicht konfigurieren oder einschränken.
-
Kein Code-Review für veröffentlichte Pakete. Anders als bei Ihrem eigenen Code lesen Sie den Quellcode eines npm-Pakets vor der Installation fast nie. Selbst mit gepinnten Versionen erfolgt die erste Installation einer neuen Version ohne Review.
-
Credential-Sprawl. Die Fähigkeit des Wurms zur Selbstreplikation hing davon ab, npm-Tokens in der Umgebung zu finden. In den meisten CI-Setups sind npm-Tokens für jeden Build-Schritt verfügbar, einschließlich jedes bösartigen Postinstall-Skripts.
Das fügt sich in ein breiteres Muster ein, das wir in unserer Analyse der Supply-Chain-Schwachstellen des Cursor-KI-Editors behandelt haben, wo ein kompromittiertes Entwicklungstool ebenfalls CI/CD-Pipelines gefährdete. Der Angriffsvektor ist anders, aber die zugrunde liegende Schwäche ist identisch: Tools, die mit erhöhten Rechten und breitem Zugriff laufen, werden ausgenutzt.
Härtung Ihrer npm-Supply-Chain: Ein praktisches Runbook
Das sind keine theoretischen Empfehlungen. Jede einzelne ist eine konkrete Änderung, die Sie diese Woche in Ihrem aktuellen Projekt umsetzen können.
1. Postinstall-Skripte absichern
Das ist die Änderung mit der höchsten Wirkung. Sie können Lifecycle-Skripte für alle Pakete standardmäßig deaktivieren:
// .npmrc, add this to every project root
ignore-scripts=true
Aktivieren Sie dann selektiv Abhängigkeiten wieder, die legitimerweise Lifecycle-Hooks benötigen:
# After npm ci with ignore-scripts=true, manually run only what you need
npx --yes node-gyp rebuild # For native modules like better-sqlite3
npm rebuild --foreground-scripts # For specific packages that need it
Der Trade-off: Einige Pakete benötigen legitimerweise Postinstall-Skripte (z. B. native C++-Bindings wie node-sass, sharp oder better-sqlite3). Dokumentieren Sie, welche Pakete sie benötigen und warum. Alles andere sollte auf Konfigurationsebene blockiert werden.
// In .npmrc for a project that needs sharp:
ignore-scripts=true
# Whitelist specific packages (configure in package.json overrides or use allowlist tools)
2. Überall npm ci statt npm install verwenden
# Dockerfile, always use ci, never install
COPY package*.json ./
RUN npm ci --ignore-scripts
COPY . .
Das ist nicht optional. npm install kann Ihre Lockfile verändern und ungeprüfte Versionen ziehen. npm ci installiert exakt das, was Ihre Lockfile vorgibt, und schlägt fehl, wenn package.json nicht mit package-lock.json übereinstimmt.
3. Jedes Paket mit einer Lockfile pinnen, und Auditing
# Generate a Software Bill of Materials (SBOM) for your project
npx @cyclonedx/cyclonedx-npm --output-file sbom.json
# Or use npm's built-in audit for known vulnerabilities
npm audit --audit-level=high
# Automated check in CI (exit code 1 on vulnerabilities found)
npm audit --audit-level=high || exit 1
Speichern Sie Ihre package-lock.json in der Versionskontrolle. Führen Sie npm audit in CI aus. Blockieren Sie Merges bei kritischen Advisories. So werden bekannte verwundbare Versionen abgefangen, bevor sie in Produktion gehen.
4. Eine Proxy-Registry vor npm verwenden
Statt Ihre CI und Entwickler direkt mit registry.npmjs.org verbinden zu lassen, verwenden Sie einen Registry-Proxy, der Pakete cached und optional scannt:
// .npmrc, point to your registry proxy
registry=https://npm-proxy.yourcompany.com/
Tools wie Verdaccio (selbst gehostet), Artifactory oder GitHub Packages können:
- Pakete cachen (kein Ausfall, wenn npm down ist)
- Bei Veröffentlichung auf bekannte Schwachstellen scannen
- Allowlists implementieren (nur freigegebene Pakete können installiert werden)
- Audit-Trails für Compliance bereitstellen
# Verdaccio quick start (self-hosted, free)
npx verdaccio --listen 4873
# Install through the proxy
npm install --registry http://localhost:4873
5. npm-Tokens aggressiv rotieren und eingrenzen
Der Shai-Hulud-Wurm verbreitete sich gezielt durch das Leaken von npm-Tokens. Reduzieren Sie den Blast Radius:
# GitHub Actions: use fine-grained, short-lived tokens
- name: Publish
uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN_FOR_THIS_ONE_PACKAGE }}
# NEVER use a publisher token that has access to multiple packages
- Erstellen Sie separate npm-Tokens für jedes Paket (verwenden Sie kein einzelnes Publisher-Token)
- Legen Sie Token-Ablaufdaten fest (npm unterstützt "Automation"-Tokens nur für CI)
- Speichern Sie Tokens in CI-Secret-Managern, nicht in
.npmrc-Dateien, die in Repos eingecheckt werden - Rotieren Sie Tokens vierteljährlich, und sofort nach jeder bekannten Offenlegung
6. Überwachen Sie Ihre Abhängigkeiten kontinuierlich
Einmalige Audits reichen nicht. Richten Sie kontinuierliches Monitoring ein:
# GitHub: Enable Dependabot security alerts (free for public repos)
# Settings → Code security and analysis → Dependency graph → Dependabot alerts
# For npm: Socket.dev provides free supply chain analysis
npx socket-dev install
# For Snyk (free tier available)
npx snyk auth
npx snyk monitor
Tools wie Socket.dev gehen über bekannte CVEs hinaus und erkennen riskante Verhaltensmuster (wie Pakete, die während des Postinstall Shell-Befehle ausführen oder auf das Dateisystem zugreifen), genau die Art von Verhalten, das der Shai-Hulud-Wurm zeigte.
Die Build-vs-Buy-Frage: Das selbst pflegen?
Wenn Sie das lesen und denken "das ist eine Menge Infrastruktur, um sie für Abhängigkeitssicherheit aufzubauen und zu pflegen", haben Sie recht. Jede dieser Ebenen, Registry-Proxying, SBOM-Generierung, CI-Pipeline-Härtung, Monitoring-Dashboards und Incident-Response-Playbooks, erfordert dedizierte Engineering-Zeit für Aufbau und Pflege.
Die meisten mittelgroßen Teams implementieren vielleicht ein oder zwei dieser Kontrollen (normalerweise Lockfile-Pinning und npm audit in CI). Das Gesamtbild, Registry-Proxying, verhaltensbasiertes Monitoring, Token-Rotations-Automatisierung und Post-Incident-Aufräumarbeiten, wird auf "irgendwann" verschoben und materialisiert sich selten vor dem nächsten Vorfall.
Hier kann die Zusammenarbeit mit einem erfahrenen Partner für individuelle Softwareentwicklung praktisch sein: Projektteams, die mehrere Supply-Chain-Vorfälle überlebt haben, verfügen oft über vorkonfigurierte CI-Templates, Registry-Proxy-Setups und Sicherheitsrichtlinien, die sie von Tag eins auf jedes neue Projekt anwenden, statt sie erst nach einem Schrecken nachzurüsten.
Best Practices: Den nächsten npm-Supply-Chain-Angriff überstehen
1. Postinstall-Skripte standardmäßig in .npmrc deaktivieren und nur die Pakete whitelisten, die sie benötigen. Diese einzelne Konfigurationszeile blockiert den Angriffsvektor, der den Shai-Hulud-Wurm effektiv machte.
2. npm ci in jeder CI-Pipeline verwenden, niemals npm install. Das verhindert Lockfile-Mutationen und gewährleistet reproduzierbare Builds aus exakt demselben Abhängigkeitsgraphen.
3. Registry-Proxying mit Verdaccio oder Artifactory implementieren, um Scanning, Caching und Allowlisting zwischen Ihren Entwicklern und der öffentlichen npm-Registry zu ergänzen.
4. npm-Tokens pro Paket und pro Pipeline eingrenzen und rotieren. Verwenden Sie niemals ein Publisher-Token mit Zugriff auf alle Ihre Pakete in einer CI-Umgebung. Rotieren Sie vierteljährlich und nach jeder bestätigten Offenlegung.
5. Kontinuierliches Dependency-Monitoring betreiben (Socket.dev, Snyk oder GitHub Dependabot), das bei neuen Postinstall-Verhaltensweisen alarmiert, nicht nur bei bekannten CVEs. Verhaltensanalyse erkennt Zero-Day-Supply-Chain-Angriffe, die signaturbasierte Scanner komplett übersehen.
Was Sie jetzt sofort tun sollten
Wenn Sie bis hierher gelesen haben, hier ist Ihre unmittelbare Aktionsliste:
- Führen Sie
npm ls --allundnpm auditin jedem aktiven Projekt Ihrer Organisation aus. Fügen Sie die Ergebnisse in Ihren Team-Kanal ein. - Prüfen Sie Ihre Lockfiles auf keyv und alle Pakete in der npm-Advisory-Liste. Falls betroffen, behandeln Sie jeden Rechner, der diese Versionen installiert hat, als kompromittiert und rotieren Sie alle Tokens.
- Fügen Sie
ignore-scripts=truezu Ihrer.npmrcnoch heute hinzu. Brechen Sie Dinge absichtlich, dokumentieren Sie, was whitelisted werden muss, und ziehen Sie von dort an die Schrauben an. - Prüfen Sie Ihre npm-Tokens. Wie viele existieren in Ihrer Organisation? Wer hat Zugriff? Wann wurden sie zuletzt rotiert?
Der Shai-Hulud-Wurm wird nicht der letzte npm-Supply-Chain-Angriff sein. Aber die Teams, die ihn mit minimaler Unterbrechung überstehen, sind diejenigen, die ihre Abhängigkeits-Pipeline bereits gehärtet hatten, bevor der Wurm kam, nicht die, die hektisch reagieren, nachdem er sie gefunden hat.
Quelle: Lieferketten-Angriff auf keyv: Shai-Hulud-Wurm infiziert mehr als 440 npm-Pakete

