Zum Hauptinhalt springen

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.

ÖkosystemWie das Paket festgelegt wirdWie 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 gesetztKorrektur-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 nachgezogenWie bei npm
Yarn 1 (yarn.lock)Ein Eintrag resolutions in package.json, und der Eintrag in yarn.lock wird nachgezogenWie 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.tomlWie bei npm
Maven (pom.xml)Ein Eintrag <dependencyManagement> in der pom.xml, aus der der Befund stammtKorrektur-PR öffnen, wie bei npm
Maven (in deiner SBOM gesehen)Ein Eintrag <dependencyManagement> in der pom.xml im WurzelverzeichnisAls 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 nachgezogenWie bei npm
GoNichts weiter: go.mod führt jedes Modul auf, das dein Build verwendet, markiert mit // indirect, und die Korrektur hebt diese Zeile anKorrektur-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_changed abgelehnt, 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.json hat lockfileVersion 2 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 in pnpm.overrides in package.json, wenn packageManager pnpm 9 oder älter nennt, oder, ohne packageManager, wenn das Projekt seine Overrides dort hält und keine pnpm-workspace.yaml hat;
  • pnpm-lock.yaml: den Abschnitt overrides:, die Einträge des Pakets unter packages und snapshots mit 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: sdist und wheels, 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, oder files.pythonhosted.org, wenn du gegen PyPI lockst. Ein Lock aus einem anderen Index wird mit lock_entry_unsupported abgelehnt.
  • Poetry: Version, Beschreibung, python-versions und files. Der content-hash von Poetry deckt nur pyproject.toml ab 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.xml prüft (gradle_verification_metadata). Die neue Version braucht Prüfsummen in dieser Datei, die Gradle berechnet; führe ./gradlew --write-verification-metadata sha256 selbst aus;
  • das Paket in einer Konfiguration gelockt ist, die eine Constraint implementation nicht 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:

GrundBedeutung
transitive_already_declaredDas Paket ist eine direkte Abhängigkeit. Korrektur-PR öffnen hebt es dort an, wo es deklariert ist
transitive_transitive_dependencies_changedDie korrigierte Version deklariert andere Abhängigkeiten (bei Python auch ein anderes Requires-Python), daher braucht der Lock eine vollständige Auflösung
transitive_override_conflictEine 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_checksumYarn 2 oder neuer: Sein Lock braucht eine Prüfsumme, die nur Yarn berechnen kann
transitive_gradle_verification_metadataGradle: Der Build prüft Abhängigkeiten, und die neue Version braucht Prüfsummen, die Gradle berechnet
transitive_gradle_configuration_unsupportedGradle: Das Paket ist in einer Konfiguration gelockt, die eine Constraint implementation nicht erreicht
transitive_build_file_unsupportedGradle: Die Build-Datei hat keinen Block dependencies { auf oberster Ebene
transitive_lock_entry_unsupportedDer 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_propertyMaven: Die verwaltete Version kommt aus einer Property
transitive_already_managedMaven: bereits mit der korrigierten oder einer höheren Version verwaltet
transitive_lock_version_unsupportednpm: lockfileVersion 1. pnpm: eine andere Lockfile-Version als 9.0
transitive_manifest_not_supportedEine Lockfile, die oben nicht aufgeführt ist, etwa bun.lock, pdm.lock oder Pipfile.lock
transitive_target_metadata_missingDer Dependency-Proxy hat keine Metadaten für die korrigierte Version geliefert, zum Beispiel weil eine Paketrichtlinie sie abweist
transitive_manifest_missing, transitive_lock_missingDas Manifest oder die Lockfile liegt auf dem Standard-Branch nicht am erwarteten Pfad
transitive_release_age_pendingDie korrigierte Version ist jünger als dein Release-Alter; frage nach dem Datum, das das Detail nennt, erneut an
transitive_release_age_unknownDer Dependency-Proxy hat keinen Veröffentlichungszeitpunkt für die korrigierte Version geliefert
transitive_branch_existsDer Branch security-fix/<package>-<version> existiert bereits
transitive_repo_not_writableDas 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.