Transitieve afhankelijkheden
Een kwetsbaar pakket is vaak niet een pakket dat je zelf hebt gedeclareerd. Een andere afhankelijkheid haalt het binnen, en die afhankelijkheid kiest de versie. Het verhogen waar het gedeclareerd is, kan niet, want het is nergens in je repository gedeclareerd. Beveiligingsfixes zetten zo’n pakket daarom vast vanuit de root van je project, met het mechanisme dat elk ecosysteem daarvoor biedt.
| Ecosysteem | Hoe het pakket wordt vastgezet | Hoe je erom vraagt |
|---|---|---|
npm (package-lock.json) | Een overrides-vermelding in package.json, en de vermeldingen van het pakket in package-lock.json verplaatst naar de gefixte versie | Fix-PR openen bij de bevinding, net als bij elke andere. Vindt gehoste Renovate niets om te verhogen, dan zet fremforge het binnen ongeveer tien minuten vast |
pnpm (pnpm-lock.yaml) | Een overrides-vermelding in pnpm-workspace.yaml (of pnpm.overrides in package.json voor pnpm 9 en ouder), en de lock daarop aangepast | Net als bij npm |
Yarn 1 (yarn.lock) | Een resolutions-vermelding in package.json, en de vermelding in yarn.lock verplaatst | Net als bij npm. Yarn 2 en later worden geweigerd |
PyPI (uv.lock, poetry.lock) | De lock-vermelding van het pakket verplaatst naar de gefixte versie, zonder vastzetting in pyproject.toml | Net als bij npm |
Maven (pom.xml) | Een <dependencyManagement>-vermelding in de pom.xml waar de bevinding uit komt | Fix-PR openen, net als bij npm |
| Maven (gezien in je SBOM) | Een <dependencyManagement>-vermelding in de root-pom.xml | Vastzetten als transitief bij de bevinding, of POST /orgs/{org}/dependency-updates/security-fixes/transitive-requests |
Gradle (gradle.lockfile) | Een constraints-vermelding in het buildbestand, en de regel in de lockfile verplaatst | Net als bij npm |
| Go | Niets extra’s: go.mod somt elke module op die je build gebruikt, gemarkeerd met // indirect, en de fix verhoogt die regel | Fix-PR openen |
Het is een gewone pull request voor een beveiligingsfix, en dezelfde releaseleeftijd geldt: een gefixte versie die jonger is dan de Releaseleeftijd (dagen) van je organisatie (standaard 3) wordt nog niet voorgesteld, tenzij ze een bekend misbruikte kwetsbaarheid oplost. Omdat geen Renovate-run een transitieve vastzetting controleert, leest fremforge de publicatietijd zelf, via je dependency proxy: time uit het npm-register, de uploadtijd van de bestanden van de gefixte versie op PyPI, en voor Maven en Gradle de tijd die Maven Central voor de pom geeft. Een gefixte versie waarvan de publicatietijd niet te lezen is, wordt niet voorgesteld.
Hij heeft ook dezelfde branch (security-fix/<package>-<version>), hetzelfde label (security-fix), dezelfde limieten en dezelfde merge-instelling als elke andere, en dezelfde keuze onder Wanneer fixes worden gemerged geldt. De pull request vermeldt dat het pakket transitief is en hoe het is vastgezet.
npm
De pull request wijzigt twee bestanden en verder niets:
"overrides": {
"lodash": "4.18.0"
}Dat wordt toegevoegd aan package.json. Elke vermelding van het pakket in package-lock.json met een kwetsbare versie wordt verplaatst naar de gefixte versie: de versie en de tarball-URL veranderen, en de integriteitshash verandert waar de vermelding er een had. De URL houdt het register van je lockfile.
fremforge voert geen npm install uit, en ook niets anders uit je repository. Het schrijft de lock-vermeldingen op basis van de metadata van het register, gelezen via de dependency proxy van je organisatie, zodat je pakketbeleid ook voor de gefixte versie geldt. Het schrijft ze alleen als dat het enige is wat een resolve zou veranderen:
- De gefixte versie declareert precies dezelfde afhankelijkheden als de versie die ze vervangt. Anders wordt de aanvraag geweigerd met
transitive_dependencies_changed, omdat de boom verder zou veranderen dan dit ene pakket. - Elke kopie van het pakket in de lock heeft een kwetsbare versie. Een override verplaatst elke kopie, dus een kopie met een andere, niet-getroffen versie weigert de aanvraag met
override_conflict. package-lock.jsonheeftlockfileVersion2 of 3.
npm ci accepteert het resultaat. Verwijder de override zodra de pakketten die het pakket binnenhalen zelf de gefixte versie vereisen.
pnpm
pnpm legt overrides ook in de lockfile vast, en weigert een lock waarvan de sectie overrides: afwijkt van de configuratie. De pull request wijzigt daarom beide:
- de override, in
pnpm-workspace.yaml(aangemaakt als de repository er geen heeft). Hij komt in plaats daarvan inpnpm.overridesinpackage.jsonalspackageManagerpnpm 9 of ouder noemt, of, zonderpackageManager, als het project zijn overrides daar bijhoudt en geenpnpm-workspace.yamlheeft; pnpm-lock.yaml: de sectieoverrides:, de vermeldingen van het pakket onderpackagesensnapshotsmet de integriteit uit het register, en elke verwijzing naar de oude versie.
pnpm install --frozen-lockfile accepteert het resultaat. Alleen lockfileVersion: '9.0' wordt ondersteund; een pakket dat is gelockt met een peer-dependency-suffix zoals foo@1.0.0(react@18.3.1) wordt geweigerd met lock_entry_unsupported.
Yarn
Voor Yarn 1 voegt de pull request een resolutions-vermelding toe aan package.json en verplaatst hij de vermelding van het pakket in yarn.lock: de versie, de resolved-URL (op het register van je lockfile, met de checksum van de tarball) en de integrity. yarn install --frozen-lockfile accepteert het resultaat.
Yarn 2 en later (een yarn.lock met __metadata) worden geweigerd met yarn_berry_checksum. Hun lock legt een checksum vast van het archief dat Yarn uit het pakket bouwt, en die kan alleen Yarn zelf berekenen. Voeg zelf een resolutions-vermelding toe en voer yarn install uit.
Voor pnpm en Yarn gelden dezelfde regels als voor npm: de gefixte versie moet dezelfde afhankelijkheden declareren, en elke kopie van het pakket in de lock moet een kwetsbare versie hebben.
Python
Voor uv.lock en poetry.lock verplaatst de pull request de lock-vermelding van het pakket naar de gefixte versie en voegt hij niets toe aan pyproject.toml. Een pakket dat pyproject.toml declareert, wordt geweigerd met already_declared, omdat Fix-PR openen het daar verhoogt. De nieuwe vermelding bevat de bestanden van de gefixte versie met hun hashes, gelezen uit de PyPI-dependency proxy van je organisatie:
- uv: de
sdistenwheels, met URL, hash, grootte en uploadtijd waar je lock die vastlegt. De URL’s houden de index van je lock: de eigen URL van de dependency proxy als je daartegen lockt, offiles.pythonhosted.orgals je tegen PyPI lockt. Een lock van een andere index wordt geweigerd metlock_entry_unsupported. - Poetry: de versie, de beschrijving,
python-versionsenfiles. Decontent-hashvan Poetry dekt alleenpyproject.toml, dus die verandert niet.
De regel is dezelfde als voor npm, toegepast op de metadata van Python: de gefixte versie moet dezelfde Requires-Dist en Requires-Python hebben als de versie die ze vervangt, gelezen uit de metadata van haar wheel. Anders wordt de aanvraag geweigerd met transitive_dependencies_changed. uv sync --locked en poetry install accepteren het resultaat. Het pakket blijft op de gefixte versie totdat een latere uv lock of poetry lock opnieuw resolvet.
Maven
Het scannen van afhankelijkheden resolvet elke pom.xml, dus een kwetsbaar pakket dat je build binnenhaalt, is een bevinding op die pom.xml. Fix-PR openen werkt daar net als bij npm: vindt gehoste Renovate niets om te verhogen, omdat de pom het pakket niet noemt, dan zet fremforge het binnen ongeveer tien minuten vast in die pom.xml. Een pakket dat alleen voorkomt in een SBOM die je uploadt, zoals een van cyclonedx-maven-plugin (zie afhankelijkheidspaden), toont in de kolom Fix in plaats daarvan Vastzetten als transitief, en wordt vastgezet in de root-pom.xml. In beide gevallen voegt de pull request toe:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.11</version>
</dependency>
</dependencies>
</dependencyManagement>Dat stelt de versie van het pakket in, waar het ook in de build voorkomt. Beheert de pom het al op een lagere letterlijke versie, dan wordt die versie ter plekke verhoogd. De aanvraag wordt geweigerd als:
- de pom het pakket als eigen afhankelijkheid declareert (
already_declared). Fix-PR openen verhoogt het daar; - de beheerde versie uit een property wordt gelezen, zoals
${jackson.version}, die ook door andere vermeldingen kan worden gelezen (managed_by_property); - het al op de gefixte versie of hoger wordt beheerd (
already_managed).
Controleer of de afhankelijkheden die het pakket binnenhalen met de nieuwe versie werken, want een bibliotheek is meestal getest tegen de versie waar ze om vroeg. Verwijder de vermelding zodra ze zelf de gefixte versie vereisen.
Gradle
Voor een Gradle-build met een lockfile (gradle.lockfile) voegt de pull request een constraint toe aan het dependencies-blok van het buildbestand naast de lockfile (build.gradle.kts of build.gradle):
dependencies {
constraints {
implementation("org.yaml:snakeyaml:2.0") {
because("fremforge security fix: pins a transitive dependency to a version that fixes its open advisories")
}
}
}Hij verplaatst ook de regel van het pakket in gradle.lockfile, zodat lock en build overeenkomen en Gradle de lock in strikte modus accepteert. Net als bij Maven moet de pom van de gefixte versie dezelfde afhankelijkheden en parent declareren als de versie die ze vervangt. De aanvraag wordt geweigerd als:
- het buildbestand het pakket al noemt (
already_declared); - de build afhankelijkheden verifieert met
gradle/verification-metadata.xml(gradle_verification_metadata). De nieuwe versie heeft checksums in dat bestand nodig, die Gradle berekent; voer zelf./gradlew --write-verification-metadata sha256uit; - het pakket is gelockt in een configuratie die een
implementation-constraint niet bereikt, zoals het eigen classpath van een plugin (gradle_configuration_unsupported); - het buildbestand geen
dependencies {-blok op het hoogste niveau heeft (build_file_unsupported).
fremforge voert Gradle niet uit. Een Gradle-build van de pull request in je CI controleert de lock tegen de build en mislukt als ze niet overeenkomen.
Als er geen pull request wordt geopend
De aanvraag toont dan de reden. Via de API is reason gelijk aan transitive_<why> en licht detail het toe:
| Reden | Betekenis |
|---|---|
transitive_already_declared | Het pakket is een directe afhankelijkheid. Fix-PR openen verhoogt het waar het gedeclareerd is |
transitive_transitive_dependencies_changed | De gefixte versie declareert andere afhankelijkheden (voor Python ook een andere Requires-Python), dus de lock heeft een volledige resolve nodig |
transitive_override_conflict | Een andere kopie van het pakket, met een niet-getroffen versie, zou ook verplaatsen, of een override, resolution of constraint zet het al anders vast |
transitive_yarn_berry_checksum | Yarn 2 of later: de lock heeft een checksum nodig die alleen Yarn kan berekenen |
transitive_gradle_verification_metadata | Gradle: de build verifieert afhankelijkheden, en de nieuwe versie heeft checksums nodig die Gradle berekent |
transitive_gradle_configuration_unsupported | Gradle: het pakket is gelockt in een configuratie die een implementation-constraint niet bereikt |
transitive_build_file_unsupported | Gradle: het buildbestand heeft geen dependencies {-blok op het hoogste niveau |
transitive_lock_entry_unsupported | De lock-vermelding heeft een vorm die fremforge niet bewerkt, zoals een pnpm-peersuffix, meerdere gelockte versies of een uv-lock van een index die het niet bedient |
transitive_managed_by_property | Maven: de beheerde versie komt uit een property |
transitive_already_managed | Maven: al beheerd op de gefixte versie of hoger |
transitive_lock_version_unsupported | npm: lockfileVersion 1. pnpm: een andere lockfileversie dan 9.0 |
transitive_manifest_not_supported | Een lockfile die hierboven niet staat, zoals bun.lock, pdm.lock of Pipfile.lock |
transitive_target_metadata_missing | De dependency proxy gaf geen metadata voor de gefixte versie, bijvoorbeeld omdat een pakketbeleid haar weigert |
transitive_manifest_missing, transitive_lock_missing | Het manifest of de lockfile staat niet op het verwachte pad op de standaardbranch |
transitive_release_age_pending | De gefixte versie is jonger dan je releaseleeftijd; vraag het opnieuw aan na de datum die het detail noemt |
transitive_release_age_unknown | De dependency proxy gaf geen publicatietijd voor de gefixte versie |
transitive_branch_exists | De branch security-fix/<package>-<version> bestaat al |
transitive_repo_not_writable | De repository is gearchiveerd of een spiegel |
Voor een aanvraag via Fix-PR openen (elk ecosysteem behalve Maven) blijft de aanvraag op Geen PR: niets te wijzigen staan en noemt het detail de reden.
API
npm, pnpm, Yarn, Python en Gradle hebben niets nodig naast POST /orgs/{org}/dependency-updates/security-fixes/requests. Voor 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>"}'Het antwoord is 201 met de aanvraag en de uitkomst ervan: pr_opened met pr_number en pr_url, of rejected met reason en detail. Een weigering voordat er een aanvraag bestaat, is een probleem van het type security-fix-<reason>, bijvoorbeeld security-fix-not-transitive. De scope is dependency-updates:write. De aanvraag wordt daarna net als elke andere vermeld door GET …/security-fixes/requests.