Wichtigste Erkenntnisse
- Eine transitive Abhängigkeit ist ein Paket, das Sie erhalten, weil etwas, von dem Sie abhängig sind, dieses benötigt.
- Ein einziges npm-Paket kann Hunderte von transitiven Abhängigkeiten mit sich bringen, von denen jede in der Lage ist, Code mit Ihren Zugriffsrechten auszuführen.
- Andere Betreuer legen die Bereiche tief in Ihrem Baum fest, sodass Korrekturen und Angriffe nach ihrem eigenen Zeitplan erfolgen.
- Der Schutz muss auf Registry-Ebene erfolgen, um auch Pakete zu erfassen, die Ihr Team noch nie überprüft hat.
Probieren Sie das einmal bei einem Projekt aus, an dem Sie schon seit mehr als ein paar Monaten arbeiten. Öffnen Sie package.json und zähle die Abhängigkeiten. Führe dann Folgendes aus: npm ls --all und schau dir an, wie viel daran vorbeizieht.
Die Liste in package.json liegt in der Regel im zweistelligen Bereich. Der Baum ist jedoch wesentlich länger, selbst wenn npm doppelte Einträge als dedupliziert markiert. Alles in diesem Baum, was Sie nicht selbst aufgeführt haben, ist eine transitive Abhängigkeit, und dies ist der Teil Ihres Stacks, dem am wenigsten Aufmerksamkeit geschenkt wird, obwohl er einen Großteil der Arbeit leistet.
Was eine transitive Abhängigkeit eigentlich ist
Eine direkte Abhängigkeit ist eine, die du angefordert hast. Du hast Folgendes ausgeführt: npm install express, landete es in package.json, und jemand aus Ihrem Team hat eine Entscheidung dazu getroffen.
Eine transitive Abhängigkeit ist eine, um die Sie nicht gebeten haben. Express benötigt eigene Pakete, diese Pakete wiederum benötigen ihre eigenen, und npm löst die gesamte Kette für Sie auf. Nichts davon taucht in Ihrem Projekt auf package.json, aber all das wird in Ihrer Lockdatei protokolliert, und was tatsächlich installiert wird, landet in node_modules. Jeder Teil davon, der ausgeführt wird – sei es während der Installation oder beim Laden Ihrer App –, erhält denselben Zugriff wie der Prozess, der ihn ausführt. Auf Ihrem Laptop ist das Ihr Benutzerkonto, und in der CI sind es der Runner und alle Geheimnisse, auf die er Zugriff hat.
Es ist hilfreich, diesem Phänomen einen Namen zu geben – nennen wir es also „erbenes Risiko“. Das erbenes Risiko ist das Risiko, das Ihr Projekt durch Pakete eingeht, die zusammen mit den von Ihnen ausgewählten Paketen geliefert wurden. Sie haben diese Pakete nicht bewertet, Sie haben deren Versionen nicht ausgewählt, und in den meisten Fällen konnten Sie sie nicht einmal benennen. Dennoch liefern Sie sie aus.
Warum der Baum so groß wird
Das liegt zum Teil einfach daran, wie sich das JavaScript-Ökosystem entwickelt hat. npm hat die Veröffentlichung kleiner, zweckgebundener Pakete vereinfacht, und die Community hat diesen Trend aufgegriffen. Ein Dienstprogramm, das eine bestimmte Aufgabe gut erfüllt, wird in Hunderte anderer Pakete eingebunden, und jedes dieser Pakete bringt seine eigenen kleinen Hilfsprogramme mit. Wie es auf der Seite „SecureChain for npm“ von TuxCare heißt, kann ein npm-Paket auf oberster Ebene Hunderte von transitiven Abhängigkeiten nach sich ziehen.
Der andere Aspekt ist, wie npm die Struktur gestaltet node_modules. Wo immer es möglich ist, npm Hebezeuge befördern die Pakete den Baum hinauf Daher befinden sich die meisten davon nebeneinander ganz oben. Das ist gut für den Speicherplatz, aber schlecht für die intuitive Bedienung. Blättern Sie durch node_modules Und nichts deutet darauf hin, dass ein Paket vier Ebenen tief ist und nur deshalb dort liegt, weil etwas, von dem man noch nie gehört hat, danach gefragt hat.
Wenn Sie die Ebenen sehen möchten, npm explain <package> zeigt Ihnen alle Pfade an, die zu einem bestimmten Paket führen. Führen Sie das Programm für einen beliebigen Eintrag aus Ihrer Lockdatei aus und wundern Sie sich nicht, wenn mehr als eine Kette dorthin führt.
Die Versionsbereiche gehören dir nicht
Das ist der Punkt, an dem viele ins Straucheln geraten. Wenn Sie eine Abhängigkeit hinzufügen, wählen Sie deren Versionsbereich aus. Wenn diese Abhängigkeit wiederum eigene Abhängigkeiten hinzufügt, wählt deren Betreuer den Versionsbereich aus, und Sie übernehmen diesen.
In den meisten dieser Bereiche wird ein Caret verwendet, wie zum Beispiel ^4.17.0, was semver behandelt dies als Jede 4.x-Version ab 4.17.0. Ihre Sperrdatei speichert genau die Versionen, die beim letzten Mal funktioniert haben, weshalb man sie festschreiben und verwenden sollte npm ci In CI spielt das eine große Rolle. Sobald jedoch jemand die Sperrdatei löscht und neu erstellt, ein Update durchführt oder in ein Projekt ohne diese Datei installiert, können Bereichsangaben im gesamten Verzeichnisbaum auf die neueste zulässige Version aufgelöst werden, einschließlich einer, die vor einer Stunde veröffentlicht wurde.
Das ist praktisch, wenn ein Betreuer einen Fix veröffentlicht. Es ist jedoch ein Problem, wenn er etwas anderes veröffentlicht.
Angreifer haben das schon vor langer Zeit herausgefunden. Bereits im Jahr 2018 hat ein neuer Betreuer des beliebten event-stream Paket eine Abhängigkeit namens „flatmap-stream“ hinzugefügt, die, wie sich herausstellte, Code enthielt, der darauf abzielte, Daten aus einer bestimmten Bitcoin-Wallet-App zu stehlen. Niemand, der event-stream hatte sich entschieden flatmap-stream. Es ist gerade angekommen.
Im Juni 2026 wiederholte sich diese Vorgehensweise. Als Angreifer Veröffentlichungsrechte für den npm-Bereich „Mastra“ erlangten, stellten sie mehr als 140 betroffene Paketversionen in weniger als zwei Stunden, wie aus Berichten von „The Hacker News“ und Buchse. Der Mastra-Code selbst blieb unverändert. Was sich geändert hat, war die Liste der Abhängigkeiten, die nun Folgendes enthielt: easy-day-js, ein Doppelgänger des dayjs Datumsbibliothek. Wer auch immer die Kontrolle hatte easy-day-js hatte am Vortag eine fehlerfreie, funktionsfähige Version 1.11.21 veröffentlicht und dann am frühen Morgen des 17. Juni, kurz vor der Veröffentlichung der Mastra-Versionen, die Version 1.11.22 mit einem bösartigen Postinstall-Hook herausgebracht. Die neuen Mastra-Versionen forderten ^1.11.21, sodass jede Neuinstallation direkt auf die manipulierte Version verwies. Hättest du eine dieser Versionen überprüft, wäre die einzige wirkliche Änderung, die du in package.json Es gab nur eine einzige neue Abhängigkeit. Alles Gefährliche befand sich eine Ebene tiefer.
Warum die Behebung einer schwerwiegenden Sicherheitslücke so lange dauert
Auch tief in der Abhängigkeitsstruktur häufen sich bekannte Sicherheitslücken an. Als Endor Labs für seinen Bericht „State of Dependency Management 2022“ weit verbreitete Java-Pakete analysierte, stellte das Unternehmen fest, dass rund 95 % der anfälligen Abhängigkeiten transitiv waren. Da es sich dabei um Maven-Pakete handelte, sollte diese Zahl eher als Anhaltspunkt für die Größenordnung bei npm dienen und nicht als konkrete npm-Zahl verstanden werden.
Angenommen, ein CVE taucht in einem Paket auf, das drei Ebenen unterhalb der von Ihnen gewählten Ebene liegt. Der Fix wird im Upstream veröffentlicht, und in einer perfekten Welt übernimmt Ihr Baum ihn einfach. In der Praxis funktioniert das jedoch nur, wenn jedes Paket zwischen Ihnen und dem Fix die neue Version zulässt. Wenn ein übergeordnetes Paket in der Kette eine bestimmte Version festgelegt oder seinen Versionsbereich unterhalb der korrigierten Version begrenzt hat, kommt der normale Abgleich genau an dieser Stelle zum Stillstand. Nun warten Sie darauf, dass dieser Betreuer eine neue Version herausbringt – und möglicherweise auch der Betreuer, der ihm übergeordnet ist.
Deshalb npm audit Das kann sehr frustrierend sein. Es hilft zwar dabei, festzustellen, dass tief im System etwas nicht stimmt, aber bei der Behebung des Problems ist es weitaus weniger hilfreich, da die Lösung oft nicht in Ihrer Hand liegt. npm audit fix wird keine Änderung der Semver-Hauptversion vornehmen, ohne --forceund --force ist eine ziemlich direkte Methode, um herauszufinden, welche Teile Ihrer App nicht funktionieren.
Dann gibt es noch Überschreibungen, was npm wurde in Version 8.3 hinzugefügt. Mit einer Überschreibung können Sie eine bestimmte Version eines transitiven Pakets erzwingen oder einen Fork einbinden, unabhängig davon, was das übergeordnete Paket verlangt hat. Dadurch lässt sich die Sicherheitslücke möglicherweise beheben. Machen Sie jedoch deutlich, was Sie getan haben. Sie verwenden nun eine Kombination von Paketen, die der Betreuer des übergeordneten Pakets wahrscheinlich nie getestet hat, und die Überschreibung verbleibt in der Regel in Ihrem package.json lange nachdem niemand mehr weiß, warum es dort ist.
Der Betreuer, der nicht zurückkommt
All das setzt voraus, dass jemand weiter oben in der Kette weiterhin Releases veröffentlicht. Tief in einem großen Projektbaum ist das nicht immer der Fall.
Viele der Pakete am unteren Ende eines Baums sind klein und im Wesentlichen fertiggestellt, und einige ihrer Betreuer haben sich schon vor Jahren anderen Projekten zugewandt. Wenn eine CVE in einem dieser Pakete auftaucht, gibt es niemanden, der den Fix erstellt, sodass es nichts gibt, was nach oben weitergegeben werden könnte. Der Scanner meldet den Fehler weiterhin, und es gibt keinen Upstream-Fix, auf den ein Override verweisen könnte. Was bleibt, ist, selbst einen Fork zu erstellen und den Patch einzubauen, jemanden zu finden, der einen Fix zurückportiert, oder das übergeordnete Paket zu ersetzen, das davon abhängt. Keine dieser Möglichkeiten ist schnell, und die letzte kann zu einer Neuprogrammierung führen, für die niemand Zeit hat.
Genau für diesen Fall des Backports wurde der „Endless Lifecycle Support“ (ELS) von TuxCare entwickelt. Für die davon abgedeckten Pakete portiert ELS Sicherheitskorrekturen auf genau die Versionen, die Sie bereits verwenden, sobald die Wartung durch den Upstream-Entwickler eingestellt wird. So können Sie bei Ihrer aktuellen Version bleiben, anstatt zu einem Upgrade gezwungen zu sein.
Den gesamten Baum schützen
Die meisten der oben genannten Korrekturen erfolgen innerhalb Ihres Projekts, jeweils für ein Paket nach dem anderen, und in der Regel erst, nachdem das Problem bereits in Ihren Code-Baum gelangt ist. Überschreibungen, Audit-Korrekturen und manuelle Festlegungen sind allesamt Möglichkeiten, etwas zu bereinigen, das aus dem Upstream-Code stammt.
Die Alternative besteht darin, die Quelle des gesamten Baums zu ändern. Das ist die Idee hinter SecureChain. Es ersetzt die öffentliche npm-Registry durch ein kuratiertes Repository, aus dem Ihre Installationen bezogen werden – einschließlich transitiver Pakete. Die Pakete werden aus verifizierten Quellen in abgesicherten, isolierten Umgebungen neu kompiliert und auf bekannte Malware sowie verdächtige Codemuster überprüft; alles, was die Überprüfung nicht besteht, wird blockiert, bevor es Ihre Umgebung erreicht. Sollte etwas, das Ihr Baum benötigt, noch nicht im Katalog enthalten sein, fügt TuxCare es hinzu. Aktive Pakete werden im Rahmen des SLA gepatcht, sobald neue CVEs veröffentlicht werden, und wenn ein von SecureChain verwaltetes Paket das Ende seiner Lebensdauer erreicht, übernimmt ELS die weitere Abdeckung, ohne dass Sie etwas unternehmen müssen.
Sobald Sie npm oder Ihren Repository-Manager darauf verweisen, ändert sich an der täglichen Arbeit nichts. npm, Yarn und pnpm behalten die gleichen Befehle bei, und SecureChain arbeitet mit Artifactory, Nexus oder GitHub Packages zusammen, falls Ihr Team Pakete bereits auf diese Weise bereitstellt. Was sich ändert, ist, dass das Paket, das vier Ebenen tiefer liegt – von dem noch niemand etwas gehört hat –, denselben Neuaufbau und dieselbe Überprüfung durchläuft wie das Framework, das Sie bewusst ausgewählt haben.
Wo soll man anfangen?
Bevor Sie irgendetwas ändern, verschaffen Sie sich einen klaren Überblick darüber, was bei Ihnen läuft. Führen Sie npm ls --all und speichern Sie die Ausgabe, damit Sie sie später vergleichen können. Wählen Sie einige Pakete aus, die Sie nicht kennen, und führen Sie folgenden Befehl aus: npm explain darauf. Stelle sicher, dass deine Sperrdatei festgeschrieben ist und dass CI npm ci, sodass die Builds anhand der Sperrdatei installiert werden, anstatt die Bereiche neu aufzulösen, und sich die Versionen nur ändern, wenn jemand eine aktualisierte Sperrdatei festschreibt.
Sehen Sie sich dann Ihre letzten Prüfungsberichte an und sortieren Sie die Feststellungen in zwei Gruppen: von Ihnen gewählte und von Ihnen übernommene. Der zweite Stapel gibt einen guten Überblick darüber, wo Ihre übernommenen Risiken tatsächlich liegen, und genau diesen sollten Sie sich genauer ansehen.
FAQ
Was ist eine transitive Abhängigkeit?
Eine transitive Abhängigkeit ist ein Paket, das Ihr Projekt installiert, weil eine Ihrer Abhängigkeiten es benötigt, nicht weil Sie es selbst hinzugefügt haben. In npm werden transitive Abhängigkeiten in Ihrer Lock-Datei erfasst und erscheinen nach der Installation in node_modules, aber sie sind in der Datei Ihres Projekts nicht deklariert package.json.
Was ist der Unterschied zwischen direkten und transitiven Abhängigkeiten?
Direkte Abhängigkeiten sind die Pakete, die Ihr Projekt in seiner eigenen package.json. Transitive Abhängigkeiten umfassen alle Pakete, die von diesen Paketen auf jeder Ebene des Baums bezogen werden. Sie legen die Versionsbereiche der direkten Abhängigkeiten fest, während die Bereiche der transitiven Abhängigkeiten durch deren übergeordnete Pakete vorgegeben werden.
Wie kann ich meine transitiven Abhängigkeiten in npm einsehen?
ausführen. npm ls --all um Ihren vollständigen Abhängigkeitsbaum auszudrucken. Um herauszufinden, warum ein bestimmtes Paket installiert ist, führen Sie folgenden Befehl aus: npm explain <package>, das alle Abhängigkeitspfade anzeigt, die zu ihm führen.
Wie behebe ich eine Sicherheitslücke in einer transitiven Abhängigkeit?
Prüfen Sie zunächst, ob durch die Aktualisierung der darüber liegenden direkten Abhängigkeit die korrigierte Version mit einbezogen wird. Falls der Versionsbereich des übergeordneten Pakets die Korrektur blockiert, können Sie die seit npm 8.3 verfügbaren npm-Overrides nutzen, um eine gepatchte Version zu erzwingen – allerdings führen Sie damit eine Kombination aus, die der Betreuer des übergeordneten Pakets wahrscheinlich nicht getestet hat. Falls keine korrigierte Version existiert, weil das Paket nicht mehr gepflegt wird, stehen Ihnen folgende Optionen zur Verfügung: ein zurückportierter Fix, ein eigener Fork oder Patch oder das Ersetzen des Pakets, das davon abhängt.
Stellen transitive Abhängigkeiten ein Sicherheitsrisiko dar?
Ja. Jeder Teil ihres Codes, der ausgeführt wird, erhält denselben Zugriff wie Ihr Build oder Ihre App; einige davon führen Installationsskripte aus, und ein einziges Paket auf oberster Ebene kann Hunderte davon einbinden. Angriffe auf die Lieferkette, darunter „Event-Stream“ im Jahr 2018 und der „Mastra“-Hack im Jahr 2026, übermittelten ihre Schadlast über eine neu hinzugefügte transitive Abhängigkeit.
Was ist ein vererbtes Risiko?
Unter „vererbtem Risiko“ versteht man die Sicherheitsrisiken, denen ein Projekt durch Pakete ausgesetzt ist, die mit den von ihm ausgewählten Abhängigkeiten mitgeliefert wurden. Dies umfasst Schwachstellen, bösartigen Code und nicht mehr gepflegte Pakete an beliebiger Stelle im transitiven Abhängigkeitsbaum.
Quellen
- SecureChain für npm, TuxCare
- SecureChain für Open-Source-Software, TuxCare
- Unbegrenzter Lebenszyklus-Support, TuxCare
- Stand des Dependency-Managements 2022, Endor Labs
- 145 Mastra-npm-Pakete wurden über ein gehacktes Mitwirkendenkonto kompromittiert, The Hacker News
- npm-Pakete von Mastra kompromittiert, Socket
- Sicherheitslücke im Node.js-Modul „event-stream“, The Hacker News
- So überschreiben Sie die Abhängigkeiten Ihrer Abhängigkeit, Stefan Judis
- npm ls, npm-Dokumentation
- npm explain, npm-Dokumentation
- npm dedupe, npm-Dokumentation
- npm ci, npm-Dokumentation
- npm audit, npm-Dokumentation
- Überschreibungen in „package.json“, npm-Dokumentation
- package-lock.json, npm-Dokumentation
- Caret-Bereiche, node-semver
