Dépendances transitives
Un paquet vulnérable n’est souvent pas un paquet que vous avez déclaré. Une autre dépendance l’importe, et c’est elle qui choisit sa version. Il est impossible de le mettre à jour là où il est déclaré, puisqu’il n’est déclaré nulle part dans votre dépôt. Les correctifs de sécurité épinglent donc un tel paquet depuis la racine de votre projet, avec le mécanisme que chaque écosystème prévoit à cet effet.
| Écosystème | Comment le paquet est épinglé | Comment le demander |
|---|---|---|
npm (package-lock.json) | Une entrée overrides dans package.json, et les entrées du paquet dans package-lock.json passées à la version corrigée | Ouvrir une demande de correctif sur le résultat, comme pour tout autre. Lorsque Renovate hébergé ne trouve rien à mettre à jour, fremforge l’épingle en une dizaine de minutes |
pnpm (pnpm-lock.yaml) | Une entrée overrides dans pnpm-workspace.yaml (ou pnpm.overrides dans package.json pour pnpm 9 et antérieur), et le verrouillage mis en concordance | Comme pour npm |
Yarn 1 (yarn.lock) | Une entrée resolutions dans package.json, et l’entrée de yarn.lock déplacée | Comme pour npm. Yarn 2 et versions ultérieures sont refusés |
PyPI (uv.lock, poetry.lock) | L’entrée de verrouillage du paquet passée à la version corrigée, sans épinglage dans pyproject.toml | Comme pour npm |
Maven (pom.xml) | Une entrée <dependencyManagement> dans le pom.xml dont provient le résultat | Ouvrir une demande de correctif, comme pour npm |
| Maven (vu dans votre SBOM) | Une entrée <dependencyManagement> dans le pom.xml racine | Épingler comme dépendance transitive sur le résultat, ou POST /orgs/{org}/dependency-updates/security-fixes/transitive-requests |
Gradle (gradle.lockfile) | Une entrée constraints dans le fichier de build, et la ligne du fichier de verrouillage déplacée | Comme pour npm |
| Go | Rien de plus : go.mod liste chaque module utilisé par votre build, marqué // indirect, et le correctif relève cette ligne | Ouvrir une demande de correctif |
Il s’agit d’une pull request de correctif de sécurité ordinaire, et le même âge de version s’applique : une version corrigée plus récente que l’Âge de la version (jours) de votre organisation (3 par défaut) n’est pas encore proposée, sauf si elle corrige une vulnérabilité connue comme exploitée. Comme aucune exécution de Renovate ne vérifie un épinglage transitif, fremforge lit lui-même la date de publication, via votre proxy de dépendances : le champ time du registre npm, l’heure de téléversement des fichiers de la version corrigée sur PyPI, et pour Maven et Gradle l’heure que Maven Central indique pour le pom. Une version corrigée dont la date de publication ne peut pas être lue n’est pas proposée.
Elle a aussi la même branche (security-fix/<package>-<version>), la même étiquette (security-fix), les mêmes limites et le même paramètre de fusion que toutes les autres, et le même choix Quand les correctifs sont fusionnés s’applique. La pull request indique que le paquet est transitif et comment il a été épinglé.
npm
La pull request modifie deux fichiers et rien d’autre :
"overrides": {
"lodash": "4.18.0"
}Ceci est ajouté à package.json. Chaque entrée de package-lock.json du paquet à une version vulnérable passe à la version corrigée : la version et l’URL de l’archive changent, ainsi que l’empreinte d’intégrité lorsque l’entrée en avait une. L’URL conserve le registre de votre fichier de verrouillage.
fremforge n’exécute pas npm install, ni quoi que ce soit d’autre provenant de votre dépôt. Il écrit les entrées de verrouillage à partir des métadonnées du registre, lues via le proxy de dépendances de votre organisation, de sorte que vos politiques de paquets s’appliquent aussi à la version corrigée. Il ne les écrit que lorsque c’est tout ce qu’une résolution modifierait :
- La version corrigée déclare exactement les mêmes dépendances que la version qu’elle remplace. Sinon, la demande est refusée avec
transitive_dependencies_changed, car l’arbre changerait au-delà de ce paquet. - Chaque copie du paquet dans le verrouillage est à une version vulnérable. Un override déplace toutes les copies ; une copie à une autre version, non concernée, fait donc refuser la demande avec
override_conflict. package-lock.jsonest enlockfileVersion2 ou 3.
npm ci accepte le résultat. Supprimez l’override dès que les paquets qui importent le paquet exigent eux-mêmes la version corrigée.
pnpm
pnpm enregistre aussi les overrides dans le fichier de verrouillage, et refuse un verrouillage dont la section overrides: diffère de la configuration. La pull request modifie donc les deux :
- l’override, dans
pnpm-workspace.yaml(créé lorsque le dépôt n’en a pas). Il va plutôt danspnpm.overridesdepackage.jsonlorsquepackageManagerdésigne pnpm 9 ou antérieur, ou, en l’absence depackageManager, lorsque le projet y conserve ses overrides et n’a pas depnpm-workspace.yaml; pnpm-lock.yaml: sa sectionoverrides:, les entréespackagesetsnapshotsdu paquet avec l’intégrité issue du registre, et chaque référence à l’ancienne version.
pnpm install --frozen-lockfile accepte le résultat. Seul lockfileVersion: '9.0' est pris en charge ; un paquet verrouillé avec un suffixe de dépendance pair tel que foo@1.0.0(react@18.3.1) est refusé avec lock_entry_unsupported.
Yarn
Pour Yarn 1, la pull request ajoute une entrée resolutions à package.json et déplace l’entrée du paquet dans yarn.lock : sa version, son URL resolved (sur le registre de votre fichier de verrouillage, avec la somme de contrôle de l’archive) et son integrity. yarn install --frozen-lockfile accepte le résultat.
Yarn 2 et versions ultérieures (un yarn.lock avec __metadata) sont refusés avec yarn_berry_checksum. Leur verrouillage enregistre une somme de contrôle de l’archive que Yarn construit à partir du paquet, que seul Yarn peut calculer. Ajoutez vous-même une entrée resolutions et exécutez yarn install.
Les mêmes règles que pour npm s’appliquent à pnpm et Yarn : la version corrigée doit déclarer les mêmes dépendances, et chaque copie du paquet dans le verrouillage doit être à une version vulnérable.
Python
Pour uv.lock et poetry.lock, la pull request passe l’entrée de verrouillage du paquet à la version corrigée et n’ajoute rien à pyproject.toml. Un paquet que pyproject.toml déclare est refusé avec already_declared, car Ouvrir une demande de correctif le met à jour à cet endroit. La nouvelle entrée porte les fichiers de la version corrigée avec leurs empreintes, lus depuis le proxy de dépendances PyPI de votre organisation :
- uv : le
sdistet leswheels, avec l’URL, l’empreinte, la taille et l’heure de téléversement lorsque votre verrouillage les enregistre. Les URL conservent l’index de votre verrouillage : l’URL propre du proxy de dépendances si vous verrouillez à partir de lui, oufiles.pythonhosted.orgsi vous verrouillez à partir de PyPI. Un verrouillage issu de tout autre index est refusé aveclock_entry_unsupported. - Poetry : la version, la description,
python-versionsetfiles. Lecontent-hashde Poetry ne couvre quepyproject.toml; il ne change donc pas.
La règle est la même que pour npm, appliquée aux métadonnées de Python : la version corrigée doit avoir les mêmes Requires-Dist et Requires-Python que la version qu’elle remplace, lus dans les métadonnées de sa wheel. Sinon, la demande est refusée avec transitive_dependencies_changed. uv sync --locked et poetry install acceptent le résultat. Le paquet reste à la version corrigée jusqu’à ce qu’un uv lock ou poetry lock ultérieur refasse la résolution.
Maven
L’analyse des dépendances résout chaque pom.xml ; un paquet vulnérable que votre build importe est donc un résultat sur ce pom.xml. Ouvrir une demande de correctif fonctionne alors comme pour npm : lorsque Renovate hébergé ne trouve rien à mettre à jour, parce que le pom ne nomme pas le paquet, fremforge l’épingle dans ce pom.xml en une dizaine de minutes. Un paquet visible uniquement dans un SBOM que vous téléversez, par exemple produit par cyclonedx-maven-plugin (voir chemins de dépendance), affiche plutôt Épingler comme dépendance transitive dans la colonne Correctif, et est épinglé dans le pom.xml racine. Dans les deux cas, la pull request ajoute :
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.11</version>
</dependency>
</dependencies>
</dependencyManagement>Cela fixe la version du paquet partout où il apparaît dans le build. Si le pom le gère déjà à une version littérale inférieure, cette version est relevée sur place. La demande est refusée lorsque :
- le pom déclare le paquet comme l’une de ses propres dépendances (
already_declared). Ouvrir une demande de correctif le met à jour à cet endroit ; - la version gérée est lue depuis une propriété, comme
${jackson.version}, que d’autres entrées peuvent aussi lire (managed_by_property) ; - il est déjà géré à la version corrigée ou supérieure (
already_managed).
Vérifiez que les dépendances qui importent le paquet fonctionnent avec la nouvelle version, car une bibliothèque est généralement testée avec la version qu’elle a demandée. Supprimez l’entrée dès qu’elles exigent elles-mêmes la version corrigée.
Gradle
Pour un build Gradle avec un fichier de verrouillage (gradle.lockfile), la pull request ajoute une contrainte au bloc dependencies du fichier de build situé à côté du fichier de verrouillage (build.gradle.kts ou 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")
}
}
}Elle déplace aussi la ligne du paquet dans gradle.lockfile, de sorte que le verrouillage et le build concordent et que Gradle accepte le verrouillage en mode strict. Comme pour Maven, le pom de la version corrigée doit déclarer les mêmes dépendances et le même parent que celui qu’elle remplace. La demande est refusée lorsque :
- le fichier de build nomme déjà le paquet (
already_declared) ; - le build vérifie les dépendances avec
gradle/verification-metadata.xml(gradle_verification_metadata). La nouvelle version a besoin de sommes de contrôle dans ce fichier, que Gradle calcule ; exécutez vous-même./gradlew --write-verification-metadata sha256; - le paquet est verrouillé dans une configuration qu’une contrainte
implementationn’atteint pas, comme le classpath propre d’un plugin (gradle_configuration_unsupported) ; - le fichier de build n’a pas de bloc
dependencies {de premier niveau (build_file_unsupported).
fremforge n’exécute pas Gradle. Un build Gradle de la pull request dans votre CI vérifie le verrouillage par rapport au build et échoue s’ils divergent.
Quand aucune pull request n’est ouverte
La demande affiche alors la raison. Via l’API, reason vaut transitive_<why> et detail l’explique :
| Raison | Signification |
|---|---|
transitive_already_declared | Le paquet est une dépendance directe. Ouvrir une demande de correctif le met à jour là où il est déclaré |
transitive_transitive_dependencies_changed | La version corrigée déclare d’autres dépendances (pour Python, aussi un autre Requires-Python), si bien que le verrouillage nécessite une résolution complète |
transitive_override_conflict | Une autre copie du paquet, à une version non concernée, serait aussi déplacée, ou un override, une résolution ou une contrainte l’épingle déjà différemment |
transitive_yarn_berry_checksum | Yarn 2 ou ultérieur : son verrouillage nécessite une somme de contrôle que seul Yarn peut calculer |
transitive_gradle_verification_metadata | Gradle : le build vérifie les dépendances, et la nouvelle version a besoin de sommes de contrôle que Gradle calcule |
transitive_gradle_configuration_unsupported | Gradle : le paquet est verrouillé dans une configuration qu’une contrainte implementation n’atteint pas |
transitive_build_file_unsupported | Gradle : le fichier de build n’a pas de bloc dependencies { de premier niveau |
transitive_lock_entry_unsupported | L’entrée de verrouillage a une forme que fremforge ne modifie pas, comme un suffixe pair pnpm, plusieurs versions verrouillées, ou un verrouillage uv issu d’un index qu’il ne sert pas |
transitive_managed_by_property | Maven : la version gérée provient d’une propriété |
transitive_already_managed | Maven : déjà géré à la version corrigée ou supérieure |
transitive_lock_version_unsupported | npm : lockfileVersion 1. pnpm : une version de fichier de verrouillage autre que 9.0 |
transitive_manifest_not_supported | Un fichier de verrouillage absent de la liste ci-dessus, comme bun.lock, pdm.lock ou Pipfile.lock |
transitive_target_metadata_missing | Le proxy de dépendances n’a fourni aucune métadonnée pour la version corrigée, par exemple parce qu’une politique de paquets la refuse |
transitive_manifest_missing, transitive_lock_missing | Le manifeste ou le fichier de verrouillage ne se trouve pas au chemin attendu sur la branche par défaut |
transitive_release_age_pending | La version corrigée est plus récente que votre âge de version ; refaites la demande après la date indiquée dans le détail |
transitive_release_age_unknown | Le proxy de dépendances n’a fourni aucune date de publication pour la version corrigée |
transitive_branch_exists | La branche security-fix/<package>-<version> existe déjà |
transitive_repo_not_writable | Le dépôt est archivé ou est un miroir |
Pour une demande passée par Ouvrir une demande de correctif (tous les écosystèmes sauf Maven), la demande conserve Aucune PR : rien à modifier et le détail indique la raison.
API
npm, pnpm, Yarn, Python et Gradle n’ont besoin de rien d’autre que POST /orgs/{org}/dependency-updates/security-fixes/requests. Pour 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>"}'La réponse est 201 avec la demande et son issue : pr_opened avec pr_number et pr_url, ou rejected avec reason et detail. Un refus avant qu’une demande existe est un problème de type security-fix-<reason>, par exemple security-fix-not-transitive. La portée est dependency-updates:write. La demande est ensuite listée par GET …/security-fixes/requests comme toutes les autres.