Aller au contenu principal

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.

L’application est active depuis le 2026-10-11. Une politique en mode Appliquer refuse les téléchargements qui l’enfreignent. Une politique en mode Observation, le mode par défaut, se contente toujours de les enregistrer. Un fichier qui embarque un malware connu est refusé pour toutes les organisations, quel que soit le mode. Vos verdicts, la vue Aurait bloqué et les résultats de paquets vous sont affichés. Voir Ce qui a changé au démarrage de l’application.

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ètreSignification
vuln_min_severityBloquer un fichier ayant une alerte ouverte de cette gravité ou plus : low, medium, high ou critical.
vuln_min_cvssBloquer à partir de ce score de base CVSS (0–10). Le score est calculé à partir du vecteur CVSS de l’alerte.
vuln_require_fix_availableNe compter que les alertes qui ont une version corrigée.
vuln_min_epssNe 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ètreSignification
licence_allowS’il n’est pas vide, seules ces licences sont autorisées.
licence_denyCes licences bloquent.
licence_warnCes licences avertissent.
licence_unknown_actionQue faire lorsqu’une licence n’est pas une expression SPDX valide ou est absente : allow, warn ou block.
licence_multiple_modePour 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: package correspond au paquet que vous avez publié.
  • match_target: component correspond à 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é avec 403, dans tous les modes, Observation et Désactivé compris. Pour laisser passer un fichier, il faut une exception approuvée pour la règle malware. 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_epss dé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 container figure dans age_types (ou si age_types est 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ètreSignification
offNon vérifié. La valeur par défaut.
if_presentUne signature ou une provenance présente doit être valide. Une version qui n’en a pas passe.
requireComme 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ètreSignification
offNon vérifié. La valeur par défaut.
warnUne dégradation est enregistrée comme avertissement ; la version est servie.
blockUne 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èmePourquoi
PyPIPyPI publie des attestations PEP 740, mais fremforge ne les vérifie pas encore.
Maven Central, RubyGemsLa 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 TerraformPas 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.shAucune 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

ModeCe qui se passe
offSeuls les malwares connus et les conflits de nom sont vérifiés.
shadowChaque 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.
enforceUn 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_clash ou malware, é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 cheminCe qu’il fait
GET /orgs/{org}/package-policyLa politique, avec sa version.
PUT /orgs/{org}/package-policyModifier 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/rulesLister 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/approversLes approbateurs nommés, en plus des propriétaires. PUT prend {"logins": [...]}.
GET, POST /orgs/{org}/package-policy/exceptionsLister 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}/approveApprouver. 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}/rejectRejeter.
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/evaluateSimulation. 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/decisionsLes données Aurait bloqué : days (1–90, 14 par défaut), decision (blocked, would_block ou pending_refused).
GET /orgs/{org}/findings/packagesRé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