npm Package Security: Wie ihr eure Supply Chain gegen kompromittierte Pakete absichert

Von Roland Golla
0 Kommentar
Schmelzende npm-Kiste mit Trojaner, daneben gläserner SBOM-Schrank im Dalí-Stil

Ihr kennt das Gefühl. npm install läuft durch, der Build ist grün, das Feature geht live. Dazwischen liegen ein paar hundert Pakete, die ihr nie gelesen habt und auch nie lesen werdet. In unserer Live-Coding-Session bei Never Code Alone haben wir mit Florian Lenz genau diesen blinden Fleck aufgemacht – und das Ergebnis war für alle Beteiligten unangenehm klar: Der Angriffsvektor liegt nicht in eurem Code. Er liegt in dem Vertrauen, das ihr jeden Tag ohne Nachdenken vergebt.

Dieser Artikel fasst die Kernpunkte aus dem Stream zusammen und beantwortet die Fragen, die uns danach am häufigsten erreicht haben. Kein Panikmodus, sondern das, was ihr in eurer Pipeline konkret umstellen könnt.

Der Fall Axios: drei Stunden, die reichten

Am 30. und 31. März 2026 ist passiert, worüber die Security-Community seit Jahren redet. Ein Angreifer übernahm den npm-Account des Haupt-Maintainers von Axios – dem meistgenutzten HTTP-Client im JavaScript-Ökosystem mit rund 100 Millionen Downloads pro Woche – und veröffentlichte zwei manipulierte Versionen: axios@1.14.1 auf dem latest-Tag und axios@0.30.4 auf dem legacy-Tag.

Der eigentliche Schadcode steckte nicht in Axios selbst. Die manipulierten Versionen zogen eine zusätzliche Abhängigkeit namens plain-crypto-js@4.2.1 herein, die im Axios-Quellcode nirgendwo importiert wird. Ihre einzige Aufgabe: ein postinstall-Hook, der plattformspezifische Payloads nachlud und einen Remote Access Trojan auf macOS, Windows und Linux installierte. Microsoft Threat Intelligence hat die Infrastruktur später dem staatlichen Akteur Sapphire Sleet zugeordnet.

Besonders lehrreich ist die Vorbereitung. Rund 18 Stunden vor dem Angriff wurde eine saubere Version plain-crypto-js@4.2.0 veröffentlicht, um dem Paket eine Publishing-Historie zu geben – damit automatische Scanner es nicht allein wegen seiner Neuheit flaggen. Beide Axios-Branches wurden innerhalb von 39 Minuten getroffen. Laut Huntress landete die erste Infektion auf einem überwachten Endpoint 89 Sekunden nach Veröffentlichung.

Neunundachtzig Sekunden. Das ist die Zeit, die zwischen einem kompromittierten Paket und eurer CI-Pipeline liegt, wenn ihr mit Caret-Ranges arbeitet und kein Lockfile durchsetzt. Die Pakete waren nach etwa drei Stunden aus der Registry entfernt. Für jeden, der in diesem Fenster einen frischen Install gefahren hat, war das zu spät.

Genau an diesem Punkt wurde es im Stream interessant. Denn die Frage ist nicht mehr, ob so etwas passiert. Die Frage ist, ob ihr innerhalb von Minuten beantworten könnt, ob es euch getroffen hat.

Was ist eine npm Supply Chain Attack überhaupt?

Bei einer klassischen Schwachstelle habt ihr einen Fehler im Code, den jemand ausnutzen kann. Bei einem Supply-Chain-Angriff ist der Code absichtlich bösartig – und er kommt über einen Kanal, dem ihr per Definition vertraut.

Das läuft typischerweise über drei Wege. Erstens: ein kompromittierter Maintainer-Account, wie bei Axios. Die Zugangsdaten werden über Phishing oder Social Engineering abgegriffen, dann wird unter dem echten Namen publiziert. Zweitens: Typosquatting, also Pakete mit Namen wie expres oder lodahs, die auf euren Tippfehler warten. Drittens: die Übernahme verwaister Pakete, bei der ein ausgebrannter Maintainer Publishing-Rechte an einen freundlichen Fremden abgibt – der Fall event-stream von 2018 ist bis heute das Lehrbuchbeispiel.

Der entscheidende Punkt, den Florian im Stream immer wieder betont hat: npm install ist kein Download. Es ist eine Ausführungsumgebung. Sobald preinstall und postinstall mitspielen, führt ihr fremden Code mit euren Rechten aus – auf eurem Laptop, in eurer CI, mit euren Umgebungsvariablen im Zugriff. Wer das einmal verinnerlicht hat, denkt anders über Dependencies nach.

Pro-Tipp: Macht euch einmal bewusst, was in eurer CI in process.env steht. Cloud-Credentials, Registry-Tokens, API-Keys für LLM-Anbieter, Deployment-Schlüssel. Ein Post-Install-Skript sieht das alles. Genau das war 2018 schon das Ziel des eslint-scope-Vorfalls, bei dem npm-Tokens abgegriffen wurden.

Wie erkenne ich, ob ein npm-Paket vertrauenswürdig ist?

Die ehrliche Antwort zuerst: mit letzter Sicherheit gar nicht. Aber ihr könnt die Wahrscheinlichkeit deutlich verschieben, und dafür gibt es ein paar Signale, die sich in wenigen Minuten prüfen lassen.

Schaut euch das Veröffentlichungsdatum der aktuellen Version an. Eine Version, die seit sechs Stunden existiert und keinen Changelog-Eintrag hat, ist ein Warnsignal. Prüft die Zahl der Maintainer und ob das Paket über OIDC Trusted Publishing veröffentlicht wird – npm zeigt Provenance-Informationen inzwischen direkt auf der Paketseite an. Werft einen Blick auf die Dependency-Liste: Ein HTTP-Client, der plötzlich eine Krypto-Bibliothek als Runtime-Dependency zieht, sollte euch stutzig machen. Genau das war bei Axios das verräterische Detail.

Und ganz wichtig: Verlasst euch nicht auf das GitHub-Repository. Das ist der Denkfehler, den Red Canary in ihrer Analyse der jüngeren npm-Vorfälle herausgearbeitet hat. Was auf GitHub liegt, ist nicht zwingend das, was in der Registry liegt. Publiziert wird lokal, nicht aus dem Repo. Der Tarball auf npm kann Dateien enthalten, die in der Git-History nie aufgetaucht sind.

# Was steckt wirklich im Tarball? Vor dem Install anschauen:
npm pack axios@1.14.0 --dry-run

# Dependency-Baum eines Pakets prüfen, ohne es zu installieren:
npm view axios@1.14.0 dependencies

Reicht npm audit nicht aus?

Nein, und das ist einer der wichtigsten Punkte für alle, die Sicherheit für ein abgehaktes Häkchen halten.

npm audit gleicht eure Abhängigkeiten gegen eine Datenbank bekannter Schwachstellen ab. Das ist wertvoll, deckt aber nur bereits gemeldete CVEs ab. Ein frisch publiziertes bösartiges Paket steht in keiner dieser Datenbanken. Beim Axios-Vorfall wurde übrigens nie ein klassischer CVE vergeben, weil es keine Schwachstelle im Axios-Code gab – die Nachverfolgung lief über GitHub Security Advisories und npm-Advisories.

Dazu kommt das Problem, das die Forschung als Alert Fatigue beschreibt: Eine Studie unter npm-Paket-Entwicklerinnen und -Entwicklern von 2026 nennt hohe False-Positive-Raten und Zeitmangel als die zentralen Hürden für konsequentere Sicherheitsmaßnahmen. Wer jeden Tag vierzig Meldungen bekommt, von denen achtunddreißig irrelevant sind, schaut irgendwann nicht mehr hin.

npm audit gehört in eure Pipeline. Aber es ist die Grundlinie, nicht die Absicherung.

Consulting-Tipp: Trennt in eurem CI-Setup sauber zwischen „blockierend“ und „informierend“. Ein Critical-Finding in einer Production-Dependency lässt den Build fallen. Ein Moderate-Finding in einer Dev-Dependency landet in einem wöchentlichen Report. Wenn alles blockiert, wird bald alles übersprungen.

Warum schützt mich mein Lockfile nicht automatisch?

Weil viele Teams ein Lockfile haben und trotzdem nicht deterministisch bauen.

Das Lockfile hält exakte Versionen und Integritäts-Hashes fest. Wenn ihr aber in eurer CI npm install statt npm ci fahrt, versucht npm, Abweichungen zwischen package.json und dem Lockfile „aufzulösen“ – und schreibt das Lockfile dabei unter Umständen neu. Eine Caret-Range wie ^1.14.0 löst dann auf die neueste passende Version auf. Bei Axios war das innerhalb weniger Minuten die kompromittierte 1.14.1.

Die Regel ist simpel und kostet euch nichts:

# .github/workflows/ci.yml – die eine Zeile, die zählt
- name: Install dependencies
  run: npm ci --ignore-scripts

npm ci installiert ausschließlich das, was im Lockfile steht, und bricht ab, wenn Lockfile und package.json auseinanderlaufen. Kein stilles Nachziehen, keine Überraschungen. Behandelt euer Lockfile als sicherheitsrelevantes Artefakt, nicht als generierten Müll, der halt im Repo liegt.

Genau derselbe Gedanke gilt für Git-basierte Dependencies. Wer "my-lib": "github:someorg/some-lib#main" in der package.json stehen hat, umgeht sämtliche Registry-Kontrollen. Der Inhalt kann sich jederzeit ändern, Tags lassen sich force-pushen. pnpm bietet dafür seit Version 10.26 die Option blockExoticSubdeps, die verhindert, dass transitive Abhängigkeiten aus Git-Repos oder rohen Tarball-URLs gezogen werden.

Wie stoppe ich postinstall-Skripte?

Über den Flag --ignore-scripts. Das ist die wirksamste Einzelmaßnahme in diesem ganzen Artikel, und sie ist eine Zeile Konfiguration.

# Projektweit in .npmrc festnageln
echo "ignore-scripts=true" >> .npmrc

Damit führt npm keine pre- und postinstall-Hooks von Abhängigkeiten mehr aus. Der Axios-Angriff wäre an dieser Stelle ins Leere gelaufen – der RAT-Dropper hing komplett am postinstall-Hook von plain-crypto-js.

Der Haken: Manche Pakete brauchen ihre Build-Skripte tatsächlich, etwa alles mit nativen Bindings wie sharp, esbuild oder Datenbanktreiber. Das löst ihr über eine Allowlist statt über pauschales Abschalten. pnpm macht das mit allowBuilds und strictDepBuilds mittlerweile vorbildlich: Skripte laufen nur für Pakete, die ihr explizit freigegeben habt, und der Install schlägt fehl, wenn ein nicht freigegebenes Paket bauen will.

Pro-Tipp: Führt ignore-scripts zuerst nur in der CI ein, nicht auf den Entwicklerrechnern. Ihr seht dann innerhalb eines Sprints genau, welche Pakete wirklich Build-Skripte benötigen, und baut daraus eure Allowlist – ohne dass euch das halbe Team im Standup erklärt, warum lokal nichts mehr läuft.

Was ist minimumReleaseAge und wie setze ich es ein?

Das ist die vielleicht eleganteste Antwort auf die Angriffsklasse, um die es hier geht – und sie ist erst seit Kurzem in npm selbst angekommen.

Die Idee: Ihr installiert keine Version, die jünger ist als X. Kompromittierte Pakete werden in der Regel innerhalb von Stunden entdeckt und entfernt – die Axios-Versionen waren rund drei Stunden live. Wenn eure Pipeline neue Versionen erst nach ein paar Tagen überhaupt in Betracht zieht, sind die schlimmsten Vorfälle vorbei, bevor sie euch erreichen.

npm hat das mit den CLI-Releases der 11.x-Reihe als minimumReleaseAge beziehungsweise min-release-age eingeführt, dort in Tagen gerechnet. pnpm kennt minimumReleaseAge schon länger und rechnet in Minuten, was feinere Steuerung erlaubt.

# .npmrc
min-release-age=7

# .npmrc für pnpm – 3 Tage in Minuten
minimumReleaseAge=4320

Der Preis ist Verzögerung. Ihr bekommt Bugfixes und Sicherheitsupdates ein paar Tage später. Das ist ein bewusster Trade-off, und für die meisten Produktivsysteme ist er richtig herum: Das Risiko, ein frisch kompromittiertes Paket zu ziehen, ist höher als das Risiko, einen Patch drei Tage später zu bekommen. Für echte Notfall-Patches definiert ihr eine dokumentierte Ausnahme.

Dependabot hat inzwischen ebenfalls einen Cooldown per Default für Version-Updates eingebaut. Das geht alles in dieselbe Richtung, und das ist gut so.

Was ist eine SBOM und brauche ich sie wegen des Cyber Resilience Act?

Eine Software Bill of Materials ist die maschinenlesbare Stückliste eurer Anwendung: jede eigene Komponente, jede Open-Source-Bibliothek, jede transitive Abhängigkeit, jeweils mit Version, Lizenz und Herkunft. Der Vergleich mit der Zutatenliste auf einer Lebensmittelverpackung, den Florian im Stream gebracht hat, trifft es gut.

Der praktische Wert zeigt sich genau in Situationen wie dem Axios-Vorfall. Die Frage „sind wir betroffen?“ beantwortet ihr mit einer SBOM in Minuten statt in Tagen. Ohne SBOM beginnt ein Telefonkonferenz-Marathon durch alle Teams, während die Uhr läuft.

Zur regulatorischen Seite, weil die Frage in jedem Entscheider-Gespräch kommt: Der Cyber Resilience Act wurde im Dezember 2024 verabschiedet und tritt gestaffelt in Kraft. Die Meldepflichten für aktiv ausgenutzte Schwachstellen greifen ab dem 11. September 2026, die vollständige Anwendung der Verordnung folgt im Dezember 2027. Die SBOM ist dabei Teil der technischen Dokumentation, die Hersteller vorhalten müssen. In Deutschland gibt die BSI-Richtlinie TR-03183-2 zusätzlich vor, wie eine SBOM inhaltlich auszusehen hat.

Ihr baut das also ohnehin. Die einzige Frage ist, ob ihr es unter Zeitdruck macht oder jetzt.

# SBOM erzeugen und direkt scannen – zwei Kommandos
syft dir:. -o cyclonedx-json > sbom.json
grype sbom:sbom.json --fail-on high

# Alternative mit Trivy, wenn ihr das ohnehin für Container nutzt
trivy fs --format cyclonedx --output sbom.json .
trivy sbom sbom.json --severity HIGH,CRITICAL --exit-code 1

Als Format haben sich CycloneDX und SPDX durchgesetzt. Nehmt eines davon, generiert die SBOM bei jedem Build und archiviert sie zusammen mit dem Artefakt. Eine SBOM, die ihr nicht aufbewahrt, hilft euch im Ernstfall nicht.

Wie sichere ich meinen eigenen npm-Publish-Prozess ab?

Wenn ihr selbst Pakete veröffentlicht – auch intern, auch nur ein Design-System für drei Teams – dann seid ihr Teil der Lieferkette anderer Leute. Hier hat sich im Ökosystem gerade viel bewegt, und einiges davon ist an Teams vorbeigegangen.

Die klassischen npm-Tokens sind seit dem 9. Dezember 2025 endgültig widerrufen. Wer noch ein NPM_TOKEN aus 2021 in den GitHub Secrets liegen hat, dessen Publish-Job ist längst rot. Ersetzt wurden sie durch granulare Access Tokens mit einer maximalen Laufzeit von 90 Tagen für Schreibrechte, und für lokales Publizieren durch Session-Tokens mit zwei Stunden Gültigkeit.

Der eigentliche Zielzustand ist aber OIDC Trusted Publishing. Statt ein Geheimnis in eurer CI abzulegen, autorisiert ihr npm, einem bestimmten Repository in einem bestimmten Workflow zu vertrauen. Es gibt kein Token mehr, das gestohlen werden kann. Seit Februar 2026 lässt sich das über den Befehl npm trust auch für viele Pakete auf einmal konfigurieren – vorher musste man das Paket für Paket durchklicken, was für Maintainer mit großen Portfolios praktisch nicht machbar war.

# GitHub Actions: Publish über OIDC, ohne NPM_TOKEN
permissions:
  contents: read
  id-token: write   # nur in diesem Job, nicht workflow-weit

steps:
  - uses: actions/setup-node@v4
    with:
      node-version: 22
      registry-url: 'https://registry.npmjs.org'
  - run: npm ci --ignore-scripts
  - run: npm publish --provenance --access public

Zwei Dinge dazu aus der Praxis. Erstens: Vergebt id-token: write ausschließlich in dem Job, der wirklich publiziert, niemals workflow-weit. Zweitens: Aktiviert nach der Umstellung in den Paketeinstellungen zusätzlich „Require two-factor authentication and disallow tokens“, sonst bleibt der alte Token-Weg als Hintertür offen. Genau diese Hintertür hat der Angreifer bei Axios genutzt – er hat die Trusted-Publisher-Absicherung über GitHub Actions schlicht umgangen, indem er manuell mit einem gestohlenen Token publizierte.

Und pinnt eure GitHub Actions auf den vollen Commit-SHA statt auf Tags. Ein Tag lässt sich verschieben.

Wie finde ich heraus, ob wir betroffen waren?

Das ist die Frage, die im Ernstfall zählt, und sie zerfällt in drei Teile.

Erstens: Habt ihr die Version überhaupt gezogen? Durchsucht eure Lockfiles über alle Repositories hinweg. Nicht nur die aktiven – auch die Feature-Branches, die Beispiel-Projekte, die vergessene interne Tool-Sammlung.

# Über alle Repos: taucht eine kompromittierte Version im Lockfile auf?
grep -rn "axios/-/axios-1.14.1.tgz" --include=package-lock.json .

# Und im tatsächlich installierten Baum
npm ls axios plain-crypto-js

Zweitens: Wurde in dem betroffenen Zeitfenster überhaupt installiert? Der Post-Mortem des Axios-Maintainers bringt es auf den Punkt: Wer auf eine saubere Version gepinnt war und zwischen 00:21 und 03:15 UTC am 31. März keinen frischen Install gefahren hat, war nicht betroffen. Eure CI-Logs mit Zeitstempeln sind hier die entscheidende Quelle. Führt euch vor Augen, ob ihr diese Logs lange genug aufbewahrt, um so eine Frage in einem halben Jahr noch beantworten zu können.

Drittens: Wenn ja, dann richtig. Ein System, auf dem ein RAT-Dropper gelaufen ist, ist kompromittiert – nicht „vermutlich okay, weil der Virenscanner nichts sagt“. Das bedeutet: alle Credentials rotieren, die auf dieser Maschine erreichbar waren. npm-Tokens, Cloud-Keys, SSH-Schlüssel, Registry-Zugänge, API-Keys. Und das System neu aufsetzen.

Consulting-Tipp: Übt das einmal, bevor es ernst wird. Nehmt euch einen Nachmittag, sucht euch einen realen vergangenen Vorfall und spielt ihn durch: Wie lange braucht ihr bis zur Antwort „betroffen ja/nein“? Wer rotiert welche Credentials? Wer entscheidet über einen Rollback? Wenn diese Übung länger als zwei Stunden dauert, habt ihr euren Backlog für das nächste Quartal gefunden.

Wie bekomme ich das intern durch, ohne das Team auszubremsen?

Diese Frage kommt fast immer von Tech Leads und Entscheiderinnen, und sie ist berechtigt. Security-Maßnahmen scheitern selten an der Technik. Sie scheitern daran, dass sie den Alltag verlangsamen und deshalb nach drei Wochen wieder abgeschaltet werden.

Was in unserer Erfahrung funktioniert, ist eine Reihenfolge nach Aufwand-Nutzen-Verhältnis statt nach Vollständigkeit:

  1. npm ci in allen Pipelines. Eine Zeile, kein Widerstand, sofortiger Effekt auf die Determinismus-Frage.
  2. –ignore-scripts in der CI, danach schrittweise die Allowlist bauen. Größter Sicherheitsgewinn pro investierter Stunde.
  3. Cooldown über min-release-age. Kostet nichts außer einer Diskussion darüber, wie viele Tage angemessen sind.
  4. SBOM-Generierung im Build. Zwei Kommandos, und ihr habt die Compliance-Frage vorbereitet, die ohnehin kommt.
  5. Trusted Publishing, falls ihr selbst publiziert. Etwas mehr Aufwand, dafür verschwindet eine ganze Klasse von Geheimnissen aus eurer Infrastruktur.
  6. Incident-Response-Übung. Der Punkt, den alle überspringen und der im Ernstfall den Unterschied macht.

Für das Gespräch mit dem Management hilft der regulatorische Rahmen mehr als jedes technische Argument. Der CRA sieht bei schwerwiegenden Verstößen gegen die wesentlichen Anforderungen empfindliche Sanktionen vor, und die Meldepflicht bei aktiv ausgenutzten Schwachstellen liegt bei 24 Stunden. Wer diese Frist einhalten will, braucht die technische Antwortfähigkeit vorher. Das ist ein Argument, das über den Sicherheits-Appell hinausgeht.

Was KI an dieser Rechnung verändert

Ein Punkt aus dem Stream, der uns beschäftigt hat: Der Kreis derer, die Software ausliefern, wächst gerade schnell. Menschen ohne klassische Entwickler-Ausbildung bauen mit KI-Assistenten funktionierende Anwendungen – und das ist erstmal eine gute Entwicklung. Die Kehrseite ist, dass Sicherheitsbewusstsein nicht mitwächst. Wenn ein Modell vorschlägt, Paket X in Version Y einzubinden, wird das übernommen, und niemand fragt, wann diese Version publiziert wurde oder wer sie publiziert hat.

Auf der Angreiferseite gilt dasselbe in umgekehrter Richtung. Der Axios-Angriff begann laut Post-Mortem mit einer gezielten Social-Engineering-Kampagne gegen den Maintainer. Genau diese Art von Angriff wird durch generative Werkzeuge billiger und überzeugender. Die Verteidigung dagegen ist nicht mehr Aufmerksamkeit, sondern Struktur: Prozesse, die auch dann halten, wenn ein einzelner Mensch getäuscht wurde.

Das ist im Kern der Gedanke hinter Trusted Publishing, hinter Cooldowns und hinter der SBOM. Nicht darauf setzen, dass niemand einen Fehler macht, sondern dafür sorgen, dass ein einzelner Fehler nicht die gesamte Kette kippt.

Fazit: Vertrauen ist kein Sicherheitsmodell

Der Axios-Vorfall hat nichts gezeigt, was die Security-Community nicht vorher gewusst hätte. Er hat es nur schwer ignorierbar gemacht. Ein Paket, das in fast jedem JavaScript-Projekt steckt, war drei Stunden lang bewaffnet – und die erste Infektion lag anderthalb Minuten nach Veröffentlichung.

Die gute Nachricht ist, dass die wirksamsten Gegenmaßnahmen keine großen Projekte sind. npm ci statt npm install. --ignore-scripts mit sauberer Allowlist. Ein Cooldown von ein paar Tagen. Eine SBOM im Build. Wer diese vier Dinge hat, ist gegen die überwiegende Mehrheit dieser Angriffe gut aufgestellt.

Wir sind seit über 15 Jahren auf Softwarequalität, Open Source und Remote Consulting spezialisiert und begleiten Teams genau bei solchen Umstellungen – von der Pipeline-Härtung über SBOM-Integration bis zur Frage, wie ihr das im Team verankert, ohne dass es nach drei Wochen wieder abgeschaltet wird. Wenn ihr gerade vor einer dieser Aufgaben steht oder einfach unsicher seid, wo ihr anfangen sollt: Schreibt uns eine Mail an roland@nevercodealone.de. Kein Formular, kein Sales-Funnel, einfach eine kurze Nachricht mit eurer Situation. Wir melden uns und schauen gemeinsam drauf.

Schaut euch die vollständige Session mit Florian Lenz an, dort seht ihr die Live-Demos im Detail. Und dann geht einmal in eure CI-Konfiguration. Die erste Zeile ist schnell geändert.

Bleibt neugierig, bleibt skeptisch – und pinnt eure Dependencies.

Never Code Alone – Gemeinsam für bessere Software-Qualität!

0 Kommentar

Tutorials und Top Posts

Gib uns Feedback

Diese Seite benutzt Cookies. Ein Akzeptieren hilft uns die Seite zu verbessern. Ok Mehr dazu