Gefahren von Sub-Dependencies und Limitation von Dependency-Management Tools

19 Dez. 2025

10 minutes reading time

Wurzeln eines Baums

Gefahren von Sub-Dependencies

Mit den neuen OWASP Top 10:2025 (Release Candidate) rücken Software Supply Chain Failures nochmal deutlicher in den Fokus moderner Anwendungssicherheit, als in der Vorgängerausgabe aus dem Jahr 2021, in der Schwachstellen der Kategorie “Vulnerable and Outdated Components” auf Platz 6 rangierten. Obwohl die Kategorie in der Liste als A03 geführt wird, wurde sie in der Community-Umfrage am stärksten priorisiert. Exakt 50 % der Teilnehmenden stuften sie als die kritischste Risikoart ein. Gleichzeitig hat sich der Scope gegenüber früheren Jahren erweitert: Es geht nicht mehr nur um das „Verwenden von Komponenten mit bekannten Schwachstellen“, sondern um das gesamte Spektrum von Ausfällen und Risiken in der Software-Lieferkette.

Gerade in komplexen Projekten mit vielen Sub-Dependencies zeigt sich, wie schwierig eine verlässliche Absicherung in der Praxis ist. Dependency-Management-Tools wie npm-audit oder Dependabot stützen sich primär auf die GitHub Security Advisory Database (GHSA) als Referenz. Schwachstellen, die nicht (oder verspätet) dort veröffentlicht werden, wie etwa „stille“ Fixes durch Maintainer:innen oder Findings aus Bug-Bounty-Programmen, die vertraglich zunächst nur an die Publisher gemeldet werden, entgehen automatisierten Updates. Zudem blockieren Beschränkungen der Semantischen Versionierung (SemVer) oft das automatische Einspielen sicherer Sub-Dependency-Versionen, um Breaking Changes zu vermeiden. Die Folge: Verwundbare Bibliotheken verbleiben trotz verfügbarer Patches unnötig lange im Produktivcode.

In den vergangenen Monaten rückte neben veralteten oder verwundbaren Bibliotheken eine weitere Bedrohung in Form von teils selbst propagierenden Malware-Paketen über kompromittierte Accounts von Maintainer:innen in den Fokus. Im weit verzweigten und hochgradig transitiven npm-Ökosystem reichten wenige manipulierte Releases aus, um über zentrale Knoten schnell in zahlreiche Projekte und Systeme zu gelangen. Von dort riefen die Packages Prä- oder Post-Install-Skripte auf, um Schadcode auszuführen [1], [2], [3]. Die Vorfälle haben gezeigt, welche Gefahr von automatischen und unkontrollierten Updates ausgehen können und verdeutlicht umso mehr, weshalb Updates der Projekt-Dependencies kontrolliert und überlegt stattfinden sollten. Umso wichtiger ist es, die Update-Mechanismen von npm zu verstehen und Lösungen zu kennen, um mehr Kontrolle über den Dependency-Graphen des eigenen Projekts zu erlangen.

Limitierungen von Dependency-Management Tools

In diesem Blogartikel werden diese Entwicklungen zum Anlass genommen, anhand eines praktischen Beispiels einen tieferen Blick auf die Funktionalitäten des npm-Bordwerkzeugs npm-audit zu werfen. Dieses stellt das wahrscheinlich niederschwelligste Angebot dar, Projekt-Dependencies auf Vulnerabilitäten zu prüfen und vulnerable Versionen hochzuziehen.

Der Update-Prozess für vulnerable Sub-Dependencies

Wie npm-audit mit den Befehlen fix und fix --force vulnerable Sub-Dependencies zu aktualisieren versucht, soll im Folgenden anhand eines kurzen Beispiels mit Create-React-App erläutert werden. Das Projekt wird schon seit Längerem nicht mehr gewartet und ist seit dem 15.02.2025 offiziell als deprecated markiert. Für die Demonstration ist das aber unerheblich. Nach dem Erstellen der App über den npx-Befehl npx create-react-app, wird mit npm audit ein Vulnerabilitäts-Report angefertigt.

Vulnerabilitätsreport des Befehls npm audit 
Vulnerabilitätsreport des Befehls npm audit 

Der von npm-audit zeigt insgesamt 9 Vulnerabilitäten, die sich durch transitive Abhängigkeiten ergeben und auf drei Vulnerabilitäten in den Paketen nth-check, postcss und webpack-dev-server zurückzuführen sind. Der Report zeigt darüber hinaus an, welche Package-Versionen entlang der Dependency-Kette vom vulnerablen Package abhängen und zeigt, dass alle Vulnerabilitäten von der direkten Dependency react-scripts ausgehen. Um die Vulnerabilitäten zu beheben, würden dem/der Entwickler:in an dieser Stelle üblicherweise die Optionen die Optionen npm audit fix und npm audit fix --force zur Verfügung stehen.

Das Ziel von npm audit fix ist es dabei, die Schranken der semantischen Versionierung zu respektieren. Bei der genaueren Betrachtung der Vulnerabilität in nth-check, die mit dem Befehl npm ls nth-check möglich ist, wird das Update auf die Version 2.0.1 durch die transitive Dependency css-select@2.1.0 verhindert, welche durch die Versionsbeschränkung ^1.0.2 das Update von nth-check auf die neue Major-Version verhindert.

Ausgabe des Befehls npm ls nth-check
Ausgabe des Befehls npm ls nth-check

Damit der Befehl npm audit fix funktioniert, muss nth-check@2.0.1 also den SemVer-Beschränkungen des Parent-Packages css-select genügen. Da css-select dieses Update nicht erlaubt, versucht npm-audit eine Version von css-select zu installieren, die das Update von nth-check erlaubt und gleichzeitig die SemVer-Beschränkungen von css-selects Parent-Packages svgo einhält. Das Update von svgo muss wiederum den Beschränkungen von dessen Parent @svgr/plugin-svgo entsprechen. Diese Logik würde bis zur direkten Abhängigkeit react-scripts fortgeführt werden, mit dem Ziel, Versionsinkompatibilitäten zu verhindern.

Können - wie in diesem Beispiel - keine geeigneten Pakete gefunden werden, deren Versionsbeschränkungen das Fix-Update erlauben, schlägt npm-audit das Setzen der --force Option vor und warnt vor möglichen Breaking Changes. Diese Option erlaubt es npm-audit nun, Breaking Changes in Kauf zu nehmen um eine neue Major-Version von react-scripts zu installieren, dessen SemVer-Beschränkungen entlang des Dependency-Graphen die Installation der Fix-Version 2.0.1 von nth-check zulassen sollen.

Neben der Vulnerabilität in der Sub-Dependency nth-check hängt react-scripts noch von zwei weiteren vulnerablen Packages ab. Für die Vulnerabilität im Paket webpack-dev-server@4.15.2, von dem react-scripts direkt abhängt, gibt es keine react-scripts Version, die eine Fix-Version von webpack-dev-server verwendet. Als einzige Version von react-scripts, die keine Abhängigkeit auf webpack-dev-server hat, schlägt npm-audit vor, mit dem Befehl fix --force die Version 0.0.0 von react-scripts zu installieren, um das vulnerable Package und damit auch die Abhängigkeit zu entfernen. Da die Version 0.0.0 weder Funktionalität noch Dependencies besitzt, zeigt npm-audit nach der Ausführung folgerichtig 0 Vulnerabilities an. Starten lässt sich die Anwendung nun aber auch nicht mehr.

Ergebnis von des Aufrufs von npm audit fix --force
Ergebnis von des Aufrufs von npm audit fix --force

Die Vulnerabilität in webpack-dev-server wurde am 30.06.2025 veröffentlicht und ist damit als einzige Vulnerabilität nach der Deprecation von Create-React-App am 15.02.2025 veröffentlicht worden. Aus Demonstrationsgründen wird im Folgenden daher die Dependency von react-scripts zu webpack-dev-server in der package-lock.json entfernt. Der neue npm-audit Report zeigt folglich nur noch 8 Vulnerabilitäten an, die sich aus sechs transitiven Abhängigkeiten von nth-check und zwei transitiven Abhängigkeiten von postcss ergeben.

Der npm-audit Report, nach dem Update von webpack-dev-server
Der npm-audit Report, nach dem Update von webpack-dev-server

Statt eines Downgrades von react-scripts auf die Version 0.0.0 schlägt npm-audit nun vor, mit der --force Option die Version 3.0.1 zu installieren. Diese Version wird von npm-audit ausgewählt, da sie nicht die Dependency resolve-url-loader verwendet, die in react-scripts@5.0.1 von dem vulnerablen Package postcss@7.0.39 abhängt.

Anzahl von Vulnerabilitäten nach Downgrade auf react-scripts@3.0.1
Anzahl von Vulnerabilitäten nach Downgrade auf react-scripts@3.0.1

Nach der Ausführung des Befehls zeigt der Report insgesamt 170 Vulnerabilitäten, die auf 22 vulnerable Packages zurückzuführen sind. Eine deutlich größere Anzahl, die durch den Downgrade auf react-scripts@3.0.1 entsteht. Und auch obwohl die ursprünglich vulnerable Abhängigkeit von resolve-url-loader zu postcss@7.0.39 durch das Downgrade behoben wurde, da react-scripts@3.0.1 (noch) nicht von resolve-url-loader abhängt, enthält react-scripts@3.0.1 deutlich mehr Dependencies, die von einer vulnerablen Version von postcss abhängen. Die vulnerable Abhängigkeit, die versucht wird durch npm audit fix --force zu beseitigen, wird also nicht nur nicht beseitigt, sondern zusätzlich exponiert, da die Anwendung durch die veraltete Version von react-scripts nun an mehreren Stellen und in der Theorie über mehrere Codepfade von einer vulnerablen postcss Version abhängt. Npm-audit optimiert also nicht den Dependency-Graphen nach “möglichst wenig Vulnerabilitäten” sondern versucht lediglich die vulnerablen Pfade aus dem Report zu beheben.

Die Security Updates von GitHubs Dependabot bauen auf den Funktionalitäten von npm-audit auf, um Vulnerabilitäten in Sub-Dependencies zu beheben. Die Limitationen von npm-audit, Vulnerabilitäten in Sub-Dependencies nur durch das Anpassen der direkten Dependencies eines Projekts beheben zu können, werden also auf die Security Updates übertragen.

Bei Betrachtung der Veröffentlichungsdaten der verschiedenen Fehlerbehebungen wird klar, wie sehr npm-audit und seine Nutzer:innen von den Maintainer:innen der verwendeten Pakete abhängig sind, und davon, dass diese ihre Abhängigkeiten und deren Versionseinschränkungen regelmäßig aktualisieren. Zum Beispiel wurde svgo@2.0.0 schon am 17.02.2021 veröffentlicht, also lange bevor die Sicherheitslücke in nth-check (veröffentlicht am 20.09.2021) bekannt war. Bereits zu diesem Zeitpunkt erlaubten die Versionsangaben ein Update von css-select auf 3.1.1 und damit auch die Installation der fehlerbereinigten Version nth-check@2.0.1.
Das Paket @svgr/plugin-svgo@6.0.0 wurde jedoch erst am 28.11.2021 veröffentlicht, also etwa zwei Monate nachdem die Sicherheitslücke bekannt wurde. In den vorherigen Versionen war svgo auf die Hauptversion 1.x.x beschränkt, sodass das Sicherheitsupdate von nth-check nicht installiert werden konnte. Außerdem verhindert die Paket-Abhängigkeit von react-scripts@5.0.1, dass @svgr/plugin-svgo auf Version 6.0.0 aktualisiert wird – obwohl diese Version bei der Veröffentlichung von react-scripts@5.0.1 bereits verfügbar war. Da react-scripts nun nicht mehr weiterentwickelt wird, müssen Entwickler:innen auf eine andere Lösung ausweichen.

Overrides als Alternative

Eine Möglichkeit, Vulnerabilitäten in Sub-Dependencies zu beheben stellt die Verwendung von Overrides dar. Beginnend mit der npm-Version 8.3 kann das overrides-Feld in der package.json dafür verwendet werden, Dependencies im Dependency-Graphen gezielt mit gewünschten Versionen oder auch geforkten Varianten zu überschreiben. Overrides werden gegenüber den SemVer-Beschränkungen priorisiert und bieten damit Entwickler:innen die Möglichkeit, mehr Kontrolle über die im Projekt befindlichen Sub-Dependencies zu übernehmen und weniger abhängig von den Dependency-Aktualisierungen und Versionseinschränkungen fremder Maintainer:innen zu sein.

Im obigen Beispiel könnte ein Override folgendermaßen aussehen:

"overrides": {
  "nth-check": "2.0.1"
}

Hierbei wird nth-check direkt mit der Patch-Version 2.0.1 überschrieben, ohne dass die darüberliegenden Packages von css-select bis react-scripts angepasst werden müssten. Durch das Verschachteln von Overrides, sog. Nested-Overrides, ist es darüber hinaus möglich, Sub-Dependencies lediglich an einer bestimmten Stelle im Dependency-Graphen auszutauschen, indem lediglich die Sub-Dependency eines bestimmten Packages selektiert wird.

"overrides": {
  "svgo": {
    "css-select": {
      "nth-check": 2.0.1
    }
  }
}

Durch die Verschachtelung wird lediglich die nth-check Dependency auf die gewünschte Version aktualisiert, die vom css-select -Package verwendet wird, welches wiederum eine Dependency des Packages svgo ist. In der package-lock.json kann dies nachvollzogen werden:

Die Sub-Dependency nth-check wird vom Package css-select@2.1.0 auf die Major-Version 1.x.x beschränkt.
Die Sub-Dependency nth-check wird vom Package css-select@2.1.0 auf die Major-Version 1.x.x beschränkt.
Durch die Verwendung des Nested-Overrides wird lediglich die nth-checkDependency im node_modules Verzeichnis des svgoPackages ausgetauscht.
Durch die Verwendung des Nested-Overrides wird lediglich die nth-checkDependency im node_modules Verzeichnis des svgoPackages ausgetauscht.

Nested-Overrides erlauben das Austauschen von Dependencies an bestimmten Stellen des Dependency-Graphen. In diesem Beispiel wird ausschließlich die Sub-Dependency nth-check aktualisiert, welche von css-select als Dependency von svgo verwendet wird. Alle anderen Vorkommen von nth-check, beispielsweise als Abhängigkeit von css-select@2.0.1 an anderer Stelle im Graphen, bleiben unverändert und nutzen weiterhin die potenziell unsichere Version 1.0.2.

Dadurch bleibt der Dependency-Graph an diesen Stellen zwar weiterhin verwundbar. Allerdings lassen sich so mögliche Breaking Changes vermeiden, die entstehen könnten, wenn SemVer-Beschränkungen missachtet werden. Damit Overrides nicht zu unerwarteten Problemen führen und die Stabilität der Anwendung gewährleistet bleibt, ist es jedoch wichtig, die Anwendung nach solchen Änderungen gründlich zu testen.

Zurück zur Malware

Automatische Updates mithilfe von npm-audit können dazu führen, dass Schadsoftware Einzug ins Projekt erhält. Es empfiehlt sich daher mit dem Befehl npm audit fix --dry-run --json einen Testdurchlauf des Updates durchzuführen und sich die Informationen anzeigen und zusammenfassen zu lassen. Auf diesem Weg lässt sich prüfen, welche Packages in welchen Versionen im Zuge eines Updates installiert werden. Sollte sich unter den Updates eine vulnerable oder wider Erwarten kompromittierte Version befinden, da diese möglicherweise noch nicht deaktiviert wurde, kann dessen Installation durch das Pinnen von Packages auf eine gewünschte Version mithilfe von Overrides verhindert werden.

Fazit

Trotz vieler Vorteile hinsichtlich der Entwicklungsgeschwindigkeit und Softwarequalität, die durch die Wiederverwendung von Softwarepaketen entstehen, spiegeln die neuen OWASP Top 10 die wachsende Bedrohung von tiefen Dependency-Graphen mit vielen Software-Paketen wider. Diese im Überblick und auf dem aktuellen Stand halten und sich gleichzeitig vor Malware-Paketen zu schützen, ist eine anspruchsvolle Herausforderung. Gerade im npm-Ökosystem entstehen schnell tief verschachtelte Abhängigkeitsgraphen mit wenigen, aber sehr weit verbreiteten Paketen, die dadurch ein attraktives Ziel für Malware-Angriffe sind. Tools wie npm-audit und Dependabot helfen beim Verwalten von Abhängigkeiten, stoßen aber oft an ihre Grenzen, weil sie sich an semantische Versionsbeschränkungen halten müssen. Overrides bieten Entwickler:innen mehr Kontrolle über die Abhängigkeiten im Projekt, da sie es ermöglichen, bestimmte Pakete gezielt und unabhängig von den Versionsbeschränkungen auf eine neuere Version zu aktualisieren. Eine Herausforderung bei der Verwendung von Overrides stellt allerdings der Umgang mit Breaking Changes dar, die durch das Ignorieren von den Versionsbeschränkungen durch die Updates entstehen können. Zusammen mit der --dry-run Flag können Overrides allerdings ein zielführendes Werkzeug sein, um Updates kontrolliert und gezielt durchzuführen und um mehr Kontrolle über die transitiv installierten Dependencies zu verfügen, um Gefahren, zumindest durch gemeldete CVEs, für das Projekt gering zu halten.

Next article

Robot Framework im Advent - Was legen wir uns unter den Baum

Robot Framework im Advent