Aller au contenu

Workflow de revue CVE

Lorsque le pipeline de sécurité détecte des vulnérabilités dans un paquet, deux issues sont possibles : le rejet automatique (pour les CVE déclenchant une politique block) ou la revue humaine (pour les CVE déclenchant une politique review). Cette page explique le chemin de revue — pourquoi il existe, qui y participe, quelles informations orientent la décision, et ce qui se passe techniquement lorsqu'une décision est prise.

Le problème du « tout bloquer »

L'instinct de bloquer tout paquet présentant une CVE de sévérité élevée est compréhensible. Le problème est que les classifications de sévérité ont été conçues pour décrire l'impact dans le pire des cas d'une vulnérabilité prise isolément, pas son risque pratique dans un environnement spécifique.

Une CVE avec un CVSS de 9,8 dans une bibliothèque que votre application appelle pour du formatage de chaînes — mais uniquement avec des entrées que votre application ne fournit jamais sous la forme vulnérable — ne représente pas le même risque qu'un CVSS de 9,8 dans une bibliothèque qui parse des entrées réseau non fiables provenant d'Internet. Les deux obtiennent le même score. Une politique de blocage systématique les traite de manière identique.

Les conséquences pratiques d'un blocage automatique trop agressif sont :

  • Les faux positifs créent une pression de contournement. Lorsque des paquets légitimes sont bloqués trop souvent, les opérateurs cherchent des contournements. La réponse à « ce scanner bloque sans cesse nos déploiements » ne devrait pas être « désactiver le scanner » — mais c'est dans cette direction que pousse la friction.
  • Les risques métier acceptés ne peuvent pas être exprimés. Certaines CVE affectent des composants absents de votre déploiement (par exemple, une vulnérabilité dans un chemin de code Windows au sein d'une bibliothèque multiplateforme). Certains risques sont explicitement acceptés par le RSSI avec des mesures compensatoires. Un blocage binaire n'offre aucun mécanisme pour enregistrer ces décisions.
  • La continuité d'activité exige de la nuance. Un correctif de sécurité critique pour une vulnérabilité peut être livré dans un paquet contenant une autre CVE de sévérité moyenne non corrigée. Bloquer l'intégralité du paquet empêche le déploiement du correctif critique.

La file de revue résout ce problème en séparant le signal de détection de la réponse. Le pipeline signale le problème ; un humain tranche ; le journal d'audit capture le raisonnement.

Le concept de la file de revue

Lorsque le scan CVE d'un paquet produit des correspondances déclenchant une politique review au niveau de sévérité applicable, le pipeline positionne cve_status: pending_review. Le binaire du paquet est déplacé vers /repos/pool/ et un manifeste est créé — il est stocké, pas rejeté — mais il n'est pas enregistré dans l'index dists/ d'APT. Le paquet ne peut pas être installé par apt tant qu'une décision n'a pas été prise.

Ceci diffère de manière significative du rejet (quarantaine). Un paquet mis en quarantaine a échoué à une vérification stricte et est présumé dangereux. Un paquet pending_review a passé tous les contrôles de format, d'intégrité et antivirus — il n'est pas connu comme malveillant — mais il porte des CVE dont la politique dit qu'elles requièrent un jugement humain.

La file de revue est visible dans l'interface Repod pour les utilisateurs ayant les rôles admin, maintainer ou auditor. La file affiche tous les paquets en attente d'une décision, triés par échéance SLA et par score de risque EPSS.

Qui effectue la revue

Repod applique une séparation des devoirs entre les personnes qui peuvent uploader des paquets et celles qui peuvent les approuver pour le déploiement.

Rôle Peut uploader Peut voir la file de revue Peut approuver / rejeter Peut lire le journal d'audit
reader Non Non Non Non
uploader Oui Non Non Non
maintainer Oui Oui Oui Oui
auditor Non Oui Non Oui
admin Oui Oui Oui Oui

maintainer et admin peuvent approuver ou rejeter des paquets dans la file de revue. auditor dispose d'une visibilité en lecture seule — il peut examiner les détails CVE et l'état de la file à des fins de conformité, mais ne peut pas prendre de décision. uploader ne peut pas voir la file du tout : la personne qui uploade un paquet ne peut pas être la même personne qui l'approuve pour le déploiement.

En pratique, le rôle auditor est conçu pour votre équipe de sécurité (RSSI et ses analystes) afin de surveiller la file sans pouvoir agir unilatéralement, tandis que maintainer et admin sont les décideurs désignés.

Diagramme complet du workflow

sequenceDiagram
    actor Dev as Développeur / CI
    participant API as API Backend
    participant Pipeline as Pipeline de validation
    participant Queue as File de revue
    actor Admin as Admin (RSSI)
    participant APT as Dépôt APT

    Dev->>API: POST /upload/ (package.deb)
    API->>Pipeline: run_validation_pipeline()
    Pipeline-->>Pipeline: Étapes 1–6 (format, SHA256, AV, CVE...)
    Pipeline-->>API: cve_status: pending_review
    API->>Queue: Sauvegarde du manifeste (status: pending_review)
    API-->>Dev: 200 OK — revue CVE en attente

    Note over Queue: Paquet stocké dans pool/<br/>mais PAS dans dists/

    API--)Admin: Notification webhook / email

    Admin->>Queue: GET /security/packages-posture
    Admin->>Queue: Revue des détails CVE, EPSS, flags KEV, SLA

    alt Admin approuve
        Admin->>API: POST /security/packages/{name}/{version}/decide<br/>action: accept_risk, justification: "..."
        API->>Queue: Sauvegarde de la décision JSON → /repos/security/decisions/
        API->>APT: reprepro.add_package() → reprepro includedeb
        APT-->>APT: Régénération de dists/, signature d'InRelease
        API->>Queue: Mise à jour du statut du manifeste : accepted_risk
        API->>API: Journal d'audit : SECURITY_DECISION / SUCCESS
        APT-->>Dev: Paquet disponible via apt install
    else Admin rejette
        Admin->>API: POST /security/packages/{name}/{version}/decide<br/>action: reject, justification: "..."
        API->>Queue: Sauvegarde de la décision JSON → /repos/security/decisions/
        API->>Queue: Déplacement du binaire vers staging/quarantine/
        API->>Queue: Mise à jour du statut du manifeste : quarantined
        API->>API: Journal d'audit : SECURITY_DECISION / SUCCESS
        Note over APT: Le paquet n'entre jamais dans dists/
    end

Ce que voit le RSSI

Pour chaque paquet de la file de revue, l'interface présente une répartition structurée de chaque résultat CVE. L'information provient du tableau cve_results du manifeste, peuplé pendant le scan Grype et enrichi avec les données EPSS et CISA KEV.

Champ Source Ce qu'il vous indique
ID CVE Grype / NVD L'identifiant canonique — liens vers NVD et les avis fournisseur
Sévérité Catégorie du score de base CVSS Critique / Élevée / Moyenne / Faible / Négligeable
Score CVSS + vecteur NVD Le score numérique et la décomposition du vecteur d'attaque (AV, AC, PR, UI, S, C, I, A)
Probabilité EPSS FIRST.org (quotidien) Probabilité de 0 à 100 % d'exploitation dans les 30 jours
CISA KEV Catalogue CISA Flag : cette CVE est activement exploitée dans la nature actuellement
Composant affecté Correspondance d'artefact Grype La bibliothèque ou le binaire spécifique à l'intérieur du .deb qui porte la vulnérabilité
Correctif disponible État de correction Grype fixed (avec version), not-fixed, ou wont-fix
SLA restant Calculé depuis la politique Jours restants avant l'échéance de revue, avec code couleur (vert / ambre / rouge)

L'affichage est trié par un score de risque composite : les CVE marquées KEV apparaissent en premier, puis triées par EPSS décroissant, puis par CVSS. Un CVSS de 9,8 avec un EPSS de 0,003 apparaîtra en dessous d'un CVSS de 7,2 avec un EPSS de 0,94, car le risque ajusté par la probabilité est plus élevé pour ce dernier.

L'EPSS expliqué

L'EPSS — l'Exploit Prediction Scoring System — est un score quotidien publié par FIRST.org pour chaque CVE de la NVD. Il répond à une question différente du CVSS.

  • Le CVSS demande : quelle gravité cela pourrait-il avoir si c'était exploité ? (orienté impact, statique)
  • L'EPSS demande : quelle est la probabilité que ceci soit exploité dans les 30 prochains jours ? (orienté probabilité, dynamique)

Le score est un nombre flottant compris entre 0,0 et 1,0, représentant une probabilité. FIRST.org le dérive d'un modèle d'apprentissage automatique entraîné sur des preuves d'exploitation réelles, des caractéristiques de vulnérabilité et des flux de renseignement sur les menaces.

Pourquoi l'EPSS est plus actionnable que le CVSS seul :

Considérez deux CVE trouvées dans le même paquet :

Score CVSS Score EPSS Interprétation
CVE-A 9,8 (Critique) 0,003 (0,3 %) Impact théorique sévère, mais quasiment jamais ciblée en pratique — probablement trop complexe ou trop étroite pour être exploitée de manière fiable
CVE-B 7,2 (Élevée) 0,94 (94 %) Moyennement sévère mais activement exploitée par de nombreux acteurs malveillants en ce moment même

Une politique reposant uniquement sur le CVSS bloque la CVE-A et signale la CVE-B avec une priorité plus faible. Une revue tenant compte de l'EPSS traite la CVE-B comme l'élément urgent. Repod présente les deux signaux ensemble afin que le réviseur puisse établir cette distinction sans avoir à recouper manuellement des bases de données externes.

Repod récupère les scores EPSS via l'API FIRST.org et les met en cache pendant 24 heures dans /repos/security/epss_cache.json. Les scores sont mis à jour à chaque exécution du pipeline pour toute CVE absente du cache encore valide.

Le CISA KEV expliqué

Le catalogue CISA Known Exploited Vulnerabilities (KEV) est une liste organisée maintenue par l'agence américaine de cybersécurité et de sécurité des infrastructures (CISA). Une CVE apparaît dans le KEV lorsque la CISA a confirmé des preuves d'exploitation active dans la nature — pas une exploitation théorique, pas un code de preuve de concept, mais des attaques observées.

La CISA met à jour le catalogue en continu à mesure que de nouvelles preuves d'exploitation sont confirmées. Le catalogue inclut un champ dateAdded (quand l'exploitation a été confirmée pour la première fois) et un champ dueDate (l'échéance de remédiation obligatoire pour les agences fédérales américaines en vertu de la directive opérationnelle contraignante 22-01).

Pourquoi le KEV est le signal le plus urgent que Repod puisse afficher :

L'EPSS est une prédiction. Le KEV est un fait. Une CVE du catalogue KEV a été observée comme étant utilisée par de véritables attaquants contre de véritables systèmes. Pour les besoins de la file de revue Repod, un flag KEV sur n'importe quelle CVE — indépendamment du score CVSS ou EPSS — doit être traité comme l'élément de revue de plus haute priorité.

Repod récupère le flux JSON complet du KEV et le met en cache dans /repos/security/kev_cache.json avec un TTL de 24 heures. Pendant l'enrichissement, chaque ID CVE issu du scan Grype est vérifié par rapport à l'ensemble KEV. En cas de correspondance, in_kev: true est positionné sur l'enregistrement CVE dans le manifeste, et la CVE est signalée par un indicateur visuel dans la file de revue.

Dans les environnements air-gap, le cache KEV peuplé lors de la dernière exécution connectée à Internet reste disponible jusqu'à son expiration. Après expiration, les flags KEV sont omis du résultat d'enrichissement plutôt que traités comme de faux négatifs — le cache manqué est journalisé.

Prendre une décision

Chaque décision dans la file de revue requiert une justification textuelle obligatoire. L'interface ne soumettra pas le formulaire sans elle, et l'API impose un champ justification non vide. Ce n'est pas une contrainte d'ergonomie — c'est le mécanisme qui rend le journal d'audit juridiquement significatif.

Les types de décision disponibles se traduisent par des résultats de cycle de vie distincts :

Action Résultat technique Exemple de justification
accept_risk Paquet promu dans l'index APT ; décision enregistrée avec une date d'expiration optionnelle « La CVE-2024-1234 affecte le chemin de code TLS 1.0, désactivé dans notre configuration de déploiement. Risque accepté jusqu'à disponibilité d'un correctif. »
exception Identique à accept_risk, avec une date d'expiration obligatoire « Exception temporaire pour le déploiement du Projet X — l'isolation réseau compense. Valide jusqu'au 01/08/2026. »
upgrade_required Paquet retenu ; définit une version cible ; déclenche le compte à rebours SLA « Le paquet doit être mis à niveau vers la 3.0.8 qui corrige cette CVE. Le déploiement de la version corrigée est requis. »
reject Paquet déplacé en quarantaine ; permanent — sans expiration « La CVE-2023-0286 est marquée KEV avec un EPSS de 14 %. Le rejet est obligatoire. »

Les décisions accept_risk et exception peuvent recevoir une expiration (en jours). Lorsque la date d'expiration est dépassée, le planificateur SLA fait automatiquement revenir le statut du paquet à pending_review et notifie via webhook/email. Ceci force une réévaluation périodique des risques acceptés plutôt que de les laisser s'accumuler silencieusement.

L'approbation comme le rejet sont journalisés en tant qu'événements SECURITY_DECISION dans le fichier d'audit JSONL quotidien. La décision est également persistée sous forme de fichier JSON dans /repos/security/decisions/<name>_<version>_<arch>.json — séparément du journal d'audit — afin de pouvoir être interrogée par le planificateur et l'affichage de la file de revue sans avoir à parser des fichiers de logs.

Suivi SLA

Chaque niveau de sévérité peut avoir un SLA configurable : le nombre de jours pendant lesquels un paquet pending_review doit recevoir une décision. Le compte à rebours SLA est visible dans la file de revue et dans la vue de détail du paquet.

Le planificateur d'alertes SLA s'exécute quotidiennement à 08:00. Il vérifie toutes les décisions actives pour expiration et les paquets en pending_review pour dépassement de SLA. Les paquets dont le SLA arrive à échéance dans les 7 jours reçoivent une notification d'avertissement (webhook + email). Les décisions expirées reviennent automatiquement à pending_review.

État Indicateur visuel
SLA > 7 jours restants Vert — aucune action requise pour l'instant
SLA ≤ 7 jours restants Ambre — revue recommandée prochainement
SLA expiré Rouge — en retard ; escalade requise
Aucun SLA configuré Aucun indicateur

Après la décision

En cas d'approbation (accept_risk ou exception) :

Le backend appelle services/reprepro.py:add_package() avec le nom de fichier du paquet et la distribution cible — le point d'entrée unique pour reprepro includedeb, partagé avec les chemins d'upload direct et d'import depuis Internet. Il exécute reprepro includedeb <distribution> <path> directement contre le volume partagé /repos/. Reprepro ajoute le paquet à l'index APT, régénère Packages.gz, met à jour le fichier Release, et re-signe InRelease en utilisant la clé GPG située dans /repos/gnupg. Le champ status du manifeste est mis à jour à accepted_risk ou exception. À partir de ce point, apt update && apt install <package> trouvera et installera le paquet.

En cas de rejet :

Le fichier binaire — qui était stocké dans /repos/pool/ pendant la période pending_review — est déplacé vers /repos/staging/quarantine/<name>_<version>_<arch>.deb. Le champ status du manifeste est mis à jour à quarantined. Le paquet n'apparaît jamais dans dists/ et ne peut pas être installé via APT. Le JSON de décision dans /repos/security/decisions/ enregistre le rejet de manière permanente.

Dans les deux cas, une notification webhook est envoyée à l'URL configurée (si webhook_enabled: true dans les paramètres) et une notification email est envoyée aux adresses configurées.

Couverture multi-format (Maven, PyPI, npm)

Tout ce qui a été décrit ci-dessus — la file de revue, le modèle decision_records, la justification obligatoire, le suivi SLA, le journal d'audit — s'applique de manière identique aux artefacts Maven, PyPI et npm. Une décision est clé par (package, version, arch) sans aucune notion de format de paquet inscrite dans le schéma, de sorte que le même endpoint POST /security/packages/{name}/{version}/decide, la même interface de file de revue, et les mêmes actions accept_risk/exception/reject/upgrade_required s'appliquent à un .jar, un wheel ou un tarball exactement comme à un .deb ou un .rpm.

Deux différences mécaniques méritent d'être comprises, car le mécanisme de visibilité diffère d'APT/RPM même si le mécanisme de décision ne diffère pas :

  • Pas d'étape séparée d'« ajout à l'outil de dépôt ». APT/RPM ont un moment distinct où reprepro/createrepo_c publie un paquet dans l'arborescence de la distribution — c'est l'étape qu'un paquet pending_review saute. Maven/PyPI/npm n'ont pas d'étape de publication équivalente au niveau de l'outil ; l'artefact est déjà dans le stockage dès qu'il est uploadé. La visibilité est plutôt contrôlée directement par le champ status du manifeste : tout endpoint de listing ou de téléchargement (la Simple API de PyPI, le packument npm, un téléchargement direct de GAV Maven) vérifie le statut et retourne 404 pour tout ce qui est en pending_review, quarantined, ou upgrade_required — l'artefact existe physiquement dans le stockage mais est inaccessible via tout client (pip, npm, mvn) jusqu'à ce qu'une décision le résolve.
  • L'index de Maven est un fichier généré, pas une requête en direct. La page Simple API de PyPI et le packument npm sont calculés à neuf depuis la base de données à chaque requête, de sorte qu'un changement de statut prend effet immédiatement. maven-metadata.xml est un fichier statique écrit sur le stockage — accepter ou rejeter un artefact Maven déclenche une régénération explicite de ce fichier, de sorte que la liste des versions vue par mvn/gradle reste synchronisée avec la décision.

Portée du scan first-party. Le scan Grype décrit tout au long de cette page ne regarde jamais que les octets propres de l'artefact — le jar/wheel/tarball que vous avez uploadé — jamais ses dépendances déclarées. Une CVE dans une bibliothèque tirée ultérieurement, au moment de la construction du consommateur (npm install, mvn résolvant une plage de versions, pip résolvant une exigence), est invisible pour ce scan. Il s'agit d'une lacune distincte et séparée, comblée par le scan de dépendances décrit ci-après.

Scan CVE des dépendances (npm, Maven, PyPI)

Pour npm, Maven et PyPI, repod exécute une seconde vérification indépendante contre les dépendances déclarées du paquet — pas les octets propres de l'artefact, et pas une résolution transitive complète (pas de parcours d'arbre équivalent à npm ls/mvn dependency:tree), juste les dépendances directes telles que déclarées :

  • npm — l'objet dependencies du package.json, pris directement depuis la charge utile de publication (ou depuis l'import de registre).
  • Maven — le bloc <dependencies> du fichier .pom (à l'exclusion de <dependencyManagement>, qui fixe des versions pour les sous-modules plutôt que de déclarer une réelle dépendance de l'artefact lui-même).
  • PyPI — les champs requires_dist provenant d'un twine upload, ou de l'API JSON de PyPI à l'import. L'upload manuel sans métadonnées de dépendance associées n'est pas scanné — il n'y a rien à scanner sans ouvrir l'archive.

Chaque nom de dépendance déclarée est interrogé sur OSV.dev, une base de données de vulnérabilités publique, gratuite et sans clé, par nom de paquet uniquement — sans correspondance de plage de version. C'est délibéré et par nature sur-inclusif : faire correspondre une plage déclarée (^1.2.3, une version Maven héritée d'un POM parent, un spécificateur PEP 508) à une version résolue précise n'est pas fiable, et le principe permanent du projet est que le sur-signalement est sûr, tandis que le sous-signalement risque de masquer une vulnérabilité réelle. Le résultat est chaque vulnérabilité jamais publiée pour ce nom de paquet dans cet écosystème — le réviseur humain est responsable de juger si la version réellement utilisée est affectée.

Alimente la même cve_policy, avec une dégradation délibérée. Une découverte de dépendance est évaluée contre la même correspondance sévérité → action que celle utilisée pour le scan Grype propre au paquet, et un résultat review-ou-pire fait basculer le paquet en pending_review dans la même file de revue RSSI décrite tout au long de cette page — aucun chemin de revue séparé, aucun nouvel état. La seule différence : un verdict block issu d'une découverte de dépendance est toujours dégradé en review, jamais un rejet pur et simple. Parce que la correspondance se fait par nom seul, une part significative des résultats « Critique » se révèlent être une version déjà corrigée en amont — bloquer strictement une publication légitime sur un signal aussi bruyant serait disproportionné. Une découverte de dépendance ne peut jamais qu'ajouter une revue que le paquet n'aurait pas eue autrement — elle ne dégrade ni ne remplace jamais un block déjà décidé par le propre scan Grype du paquet.

Stocké séparément de cve_results (qui couvre le code propre du paquet) dans un champ dédié dependency_cve_scan, affiché dans sa propre section de l'interface de revue CVE — jamais fusionné avec les découvertes first-party, puisqu'elles répondent à une question différente. Un échec réseau lors de l'interrogation d'OSV.dev ne bloque jamais la publication : le scan de dépendances reste simplement vide pour ce paquet, exactement comme s'il précédait cette fonctionnalité.

Au-delà du scan à l'upload : dual-scan et re-matching CVE

Le scan Grype au moment de l'upload/import est un instantané unique : un moteur, scannant les octets propres de l'artefact, une seule fois. Deux mécanismes supplémentaires étendent la couverture dans le temps, et ils sont complémentaires plutôt que redondants — chacun comble une lacune différente :

Mécanisme Ce qui change Portée Déclencheur
Scan de dépendances (ci-dessus) Cible de scan différente — dépendances déclarées au lieu de l'artefact lui-même npm, Maven, PyPI À la publication/l'import
Dual-scan Moteur différent — Trivy comme second avis aux côtés de Grype SaaS uniquement, opt-in Balayage périodique des paquets déjà publiés
Re-matching CVE Même moteur, ré-exécuté à mesure que sa base de données de vulnérabilités évolue dans le temps Tous les formats de paquets, toutes les éditions, activé par défaut Quotidien, automatiquement

Dual-scan : validation croisée Grype + Trivy (SaaS uniquement)

Aucun scanner CVE n'est exhaustif — les moteurs différents ont des angles morts de correspondance différents, et repod ne prétend pas que le scan initial de Grype attrape tout. Dans les déploiements SaaS, un second moteur indépendant, Trivy, rescanne les paquets déjà publiés (deb, rpm, apk, Maven, PyPI, npm — les images OCI sont exclues, puisqu'elles vivent dans le registre Zot séparé plutôt qu'un stockage équivalent à pool/) lors d'un balayage périodique quotidien. Trivy s'exécute en mode serveur contre un sidecar dédié afin que sa base de données de vulnérabilités reste chargée en mémoire d'un scan à l'autre — proportionné à un compute SaaS partagé, non exécuté en Enterprise on-premise ni en Community, et opt-in même au sein du SaaS.

Le dual-scan ne s'exécute jamais dans le chemin bloquant de l'upload — il ne fait que réexaminer des paquets déjà marqués validated, il ne peut donc jamais ajouter de latence à un upload ou un import. Il s'agit d'une corrélation, pas d'un remplacement : les cve_results d'origine de Grype ne sont jamais écartés. Chaque entrée CVE gagne un champ detected_by["grype"], ["trivy"], ou ["grype", "trivy"] — visible dans l'interface comme un badge sur chaque ligne CVE. Une CVE sur laquelle les deux moteurs s'accordent est affichée de manière discrète (ce n'est pas une information nouvelle) ; une CVE que Trivy seul a trouvée est le signal qui compte réellement.

Seule une découverte avec detected_by == ["trivy"] — quelque chose que le scan initial de Grype a totalement manqué — peut déclencher une nouvelle décision de politique. Une CVE que Trivy reconfirme, et que Grype avait déjà trouvée, n'est pas une information nouvelle et ne redéclenche jamais de revue, quelle que soit sa sévérité. Comme pour le scan de dépendances, un verdict block détecté uniquement par Trivy est dégradé en review : un paquet déjà publié ne peut pas être rétroactivement bloqué strictement, seulement renvoyé dans la file RSSI. La sortie brute de Trivy et l'horodatage du dernier scan sont conservés dans un champ trivy_scan séparé à des fins d'audit, aux côtés des cve_results corrélés.

Re-matching CVE via SBOM stocké

Un paquet est scanné par Grype exactement une fois, à l'upload ou à l'import. Si une CVE est publiée pour l'un de ses composants le lendemain, rien ne l'avait détectée auparavant — la validation croisée Trivy du dual-scan est SaaS uniquement et opt-in, et dans tous les cas il s'agit d'un moteur différent regardant le même instantané ponctuel. Le re-matching CVE comble cette lacune directement : il ré-exécute le même moteur, Grype, contre sa propre base de données de vulnérabilités — rafraîchie quotidiennement dans tous les cas — pour chaque paquet déjà publié, sur les sept formats pris en charge (deb, rpm, apk, Maven, PyPI, npm, OCI) et chaque édition, y compris Community.

Le détail mécanique clé : le re-matching ne rouvre ni ne ré-extrait jamais le fichier de paquet d'origine. Un SBOM CycloneDX est capturé comme sous-produit du scan Grype d'origine (Grype peut émettre à la fois le rapport de vulnérabilités et un SBOM en un seul passage) et stocké sur disque. Le re-matching exécute grype sbom:<stored-file>, qui réapplique la base de données de vulnérabilités actuelle contre cette liste de composants stockée — une opération locale, mesurablement moins coûteuse qu'un scan complet puisqu'aucune extraction binaire n'est impliquée.

cve_results est intégralement rafraîchi avec le nouvel ensemble de correspondances à chaque re-matching, mais seuls les ID CVE absents du scan précédent sont évalués contre cve_policy pour décider si le paquet nécessite une revue. Une CVE précédemment connue qui cesse de correspondre (par exemple, parce que la base de données Grype a corrigé un faux positif antérieur) ne redéclenche jamais rétroactivement une décision — elle disparaît simplement de la liste rafraîchie. Comme pour le dual-scan, une violation de politique découverte de cette manière — que l'action configurée soit block ou review — achemine le paquet vers la même file RSSI pending_review ; il n'y a aucune action de mise en quarantaine automatique ici, puisque le re-matching ne touche jamais qu'un paquet déjà publié.

Contrairement au dual-scan, le re-matching CVE s'exécute par défaut, quotidiennement, dans chaque déploiement — il n'est pas opt-in. Un paquet sans SBOM stocké (publié avant l'existence de cette fonctionnalité, ou pour lequel la capture du SBOM a échoué) est simplement ignoré, pas traité comme une erreur.

Posture des paquets applicatifs (distincte de la conformité du parc)

La métrique « Conformité du parc » de Repod (GET /compliance/summary) est — et reste — un chiffre centré sur la machine : le pourcentage de machines gérées exemptes de CVE actives, calculé à partir de inventory_cve jointe à inventory_clients. Un artefact Maven/PyPI/npm n'est jamais « installé » sur une machine gérée par Repod — il est consommé par mvn/pip/npm sur un poste de développeur ou un runner CI sur lequel Repod n'a aucune visibilité. Fusionner ces artefacts dans la conformité du parc mélangerait deux questions réellement différentes, elles ne le sont donc pas.

À la place, GET /compliance/summary/packages rapporte un indicateur de posture séparé sur les paquets applicatifs du dépôt : combien d'artefacts Maven/PyPI/npm publiés existent, ventilés par format, combien portent une découverte de sévérité Critique ou Élevée, et combien se trouvent actuellement dans la file de revue. Traitez-le comme une « posture de sécurité de la chaîne d'approvisionnement », aux côtés de la « conformité du parc », sans y être fusionné.

Promotion (entre dépôts, pas entre environnements)

Pour APT/RPM, la « promotion » déplace un paquet entre des distributions qui représentent généralement des étapes de déploiement (par exemple jammy-testingjammy). Pour Maven/PyPI/npm, il n'existe pas de notion équivalente d'environnement machine dans lequel promouvoir — ces artefacts ne sont pas déployés sur des machines du tout. Ce qui subsiste, c'est l'idée d'un dépôt organisé : promouvoir un artefact Maven/PyPI/npm le copie (avec ses checksums) d'un dépôt/ index/namespace à un autre — par exemple, d'un dépôt testé en interne vers celui que votre CI/CD utilise réellement pour les publications.

POST /maven/repositories/{name}/promote, POST /pypi/repositories/{name}/promote, et POST /npm/repositories/{name}/promote prennent chacun le nom du paquet, la version, et le dépôt cible. La promotion :

  • Revérifie les filtres de contenu du dépôt cible (un paquet autorisé dans le dépôt source n'est pas automatiquement autorisé dans le dépôt cible).
  • Refuse de promouvoir tout ce qui est encore en pending_review ou quarantined — la promotion ne peut pas servir à contourner une décision de sécurité en attente.
  • Copie les octets de l'artefact et re-dérive les checksums à destination (ne fait jamais confiance aux checksums calculés pour l'emplacement source).
  • Pour Maven spécifiquement, régénère le maven-metadata.xml de destination afin que la version promue devienne résolvable.

Journal d'audit

Chaque décision est capturée sous forme d'entrée JSONL structurée dans le fichier d'audit quotidien à /repos/audit/YYYY-MM-DD.jsonl. Voici un exemple réel provenant d'une instance Repod en production — une approbation CVE avec justification obligatoire :

{
  "timestamp": "2026-05-11T15:12:58.443791+00:00",
  "action": "SECURITY_DECISION",
  "user": "admin",
  "result": "SUCCESS",
  "package": "openssl",
  "version": "3.0.2-0ubuntu1",
  "detail": "Action : accept_risk | Justification : CVEs corrigées dans la prochaine mise à jour planifiée. Risque acceptable en environnement contrôlé. | Expire : 2026-06-10T15:12:57.916475+00:00"
}

Et un rejet correspondant :

{
  "timestamp": "2026-05-11T15:15:29.397702+00:00",
  "action": "SECURITY_DECISION",
  "user": "admin",
  "result": "SUCCESS",
  "package": "libssl3",
  "version": "3.0.2-0ubuntu1",
  "detail": "Action : reject | Justification : CVE-2023-0286 est activement exploitée (KEV CISA) avec EPSS 14%. Rejet immédiat. | Expire : jamais"
}

Le journal d'audit est en append-only au niveau du système de fichiers — aucun endpoint API ne peut modifier ou supprimer des entrées. Chaque jour crée un nouveau fichier ; le backend n'ouvre jamais en écriture le fichier d'un jour précédent. Pour l'intégration SIEM, le format JSONL est directement consommable par Elasticsearch, Splunk, et la plupart des plateformes d'agrégation de logs.

Interroger le journal d'audit

L'endpoint GET /artifacts/audit/logs retourne les entrées récentes en ordre chronologique inversé. Utilisez GET /artifacts/audit/package/<name> pour récupérer l'historique complet d'un paquet spécifique sur toutes les dates — y compris chaque tentative d'upload, décision CVE, événement de quarantaine, et suppression.