Analyse des licences
L’analyse des licences répond à la question qu’ai-je le droit de livrer, en complément du que contient cette image du SBOM et du qu’est-ce qui est vulnérable de l’analyse des dépendances. fremforge exécute la détection de licences de Trivy sur chaque PR, classe la licence de chaque dépendance résolue dans une catégorie de gravité de politique SPDX, et affiche le résultat à côté de la PR et dans un tableau de bord dédié de l’organisation. Elle est activée par défaut pour chaque organisation : aucune étape d’activation.
Comment elle fonctionne
L’analyseur central de fremforge (le même qui exécute le SAST, l’analyse des dépendances et l’analyse des images) lit la licence de chaque dépendance résolue par vos fichiers de verrouillage, et exécute la détection de fichiers de licence de Trivy sur les fichiers LICENSE/COPYING propres à votre dépôt, à chaque PR. Elle tourne sur les runners propres à fremforge : rien n’est installé dans votre dépôt. Contrairement au SAST ou à l’analyse des dépendances, qui publient une liste limitée aux problèmes, l’analyse des licences publie un inventaire complet des licences : un résultat par (package × licence) sur l’ensemble des dépendances résolues. Le workflow envoie ensuite l’inventaire à fremforge par POST :
POST https://frem.sh/_app/api/v1/license-scan/findingsLe PAT du runner a besoin de la portée findings:write (créé par dépôt par le contrôleur de runners au moment de l’installation du workflow). Lors d’une ingestion réussie, fremforge :
- Stocke les résultats. Une ligne par résultat dans
license_scan_findings. Chaque analyse remplace intégralement l’analyse précédente pour le même(tenant, repo, pr_number): une PR qui retire une dépendance cesse d’afficher ses lignes obsolètes. Les analyses de push (pr_numbernul) ont leur propre catégorie afin de ne pas remplacer les analyses de PR. - Publie un commentaire sur la PR qui résume les résultats (gravité, licence, paquet, manifeste), via l’API des tickets de Forgejo.
- Définit le statut de commit
fremforge/license-scansur le SHA de tête de la PR :successlorsque tout est seulement informatif ou dans la liste d’autorisation,failurelorsque le seuil de la politique est atteint.
Le commentaire et le statut côté Forgejo sont fournis au mieux : une panne de Forgejo est journalisée et rétrogradée en simple information, mais ne fait jamais échouer l’ingestion. Les résultats stockés font foi ; l’affichage sur la PR est une couche de confort.
D’où viennent les licences des dépendances
Rien n’est installé : les licences sont lues dans les métadonnées du registre pour la version exacte que chaque fichier de verrouillage épingle. Pour npm, PyPI, Go et Maven, ces métadonnées sont lues via le proxy de dépendances de votre organisation, sur la route interne du service d’analyse, ou via le cache d’analyse partagé de fremforge lorsque votre organisation n’a pas de proxy ; ce n’est que lorsqu’aucun des deux ne peut répondre qu’elles sont lues depuis le registre public via le proxy de sortie de fremforge. Les licences NuGet, crates.io, RubyGems et Packagist sont lues depuis le registre public via le proxy de sortie. Les anciens miroirs de paquets internes au cluster de fremforge sont retirés.
| Écosystème | Source | Ce qui est lu |
|---|---|---|
| npm (fichiers de verrouillage npm / pnpm / yarn / bun) | registry.npmjs.org, via le proxy de dépendances | le champ license du paquet |
Go (go.mod) | proxy.golang.org, via le proxy de dépendances ; les modules privés (frem.sh, votre GOPRIVATE déclaré) ne sont jamais demandés | le fichier LICENSE / COPYING à la racine du module, classé par la même détection de fichiers de licence que vos propres fichiers (Go n’a pas de métadonnées de licence) |
Maven (pom.xml, fichiers de verrouillage Gradle) | Maven Central, via le proxy de dépendances | les <licenses> du POM, en suivant les relocalisations et les POM parents |
NuGet (packages.lock.json) | nuget.org, via le proxy de sortie | l’expression de licence du paquet, ou une URL de licence reconnaissable |
PyPI (requirements.txt, Poetry, uv) | pypi.org, via le proxy de dépendances | les métadonnées JSON de la release (license_expression, un license court, les classificateurs de licence) |
| crates.io, RubyGems, Packagist | le registre public, via le proxy de sortie | le champ de licence par version du registre |
Les noms de licence en texte libre (GPL v3, GNU AFFERO GPL 3.0, Apache License, Version 2.0, …) sont convertis en identifiants SPDX. Tout ce qui ne peut pas être lu avec certitude est signalé comme unknown et présenté pour examen, jamais supposé permissif.
Quand une source est indisponible
Si un miroir ou un registre ne répond pas (erreur réseau, délai dépassé, 5xx), les paquets concernés ne sont ni considérés comme propres ni autorisés à bloquer : le statut fremforge/license-scan conserve ce que le reste de l’analyse a produit et ajoute lookup-unavailable for N package(s), re-scan queued. fremforge relance automatiquement l’analyse toutes les quelques minutes jusqu’à ce que la source réponde, et la mention disparaît.
Catégories de gravité
L’analyse des licences utilise un vocabulaire de gravité à 7 catégories (et non les 4 niveaux CRITICAL/HIGH/… de l’analyse des CVE), qui suit la classification de politique SPDX de Trivy, de la moins contraignante à la plus contraignante :
| Catégorie | Signification | Exemples de licences |
|---|---|---|
unknown | La licence n’a pas pu être déterminée (elle reste présentée pour examen) | NOASSERTION |
unencumbered | Équivalent au domaine public | CC0-1.0, Unlicense |
permissive | Utilisation libre, obligations minimales | — |
notice | Permissive, avec obligations d’attribution / de mention | MIT, BSD-3-Clause, Apache-2.0, ISC |
reciprocal | Copyleft faible : les modifications de la bibliothèque elle-même sont partagées | LGPL-2.1, LGPL-3.0, MPL-2.0, EPL-2.0, CDDL-1.0, GPL-2.0 WITH Classpath-exception-2.0 |
restricted | Copyleft fort | GPL-2.0, GPL-3.0, CC-BY-SA-4.0 |
forbidden | Copyleft réseau / source disponible | AGPL-3.0, SSPL-1.0, BUSL-1.1, CC-BY-NC-4.0 |
La LGPL est reciprocal dans fremforge (la table de Trivy la place dans restricted) : un programme peut lier une bibliothèque LGPL sans devenir lui-même LGPL ; c’est donc un copyleft faible, qui ne bloque pas au seuil par défaut.
Contrôles de politique
Configurez sur la page Politique de licences, via l’API (PUT /orgs/<org>/code-security/licenses/settings, pull requests uniquement) ou en tant que code avec le module Terraform et OpenTofu :
- Enabled : lorsqu’elle est désactivée, l’ingestion renvoie
202 {accepted: 0, status: 'disabled'}(le workflow s’exécute toujours, les résultats sont simplement abandonnés). Activée par défaut. - Gravité de blocage : le seuil à partir duquel un résultat bloque la fusion. Par défaut
restricted(depuis le 2026-09-29 ; c’étaitforbidden, qui laissait passer les dépendances GPL à titre informatif) : le copyleft fort et au-delà bloquent, le copyleft faible reste informatif. Une licence qui ne figure sur aucune des listes ci-dessous est jugée selon ce seuil. Combinez avec une protection de branche exigeant la vérificationfremforge/license-scanpour bloquer réellement la fusion. - Politique de licences : des listes explicites et deux règles, décrites dans la section suivante.
Les résultats forbidden (→ critique) ou restricted (→ élevé), ou refusés par la liste de refus, qui sont nouvellement introduits et non ignorés par une analyse déclenchent aussi le hook d’alerte de sécurité en temps réel ; les catégories inférieures sont seulement informatives.
Politique de licences
Chaque résultat est décidé par opérande SPDX, avec la même lecture que la politique de paquets : dans MIT OR GPL-3.0-only, l’utilisateur peut choisir, si bien que l’opérande le moins restrictif décide ; dans Apache-2.0 AND GPL-2.0-only, les deux s’appliquent, si bien que l’opérande le plus restrictif décide. Les parenthèses imbriquées, les exceptions WITH (GPL-2.0-only WITH Classpath-exception-2.0) et les identifiants LicenseRef-… sont lus comme SPDX les définit.
| Paramètre | Champ d’API | Par défaut | Effet |
|---|---|---|---|
| Liste d’autorisation | allowlisted_license_ids | vide | Un opérande qui y figure est autorisé : le résultat est ignoré à l’ingestion (acteur tenant_allowlist), quelle que soit sa catégorie. |
| Liste de refus | denied_license_ids | vide | Un opérande qui y figure bloque, quelle que soit sa catégorie. Le refus l’emporte sur l’autorisation pour un même identifiant. |
| Liste d’avertissement | warned_license_ids | vide | Un opérande qui y figure est informatif : signalé, jamais bloquant, quelle que soit sa catégorie. |
| Licences inconnues | unknown_action | threshold | Une licence sans identifiant SPDX, NOASSERTION, NONE, UNKNOWN, une chaîne qui n’est pas une expression SPDX, ou un identifiant que fremforge ne sait pas classer (comme un LicenseRef-… non listé) : threshold la juge comme la catégorie unknown selon la gravité de blocage ; allow l’ignore, warn la signale, deny la bloque. |
| Plusieurs licences | multiple_mode | all | Un composant qui déclare plusieurs licences (des résultats distincts, pas une seule expression) : all juge chaque licence séparément, si bien que n’importe laquelle peut bloquer ; any laisse la moins restrictive décider pour toutes. |
Les entrées des listes sont des identifiants SPDX (MIT, LicenseRef-acme) ou un identifiant avec son exception (GPL-2.0-only WITH Classpath-exception-2.0), comparés sans tenir compte de la casse ; GPL-2.0+ correspond à GPL-2.0-or-later. Une entrée pour la licence de base la couvre avec n’importe quelle exception ; une entrée pour la paire ne couvre que la paire. Jusqu’à 100 entrées par liste. Un PUT remplace les paramètres : un champ omis prend sa valeur par défaut.
Les valeurs par défaut ne changent rien. Avec des listes de refus et d’avertissement vides, unknown_action: threshold et multiple_mode: all, chaque résultat est décidé exactement comme la gravité de blocage et la liste d’autorisation le décidaient avant l’existence de cette politique. La seule différence est la liste d’autorisation elle-même, qui porte désormais sur les opérandes : MIT OR GPL-3.0-only passe une liste d’autorisation qui contient MIT.
Pourquoi un résultat bloque
Sur une pull request, seuls les résultats introduits par la pull request bloquent : un paquet et une licence déjà présents sur la branche ne bloquent pas.
Chaque résultat porte la décision sous policy dans l’API des résultats (GET /orgs/<org>/code-security/licenses/findings), et le tableau de bord l’affiche dans la colonne Décision de la politique :
{
"license_id": "MIT OR GPL-3.0-only",
"policy": {
"action": "allow",
"rule": "allow_list",
"decided_by": ["MIT"],
"operands": [
{ "operand": "MIT", "action": "allow", "rule": "allow_list", "matched": "MIT" },
{ "operand": "GPL-3.0-only", "action": "block", "rule": "deny_list", "matched": "GPL-3.0-only" }
],
"why": "allowed: MIT is on the allow list (OR takes the least restrictive operand)"
}
}action vaut allow, warn ou block ; rule vaut allow_list, deny_list, warn_list, threshold, below_threshold, unknown, multiple_any ou exception. Le commentaire de PR liste chaque résultat bloquant avec sa raison, et le statut fremforge/license-scan nomme les règles (2 finding(s) blocked by the licence policy (1 denied, 1 at or above restricted)).
Pour essayer une politique avant qu’une PR ne le fasse, POST /orgs/<org>/code-security/licenses/evaluate décide d’une liste de licences sans rien écrire :
curl -sS -X POST -H "Authorization: Bearer $FREMFORGE_TOKEN" -H 'Content-Type: application/json' \
-d '{"licenses":[{"license_id":"MIT OR GPL-3.0-only"},{"license_id":"Apache-2.0 AND GPL-2.0-only"}]}' \
https://frem.sh/_app/api/v1/orgs/<org>/code-security/licenses/evaluateDérogations par dépôt
Un dépôt peut remplacer n’importe lequel des cinq champs de politique : PUT /orgs/<org>/repos/<repo>/code-security/licenses/settings avec les champs à définir. Un champ défini remplace la valeur de l’organisation pour ce dépôt (une liste remplace la liste de l’organisation ; un tableau vide est une valeur) ; un champ absent ou null l’hérite. GET sur le même chemin affiche le remplacement et la politique effective, DELETE le supprime. Dans la console, les remplacements se trouvent sous Remplacements par dépôt dans Sécurité › Politiques › Politique de licences.
Exceptions de licence pour un composant
Pour accepter une licence pour un composant, par exemple un outil GPL validé par votre service juridique, demandez une exception (POST /orgs/<org>/exceptions avec kind: package, ou via la console) avec la portée component, la règle licence, le purl du composant sans version (pkg:npm/left-pad, pkg:pypi/requests, pkg:maven/org.example:lib) et l’identifiant de licence comme sélecteur (laissez le sélecteur vide pour n’importe quelle licence de ce composant). C’est la même exception que pour les politiques de paquets : une justification, une expiration obligatoire d’au plus un an, et l’approbation d’un propriétaire ou d’un approbateur désigné qui n’est pas le demandeur. Une fois approuvée (POST /orgs/<org>/exceptions/<id>/approve, par quelqu’un d’autre que le demandeur), l’analyse suivante de n’importe quel dépôt ignore les résultats correspondants (acteur licence-exception:<id>, dismissal_approved_by l’approbateur, raison avec la justification), jusqu’à ce que l’exception expire (le résultat se rouvre ce jour-là) ou soit révoquée (l’analyse suivante le rouvre).
Ignorer des résultats
Un administrateur de l’organisation peut ignorer des résultats individuels (avec une raison obligatoire, écrite dans le journal d’audit) depuis l’onglet Licences. Les dérogations manuelles sont reportées lors de la réanalyse qui remplace les résultats à l’ingestion, selon la clé (licence ID, package, manifest path) : réanalyser une PR ne fait pas réapparaître un résultat qu’un administrateur a déjà accepté. Une dérogation peut porter une date d’expiration facultative, après laquelle le résultat se rouvre de lui-même ; voir quand un résultat se ferme. Les dérogations par la politique de licences (liste d’autorisation, allow pour les inconnues, multiple_mode: any, une exception) ne sont pas reportées ; elles sont recalculées à partir des paramètres à chaque analyse, si bien qu’un changement de politique prend effet à l’analyse suivante.
Où les résultats apparaissent
Sécurité › Résultats › Licences liste les résultats actifs et les dérogations récentes. Chaque résultat affiche la catégorie de gravité, l’identifiant de licence SPDX, le ecosystem:package, le chemin du manifeste et la décision de la politique avec sa raison. Le commentaire de PR renvoie vers ce tableau de bord.
Conservation
Les résultats suivent la politique de conservation de l’audit propre à chaque tenant, comme les autres analyseurs de sécurité du code. Les résultats des PR fermées sont supprimés par la tâche quotidienne /jobs/license-scan-closed-pr-cleanup, car une PR fermée n’est jamais réanalysée.
Voir aussi
- Recommandations OSV : l’analyse des CVE des dépendances (le complément vulnérabilités de cette vue licences).
- SBOM : l’inventaire des paquets auquel les licences se rattachent.
- Analyse des images : l’analyseur voisin qui partage la même exécution centrale et le même modèle d’ingestion
findings:write. - Les fonctionnalités de sécurité en un coup d’œil : la matrice complète des contrôles.