Politiques de paquets
Une politique de paquets décide, fichier par fichier, si un paquet du registre de votre organisation peut être téléchargé. Elle couvre les types de registre qu’héberge fremforge : npm, Maven, PyPI, NuGet, Cargo, Go, Conda, CRAN, Debian, RPM, Helm et paquets génériques, ainsi que les images de conteneurs (voir Images de conteneurs). Chaque organisation a une seule politique, avec des paramètres facultatifs par type de paquet.
Ce que vérifie une politique
Lorsqu’un fichier est publié, fremforge le décompresse, liste les composants qu’il embarque et lit la licence déclarée par le paquet lui-même (voir SBOM). Chaque composant est comparé aux alertes OSV, puis comparé de nouveau chaque jour. La politique est évaluée par rapport à ces résultats et le verdict est stocké pour le fichier : allow, warn ou block. Une modification de la politique, d’une liste ou d’une exception réévalue immédiatement tous les fichiers, sans nouvelle analyse.
Au téléchargement, c’est le verdict stocké qui décide. Cela vaut pour les
téléchargements via l’API du registre (/api/packages/…, qu’utilisent tous les
gestionnaires de paquets) et depuis l’interface web. Les métadonnées de paquet,
comme un packument npm ou un index simple PyPI, ne sont jamais filtrées. Une
fois l’application activée, un résolveur qui demande une version bloquée reçoit
403 pour ce fichier ; il n’est pas redirigé vers une autre version.
Vulnérabilités
| Paramètre | Signification |
|---|---|
vuln_min_severity | Bloquer un fichier ayant une alerte ouverte de cette gravité ou plus : low, medium, high ou critical. |
vuln_min_cvss | Bloquer à partir de ce score de base CVSS (0–10). Le score est calculé à partir du vecteur CVSS de l’alerte. |
vuln_require_fix_available | Ne compter que les alertes qui ont une version corrigée. |
vuln_min_epss | Ne compter que les alertes dont la probabilité EPSS atteint au moins cette valeur (0–1). Une alerte sans score EPSS ne compte pas. |
La règle de vulnérabilité n’est active que si un seuil de gravité ou de CVSS est défini. Si les deux le sont, une alerte qui atteint l’un ou l’autre compte.
Licences
Les licences sont des identifiants SPDX, vérifiés à la fois par rapport à la licence déclarée et aux licences détectées dans les composants embarqués.
Les listes de licences se définissent sur la page
Politique de licences, un seul ensemble de
règles pour les pull requests et les téléchargements. La page Politiques de
paquets y renvoie. Les champs ci-dessous constituent le volet téléchargement de
cette politique, et PUT /orgs/{org}/package-policy les accepte toujours.
| Paramètre | Signification |
|---|---|
licence_allow | S’il n’est pas vide, seules ces licences sont autorisées. |
licence_deny | Ces licences bloquent. |
licence_warn | Ces licences avertissent. |
licence_unknown_action | Que faire lorsqu’une licence n’est pas une expression SPDX valide ou est absente : allow, warn ou block. |
licence_multiple_mode | Pour une expression à plusieurs licences (MIT OR GPL-3.0-only) : any_allowed passe si l’une d’elles est autorisée, all_allowed exige qu’elles le soient toutes. |
Les expressions sont évaluées opérande par opérande. OR passe lorsqu’un
opérande est autorisé, AND exige chaque opérande, et X WITH exception est
évalué comme X.
Listes d’autorisation et de refus
Une règle de liste nomme un type de paquet (ou n’importe quel type), un motif de
nom (jokers * et ?, par exemple @acme/* ou
org.apache.logging.log4j:log4j-core) et une plage de versions facultative en
syntaxe vers:, par exemple
vers:maven/<2.17.1. Les plages suivent les règles de version propres à chaque
écosystème pour npm, Cargo et Go (semver), PyPI (PEP 440), Maven et NuGet.
match_target: packagecorrespond au paquet que vous avez publié.match_target: componentcorrespond à un composant embarqué dans celui-ci.
Une règle deny bloque. Une règle allow sur un paquet le fait passer outre les règles de vulnérabilité, de licence et d’âge, et une règle allow sur un composant dispense ce composant des règles de vulnérabilité et de licence. Les règles allow ne prennent jamais le pas sur un malware connu ni sur une règle deny. Une règle peut porter une date d’expiration.
Âge minimal
min_age_hours retient les fichiers publiés depuis moins de ce nombre d’heures,
éventuellement pour les seuls types de age_types. L’âge est évalué au moment
du téléchargement : un fichier devient donc téléchargeable dès qu’il est assez
ancien. Enregistrer l’âge de version de
l’organisation le règle sur ce nombre de jours (0 le désactive) ; un type qui
a son propre âge minimal dans type_overrides le conserve.
Une version qui corrige une vulnérabilité connue comme exploitée (une CVE listée par l’EU Vulnerability Database de l’ENISA ou le catalogue KEV de la CISA) n’est pas retenue : le correctif d’une vulnérabilité exploitée dans la nature ne doit pas attendre. La règle d’âge apparaît tout de même dans la décision, marquée comme levée avec la CVE pour laquelle elle l’a été, de sorte que la dispense est visible dans le verdict, le journal d’audit et le SIEM. Le correctif d’une CVE dont l’exploitation n’est pas connue attend comme d’habitude.
Toujours actif
Deux vérifications s’exécutent quoi que dise la politique, même en mode Désactivé :
- Malware connu. Un fichier qui embarque un paquet listé dans une alerte OSV
MAL-*est refusé avec403, dans tous les modes, Observation et Désactivé compris. Pour laisser passer un fichier, il faut une exception approuvée pour la règlemalware. Voir paquets malveillants. - Conflit de nom. Une alerte qui nomme le nom et la version de votre propre paquet, généralement parce qu’un paquet public porte le même nom, donne un avertissement. Elle ne bloque jamais.
Images de conteneurs
Une image de conteneur est évaluée par digest de manifeste, selon les mêmes règles, avec les données propres à l’image :
- Les vulnérabilités sont les résultats CVE de
l’analyse des images de conteneurs pour ce
digest (ou son tag), avec leur gravité et leur score CVSS. Les résultats
d’image ne portent pas de score EPSS ; une politique avec
vuln_min_epssdéfini ne les compte donc pas. Un résultat écarté ne compte pas. - Le malware est un résultat paquet malveillant ouvert sur ce digest.
- Les licences sont celles que l’analyse d’image signale pour les paquets de l’image. Un paquet sans licence compte comme inconnu.
- Les listes d’autorisation et de refus désignent l’image comme type
container(son nom et son tag) et les paquets qu’elle contient comme composants. - L’âge minimal se compte à partir du push du tag, si
containerfigure dansage_types(ou siage_typesest vide).
L’image est vérifiée lorsqu’elle est tirée (docker pull, ou tout client qui
lit /v2/…/manifests/…), pour chaque manifeste servi par le pull, y compris
chaque plateforme d’une image multiplateforme. Les images sont uniquement en
mode observation : un pull qui enfreint la politique est enregistré sous
Aurait bloqué et n’est jamais refusé, quel que soit le mode choisi. Les
résultats d’image sont réévalués au moins toutes les 12 heures, et
immédiatement lorsque vous modifiez la politique, une liste ou une exception.
Ce mécanisme est distinct des blocages de pull d’image existants, qui restent
inchangés : le paramètre block_pull de
l’analyse des images de conteneurs et le
blocage des paquets malveillants refusent déjà des pulls aujourd’hui.
Signatures
signature_requirement vérifie les signatures des paquets npm que votre
organisation récupère depuis npmjs.org via le proxy de dépendances de fremforge,
dès que le proxy de dépendances est disponible pour votre organisation, avant
que l’archive ne soit servie :
- la signature du registre que npmjs.org appose sur chaque version
(
name@version:integrity), vérifiée avec les clés de registre publiées par npm ; - la provenance de build, lorsque la version en a une : une attestation SLSA signée par Sigstore, vérifiée hors ligne avec la racine de confiance publique de Sigstore et liée au SHA-512 de l’archive.
| Paramètre | Signification |
|---|---|
off | Non vérifié. La valeur par défaut. |
if_present | Une signature ou une provenance présente doit être valide. Une version qui n’en a pas passe. |
require | Comme if_present, et une version doit porter une signature valide (ou, si des signataires sont listés, une provenance de confiance). |
signature_identities liste les identités de build auxquelles vous faites
confiance, chacune étant un issuer OIDC et un subject_regex auquel le sujet
complet du certificat doit correspondre. Dès qu’une identité est listée, seule
une provenance issue de l’une d’elles compte ; une signature du registre seule
ne suffit pas, car elle prouve ce que npmjs.org a servi, et non qui l’a
construit. Pour des paquets construits par GitHub Actions dans une seule
organisation :
"signature_requirement": "require",
"signature_identities": [
{ "issuer": "https://token.actions.githubusercontent.com",
"subject_regex": "https://github\\.com/sigstore/.+" }
]Comme toute règle, elle suit le mode de la politique. Une version en
échec est enregistrée avec la règle signature et une raison telle que
no provenance from a trusted identity ou the registry signature does not verify. Une exception approuvée avec la règle signature
l’excuse.
Ce qui n’est pas vérifié : les paquets publiés sur frem.sh (le registre ne
conserve pour eux ni signature npm ni provenance, la règle ne s’y applique donc
pas du tout), ainsi que tous les autres types de paquets. Les signatures .asc
de Maven et les signatures NuGet ne sont pas vérifiées.
Dégradation de provenance
provenance_downgrade repère une version dont la provenance de build est plus
faible que celle de la version précédente. C’est la trace typique d’une
version publiée depuis un ordinateur portable, ou avec un jeton volé, alors que
les versions antérieures provenaient de la CI. Elle s’appuie sur les mêmes faits
vérifiés que les signatures ; elle s’applique donc aux paquets
npm récupérés depuis npmjs.org via le proxy de dépendances.
La version précédente est la version inférieure la plus élevée publiée plus tôt. Ainsi, un rétroportage sur une branche plus ancienne, comme un correctif de sécurité 4.5.x publié après 5.4.x, est comparé à la version précédente de cette branche, et non à la plus récente. Les branches anciennes sont souvent encore construites sans provenance.
Une version constitue une dégradation lorsque la version précédente avait une provenance de build, et que cette version :
- n’a aucune provenance ;
- a une provenance qui ne se vérifie pas ; ou
- a été construite par un autre signataire : un autre émetteur OIDC, un autre dépôt source ou un autre workflow de build, ou un runner auto-hébergé alors que la version précédente s’exécutait sur un runner hébergé. Ces éléments sont lus dans le certificat Sigstore vérifié. Un nouveau tag du même workflow correspond au même signataire.
La première version d’un paquet, et une version dont la précédente n’avait pas
de provenance, passent toujours. Cette règle compare des versions. Pour exiger
une provenance sur chaque version, utilisez signature_requirement.
| Paramètre | Signification |
|---|---|
off | Non vérifié. La valeur par défaut. |
warn | Une dégradation est enregistrée comme avertissement ; la version est servie. |
block | Une dégradation bloque la version. Comme pour toute règle, le mode de la politique décide : enforce refuse le téléchargement, shadow se contente de l’enregistrer. |
"provenance_downgrade": "block"Dans la console, elle se trouve sous Politiques › Politiques de paquets ›
Signatures. Avec Terraform, c’est l’argument provenance_downgrade de
fremforge_package_signature_policy. Elle peut aussi être définie pour un seul
type de paquet dans type_overrides.
Une version en échec est enregistrée avec la règle provenance_downgrade et
une raison qui nomme la version précédente, par exemple
2.4.1 has no build provenance, but the previous version 2.4.0 had ou
... built by a different signer than the previous version 2.4.0: source repository https://github.com/acme/lib -> https://github.com/fork/lib. Une
exception approuvée avec la règle signature et le sélecteur
provenance_downgrade n’excuse que cette règle. Une exception avec la règle
signature sans sélecteur excuse les deux règles de signature.
Là où elle ne s’applique pas encore :
| Écosystème | Pourquoi |
|---|---|
| PyPI | PyPI publie des attestations PEP 740, mais fremforge ne les vérifie pas encore. |
| Maven Central, RubyGems | La signature Sigstore est facultative en amont et n’est pas vérifiée par le proxy. |
| Cargo, Go, NuGet, Composer, Helm, conda, Debian, RPM, modules Terraform | Pas de provenance de build par version en amont. Les signatures de dépôt ou de base de sommes de contrôle prouvent l’intégrité, pas qui a construit une version. |
| Paquets publiés sur frem.sh | Aucune provenance n’est enregistrée pour eux. |
La vérification du fichier de verrouillage par le proxy et son filtrage des métadonnées de paquet ne l’appliquent pas non plus ; elle est évaluée lors du téléchargement d’une archive.
Paramètres par type
type_overrides définit différemment n’importe laquelle des règles ci-dessus
pour un type de paquet, par exemple une liste de licences plus stricte pour
npm. "enabled": false désactive la politique pour ce type, hormis la
vérification des malwares.
Modes
| Mode | Ce qui se passe |
|---|---|
off | Seuls les malwares connus et les conflits de nom sont vérifiés. |
shadow | Chaque règle est évaluée. Rien n’est refusé, et chaque téléchargement qui aurait été refusé est enregistré. C’est la valeur par défaut. |
enforce | Un téléchargement qui enfreint la politique est refusé avec 403. La réponse nomme les règles appliquées et renvoie vers la page de la politique. |
pending_action décide du sort d’un fichier dont l’évaluation n’est pas encore
terminée, par exemple un fichier publié il y a quelques secondes. allow (la
valeur par défaut) le laisse passer. refuse répond 503 avec Retry-After,
en mode enforce uniquement.
Exceptions
Une exception laisse passer un paquet outre une règle pendant une durée limitée. Elle nécessite une seconde personne : le demandeur ne peut pas approuver sa propre demande, et l’approbateur doit être propriétaire de l’organisation ou figurer sur la liste des approbateurs de l’organisation.
Une exception comporte :
- une portée : un fichier, une plage de versions d’un paquet, toutes les versions d’un paquet, ou un composant embarqué (par purl) ;
- une règle :
vuln,licence,age,deny_list,name_clashoumalware, éventuellement restreinte à un identifiant d’alerte ou à une licence SPDX ; - une justification ;
- une date d’expiration à 366 jours au plus. Elle est obligatoire.
Une exception est demandée, puis approuvée ou rejetée. Une exception approuvée
peut être révoquée à tout moment, et elle est révoquée automatiquement à son
expiration. Chaque étape est écrite dans le
journal d’audit (package_policy.exception.*).
Les exceptions de politique de paquets partagent une même file, une même API et les mêmes paramètres avec les exceptions sur les résultats : voir Exceptions, qui couvre aussi l’équipe d’approbateurs, la durée maximale et la révocation automatique.
La vue « Aurait bloqué »
Ouvrez les pages d’administration de votre organisation sous Sécurité › Politiques › Politiques de paquets. La page propose quatre vues :
- Politique définit le mode et les règles, et affiche les verdicts actuels de vos fichiers publiés. Pour voir ce que de nouveaux paramètres bloqueraient avant de les enregistrer, utilisez la simulation de l’API (ci-dessous).
- Listes d’autorisation et de refus contient les règles de liste.
- Exceptions affiche les demandes, la file d’approbation et l’historique. Vos propres demandes n’affichent pas de bouton Approuver.
- Aurait bloqué liste les téléchargements que la politique a refusés, ou aurait refusés en mode observation, au cours des 14 derniers jours : une ligne par fichier, règle, utilisateur et jour, avec le nombre de téléchargements. Ce sont les éléments à examiner avant de passer une organisation en mode enforce.
Les téléchargements refusés ou enregistrés de cette façon figurent aussi dans
le journal d’accès au registre avec la décision
would_block ou blocked.
Ce qui a changé au démarrage de l’application
Le 2026-10-11 :
- Le mode Appliquer refuse. Auparavant, Appliquer enregistrait les mêmes résultats qu’Observation. Si votre politique était déjà réglée sur Appliquer, les téléchargements qui l’enfreignent sont désormais refusés. Consultez d’abord Aurait bloqué, ou passez en Observation d’ici là.
- Les malwares connus sont refusés pour tous. Auparavant, ils étaient seulement enregistrés.
- Vos résultats sont affichés. Les verdicts de fichiers, Aurait bloqué, les résultats de paquets et les API d’évaluation, de simulation, de décisions et de résultats répondent désormais pour votre organisation. Depuis lors, les résultats de paquets et les décisions de politique produisent aussi des événements du journal d’audit (et donc des événements SIEM et webhook), des e-mails d’alerte de sécurité, des lignes du résumé hebdomadaire et des lignes dans l’onglet Malware, pour les changements à compter de ce jour. Aurait bloqué liste aussi les téléchargements enregistrés au cours des 14 jours précédents.
- Les images de conteneurs ne changent pas : toujours en observation uniquement, voir Images de conteneurs.
API
Utilisez un jeton doté de policy:read ou policy:write (findings:read pour
les résultats de paquets). URL de base : https://frem.sh/_app/api/v1. Voir la
référence de l’API pour la forme complète des requêtes et
des réponses.
| Méthode et chemin | Ce qu’il fait |
|---|---|
GET /orgs/{org}/package-policy | La politique, avec sa version. |
PUT /orgs/{org}/package-policy | Modifier la politique. Envoyez la version que vous avez lue ; une version périmée reçoit 409 policy-version-conflict. |
GET, POST /orgs/{org}/package-policy/rules | Lister ou ajouter des règles d’autorisation et de refus. Une règle nécessite list, name_glob et reason. |
DELETE /orgs/{org}/package-policy/rules/{id} | Supprimer une règle. |
GET, PUT /orgs/{org}/package-policy/approvers | Les approbateurs nommés, en plus des propriétaires. PUT prend {"logins": [...]}. |
GET, POST /orgs/{org}/package-policy/exceptions | Lister les exceptions (filtrer avec status) ou en demander une. |
GET /orgs/{org}/package-policy/exceptions/{id} | Une exception. |
POST /orgs/{org}/package-policy/exceptions/{id}/approve | Approuver. Refusé avec self-approve pour le demandeur et not-an-approver pour toute autre personne qui n’y est pas autorisée. |
POST /orgs/{org}/package-policy/exceptions/{id}/reject | Rejeter. |
DELETE /orgs/{org}/package-policy/exceptions/{id} | Révoquer. |
GET /orgs/{org}/package-policy/evaluation?type=&name=&version= | Le verdict de chaque fichier d’une version de paquet, avec ses raisons, ses résultats et ses licences. |
POST /orgs/{org}/package-policy/evaluate | Simulation. Avec {"policy": {...}}, elle indique ce que chaque fichier actuel obtiendrait avec ces paramètres, sans rien écrire. Avec {"components": [{"purl": "...", "licence": "..."}]}, elle évalue une liste de composants, par exemple depuis une étape de CI avant publication. |
GET /orgs/{org}/package-policy/decisions | Les données Aurait bloqué : days (1–90, 14 par défaut), decision (blocked, would_block ou pending_refused). |
GET /orgs/{org}/findings/packages | Résultats d’alertes sur les paquets du registre. Paginez avec cursor (le next_cursor de la page précédente) et limit (jusqu’à 500). |
Une politique en mode observation qui bloque les alertes high et critical et l’AGPL, et retient les nouvelles versions npm pendant 24 heures :
TOKEN=ffp_...
ORG=acme
V=$(curl -sS -H "Authorization: Bearer $TOKEN" \
"https://frem.sh/_app/api/v1/orgs/$ORG/package-policy" | jq .version)
curl -sS -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
"https://frem.sh/_app/api/v1/orgs/$ORG/package-policy" \
-d "{\"version\": $V, \"mode\": \"shadow\", \"vuln_min_severity\": \"high\",
\"licence_deny\": [\"AGPL-3.0-only\", \"AGPL-3.0-or-later\"],
\"min_age_hours\": 24, \"age_types\": [\"npm\"]}"Refuser log4j-core en dessous de 2.17.1 partout où il est embarqué :
curl -sS -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
"https://frem.sh/_app/api/v1/orgs/$ORG/package-policy/rules" \
-d '{"list": "deny", "pkg_type": "maven", "name_glob": "org.apache.logging.log4j:log4j-core",
"version_range": "vers:maven/<2.17.1", "match_target": "component",
"reason": "Log4Shell"}'Audit
Chaque modification figure dans le journal d’audit :
package_policy.updated (avec ce qui a changé), package_policy.rule.created
et .deleted, package_policy.approvers.updated, les événements d’exception,
et package_policy.verdict_changed lorsqu’un fichier passe d’allow vers warn ou
block. Un téléchargement refusé ou qui aurait été refusé est enregistré comme
package_policy.download_blocked ou package_policy.download_would_block, une
fois par fichier, règle, utilisateur et jour.
Voir aussi
- Paquets malveillants
- Analyse des images de conteneurs, qui dispose de son propre blocage de pull pour les images de conteneurs
- SBOM
- Journal d’accès au registre