Transitive Abhängigkeiten
Ein verwundbares Paket ist oft keines, das du deklariert hast. Eine andere Abhängigkeit zieht es herein, und diese Abhängigkeit wählt seine Version. Es dort anzuheben, wo es deklariert ist, geht nicht, weil es nirgends in deinem Repository deklariert ist. Sicherheitskorrekturen legen ein solches Paket stattdessen von der Wurzel deines Projekts aus fest, mit dem Mechanismus, den das jeweilige Ökosystem dafür vorsieht.
| Ökosystem | Wie das Paket festgelegt wird | Wie du es anforderst |
|---|---|---|
npm (package-lock.json) | Ein Eintrag overrides in package.json, und die Einträge des Pakets in package-lock.json werden auf die korrigierte Version gesetzt | Korrektur-PR öffnen am Befund, wie bei jedem anderen. Findet gehostetes Renovate nichts anzuheben, legt fremforge es innerhalb von etwa zehn Minuten fest |
pnpm (pnpm-lock.yaml) | Ein Eintrag overrides in pnpm-workspace.yaml (oder pnpm.overrides in package.json für pnpm 9 und älter), und der Lock wird passend nachgezogen | Wie bei npm |
Yarn 1 (yarn.lock) | Ein Eintrag resolutions in package.json, und der Eintrag in yarn.lock wird nachgezogen | Wie bei npm. Yarn 2 und neuer werden abgelehnt |
PyPI (uv.lock, poetry.lock) | Der Lock-Eintrag des Pakets wird auf die korrigierte Version gesetzt, ohne Festlegung in pyproject.toml | Wie bei npm |
Maven (pom.xml) | Ein Eintrag <dependencyManagement> in der pom.xml, aus der der Befund stammt | Korrektur-PR öffnen, wie bei npm |
| Maven (in deiner SBOM gesehen) | Ein Eintrag <dependencyManagement> in der pom.xml im Wurzelverzeichnis | Als transitiv festlegen am Befund, oder POST /orgs/{org}/dependency-updates/security-fixes/transitive-requests |
Gradle (gradle.lockfile) | Ein Eintrag constraints in der Build-Datei, und die Zeile in der Lockfile wird nachgezogen | Wie bei npm |
| Go | Nichts weiter: go.mod führt jedes Modul auf, das dein Build verwendet, markiert mit // indirect, und die Korrektur hebt diese Zeile an | Korrektur-PR öffnen |
Es ist ein gewöhnlicher Pull Request für eine Sicherheitskorrektur, und es gilt dasselbe Release-Alter: Eine korrigierte Version, die jünger ist als das Release-Alter (Tage) deiner Organisation (standardmäßig 3), wird noch nicht vorgeschlagen, es sei denn, sie behebt eine bekanntermaßen ausgenutzte Schwachstelle. Weil kein Renovate-Lauf eine transitive Festlegung prüft, liest fremforge den Veröffentlichungszeitpunkt selbst, über deinen Dependency-Proxy: time aus der npm-Registry, bei PyPI den Upload-Zeitpunkt der Dateien der korrigierten Version und bei Maven und Gradle den Zeitpunkt, den Maven Central für die pom angibt. Eine korrigierte Version, deren Veröffentlichungszeitpunkt sich nicht lesen lässt, wird nicht vorgeschlagen.
Er hat auch denselben Branch (security-fix/<package>-<version>), dasselbe Label (security-fix), dieselben Limits und dieselbe Merge-Einstellung wie jeder andere, und es gilt dieselbe Wahl unter Wann Korrekturen gemergt werden. Der Pull Request gibt an, dass das Paket transitiv ist und wie es festgelegt wurde.
npm
Der Pull Request ändert zwei Dateien und sonst nichts:
"overrides": {
"lodash": "4.18.0"
}Das wird zu package.json hinzugefügt. Jeder Eintrag des Pakets in package-lock.json mit einer verwundbaren Version wird auf die korrigierte Version gesetzt: Version und Tarball-URL werden geändert, und der Integritäts-Hash, wo der Eintrag einen hatte. Die URL behält die Registry deiner Lockfile.
fremforge führt weder npm install noch sonst etwas aus deinem Repository aus. Es schreibt die Lock-Einträge aus den Metadaten der Registry, gelesen über den Dependency-Proxy deiner Organisation, sodass deine Paketrichtlinien auch für die korrigierte Version gelten. Es schreibt sie nur, wenn das alles ist, was eine Auflösung ändern würde:
- Die korrigierte Version deklariert genau die Abhängigkeiten der Version, die sie ersetzt. Andernfalls wird die Anforderung mit
transitive_dependencies_changedabgelehnt, weil sich der Baum über dieses Paket hinaus ändern würde. - Jede Kopie des Pakets im Lock hat eine verwundbare Version. Ein Override verschiebt jede Kopie, daher führt eine Kopie mit einer anderen, nicht betroffenen Version zur Ablehnung mit
override_conflict. package-lock.jsonhatlockfileVersion2 oder 3.
npm ci akzeptiert das Ergebnis. Entferne den Override, sobald die Pakete, die das Paket hereinziehen, selbst die korrigierte Version verlangen.
pnpm
pnpm hält Overrides auch in der Lockfile fest und lehnt einen Lock ab, dessen Abschnitt overrides: von der Konfiguration abweicht. Der Pull Request ändert daher beides:
- den Override in
pnpm-workspace.yaml(die Datei wird angelegt, wenn das Repository keine hat). Er kommt stattdessen inpnpm.overridesinpackage.json, wennpackageManagerpnpm 9 oder älter nennt, oder, ohnepackageManager, wenn das Projekt seine Overrides dort hält und keinepnpm-workspace.yamlhat; pnpm-lock.yaml: den Abschnittoverrides:, die Einträge des Pakets unterpackagesundsnapshotsmit der Integrität aus der Registry und jeden Verweis auf die alte Version.
pnpm install --frozen-lockfile akzeptiert das Ergebnis. Unterstützt wird nur lockfileVersion: '9.0'; ein Paket, das mit einem Peer-Dependency-Suffix wie foo@1.0.0(react@18.3.1) gelockt ist, wird mit lock_entry_unsupported abgelehnt.
Yarn
Bei Yarn 1 fügt der Pull Request einen Eintrag resolutions zu package.json hinzu und zieht den Eintrag des Pakets in yarn.lock nach: seine Version, seine URL unter resolved (auf der Registry deiner Lockfile, mit der Prüfsumme des Tarballs) und seine integrity. yarn install --frozen-lockfile akzeptiert das Ergebnis.
Yarn 2 und neuer (eine yarn.lock mit __metadata) werden mit yarn_berry_checksum abgelehnt. Ihr Lock hält eine Prüfsumme des Archivs fest, das Yarn aus dem Paket baut, und die kann nur Yarn selbst berechnen. Füge selbst einen Eintrag resolutions hinzu und führe yarn install aus.
Für pnpm und Yarn gelten dieselben Regeln wie für npm: Die korrigierte Version muss dieselben Abhängigkeiten deklarieren, und jede Kopie des Pakets im Lock muss eine verwundbare Version haben.
Python
Bei uv.lock und poetry.lock setzt der Pull Request den Lock-Eintrag des Pakets auf die korrigierte Version und fügt pyproject.toml nichts hinzu. Ein Paket, das pyproject.toml deklariert, wird mit already_declared abgelehnt, weil Korrektur-PR öffnen es dort anhebt. Der neue Eintrag enthält die Dateien der korrigierten Version mit ihren Hashes, gelesen aus dem PyPI-Dependency-Proxy deiner Organisation:
- uv:
sdistundwheels, mit URL, Hash, Größe und Upload-Zeitpunkt, soweit dein Lock sie festhält. Die URLs behalten den Index deines Locks: die eigene URL des Dependency-Proxys, wenn du gegen ihn lockst, oderfiles.pythonhosted.org, wenn du gegen PyPI lockst. Ein Lock aus einem anderen Index wird mitlock_entry_unsupportedabgelehnt. - Poetry: Version, Beschreibung,
python-versionsundfiles. Dercontent-hashvon Poetry deckt nurpyproject.tomlab und ändert sich daher nicht.
Die Regel ist dieselbe wie bei npm, angewendet auf die Metadaten von Python: Die korrigierte Version muss dieselben Requires-Dist und Requires-Python haben wie die Version, die sie ersetzt, gelesen aus den Metadaten ihres Wheels. Andernfalls wird die Anforderung mit transitive_dependencies_changed abgelehnt. uv sync --locked und poetry install akzeptieren das Ergebnis. Das Paket bleibt auf der korrigierten Version, bis ein späteres uv lock oder poetry lock neu auflöst.
Maven
Das Abhängigkeits-Scanning löst jede pom.xml auf, daher ist ein verwundbares Paket, das dein Build hereinzieht, ein Befund an dieser pom.xml. Korrektur-PR öffnen funktioniert dort wie bei npm: Findet gehostetes Renovate nichts anzuheben, weil die pom das Paket nicht nennt, legt fremforge es innerhalb von etwa zehn Minuten in dieser pom.xml fest. Ein Paket, das nur in einer hochgeladenen SBOM auftaucht, etwa einer aus cyclonedx-maven-plugin (siehe Abhängigkeitspfade), zeigt in der Spalte Fix stattdessen Als transitiv festlegen und wird in der pom.xml im Wurzelverzeichnis festgelegt. In beiden Fällen fügt der Pull Request Folgendes hinzu:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.11</version>
</dependency>
</dependencies>
</dependencyManagement>Das setzt die Version des Pakets überall, wo es im Build vorkommt. Verwaltet die pom es bereits mit einer niedrigeren literalen Version, wird diese Version an Ort und Stelle angehoben. Abgelehnt wird, wenn:
- die pom das Paket als eigene Abhängigkeit deklariert (
already_declared). Korrektur-PR öffnen hebt es dort an; - die verwaltete Version aus einer Property gelesen wird, etwa
${jackson.version}, die auch andere Einträge lesen können (managed_by_property); - es bereits mit der korrigierten oder einer höheren Version verwaltet wird (
already_managed).
Prüfe, ob die Abhängigkeiten, die das Paket hereinziehen, mit der neuen Version funktionieren, denn eine Bibliothek ist meist gegen die Version getestet, die sie verlangt hat. Entferne den Eintrag, sobald sie selbst die korrigierte Version verlangen.
Gradle
Bei einem Gradle-Build mit Lockfile (gradle.lockfile) fügt der Pull Request dem Block dependencies der Build-Datei neben der Lockfile (build.gradle.kts oder build.gradle) eine Constraint hinzu:
dependencies {
constraints {
implementation("org.yaml:snakeyaml:2.0") {
because("fremforge security fix: pins a transitive dependency to a version that fixes its open advisories")
}
}
}Er zieht auch die Zeile des Pakets in gradle.lockfile nach, sodass Lock und Build übereinstimmen und Gradle den Lock im strikten Modus akzeptiert. Wie bei Maven muss die pom der korrigierten Version dieselben Abhängigkeiten und dasselbe Parent deklarieren wie die, die sie ersetzt. Abgelehnt wird, wenn:
- die Build-Datei das Paket bereits nennt (
already_declared); - der Build Abhängigkeiten mit
gradle/verification-metadata.xmlprüft (gradle_verification_metadata). Die neue Version braucht Prüfsummen in dieser Datei, die Gradle berechnet; führe./gradlew --write-verification-metadata sha256selbst aus; - das Paket in einer Konfiguration gelockt ist, die eine Constraint
implementationnicht erreicht, etwa im eigenen Classpath eines Plugins (gradle_configuration_unsupported); - die Build-Datei keinen Block
dependencies {auf oberster Ebene hat (build_file_unsupported).
fremforge führt Gradle nicht aus. Ein Gradle-Build des Pull Requests in deiner CI prüft den Lock gegen den Build und schlägt fehl, wenn sie nicht übereinstimmen.
Wenn kein Pull Request geöffnet wird
Die Anforderung zeigt dann den Grund. Über die API ist reason dann transitive_<why>, und detail erklärt ihn:
| Grund | Bedeutung |
|---|---|
transitive_already_declared | Das Paket ist eine direkte Abhängigkeit. Korrektur-PR öffnen hebt es dort an, wo es deklariert ist |
transitive_transitive_dependencies_changed | Die korrigierte Version deklariert andere Abhängigkeiten (bei Python auch ein anderes Requires-Python), daher braucht der Lock eine vollständige Auflösung |
transitive_override_conflict | Eine andere Kopie des Pakets mit einer nicht betroffenen Version würde mitverschoben, oder ein Override, eine Resolution oder eine Constraint legt es bereits anders fest |
transitive_yarn_berry_checksum | Yarn 2 oder neuer: Sein Lock braucht eine Prüfsumme, die nur Yarn berechnen kann |
transitive_gradle_verification_metadata | Gradle: Der Build prüft Abhängigkeiten, und die neue Version braucht Prüfsummen, die Gradle berechnet |
transitive_gradle_configuration_unsupported | Gradle: Das Paket ist in einer Konfiguration gelockt, die eine Constraint implementation nicht erreicht |
transitive_build_file_unsupported | Gradle: Die Build-Datei hat keinen Block dependencies { auf oberster Ebene |
transitive_lock_entry_unsupported | Der Lock-Eintrag hat eine Form, die fremforge nicht bearbeitet, etwa ein pnpm-Peer-Suffix, mehrere gelockte Versionen oder einen uv-Lock aus einem Index, den es nicht bedient |
transitive_managed_by_property | Maven: Die verwaltete Version kommt aus einer Property |
transitive_already_managed | Maven: bereits mit der korrigierten oder einer höheren Version verwaltet |
transitive_lock_version_unsupported | npm: lockfileVersion 1. pnpm: eine andere Lockfile-Version als 9.0 |
transitive_manifest_not_supported | Eine Lockfile, die oben nicht aufgeführt ist, etwa bun.lock, pdm.lock oder Pipfile.lock |
transitive_target_metadata_missing | Der Dependency-Proxy hat keine Metadaten für die korrigierte Version geliefert, zum Beispiel weil eine Paketrichtlinie sie abweist |
transitive_manifest_missing, transitive_lock_missing | Das Manifest oder die Lockfile liegt auf dem Standard-Branch nicht am erwarteten Pfad |
transitive_release_age_pending | Die korrigierte Version ist jünger als dein Release-Alter; frage nach dem Datum, das das Detail nennt, erneut an |
transitive_release_age_unknown | Der Dependency-Proxy hat keinen Veröffentlichungszeitpunkt für die korrigierte Version geliefert |
transitive_branch_exists | Der Branch security-fix/<package>-<version> existiert bereits |
transitive_repo_not_writable | Das Repository ist archiviert oder ein Mirror |
Bei einer Anforderung über Korrektur-PR öffnen (jedes Ökosystem außer Maven) behält die Anforderung Kein PR: nichts zu ändern, und das Detail nennt den Grund.
API
npm, pnpm, Yarn, Python und Gradle brauchen nichts außer POST /orgs/{org}/dependency-updates/security-fixes/requests. Für Maven:
curl -sS -X POST "https://frem.sh/_app/api/v1/orgs/acme/dependency-updates/security-fixes/transitive-requests" \
-H "Authorization: Bearer $FREMFORGE_TOKEN" -H "Content-Type: application/json" \
-d '{"finding_id": "<dependency finding id>"}'Die Antwort ist 201 mit der Anforderung und ihrem Ergebnis: pr_opened mit pr_number und pr_url oder rejected mit reason und detail. Eine Ablehnung, bevor eine Anforderung existiert, ist ein Problem vom Typ security-fix-<reason>, zum Beispiel security-fix-not-transitive. Der Scope ist dependency-updates:write. Die Anforderung wird danach wie jede andere von GET …/security-fixes/requests aufgeführt.