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_cpublie un paquet dans l'arborescence de la distribution — c'est l'étape qu'un paquetpending_reviewsaute. 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 champstatusdu 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 enpending_review,quarantined, ouupgrade_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.xmlest 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 parmvn/gradlereste 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
dependenciesdupackage.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_distprovenant d'untwine 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-testing → jammy). 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_reviewouquarantined— 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.xmlde 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.