« 12 vulnérabilités détectées (1 critical, 2 high, 7 medium) » — J’ai scanné
mon API FastAPI devops-status-api avec Grype. En analysant chaque CVE, je
réalise que CVE-2025-12084 (xml.dom.minidom) affecte Python 3.12, mais mon code
ne parse jamais de XML, uniquement du JSON. C’est un faux positif qui pollue
mes rapports.
C’est l’alert fatigue : à force de faux positifs, mes équipes finissent par ignorer les alertes. Le VEX (Vulnerability Exploitability eXchange) résout ce problème en permettant de documenter l’exploitabilité réelle d’une vulnérabilité dans mon contexte spécifique.
Dans ce guide, je montre concrètement :
- C’est quoi VEX et pourquoi c’est devenu indispensable
- Comment créer des documents VEX (OpenVEX et CycloneDX)
- Comment intégrer VEX avec Grype pour filtrer les faux positifs
- Comment automatiser VEX en CI/CD (GitHub Actions)
- Comment utiliser VEX pour la conformité (CRA, NIS2)
Qu’est-ce que VEX ?
Section intitulée « Qu’est-ce que VEX ? »Définition simple
Section intitulée « Définition simple »VEX (Vulnerability Exploitability eXchange) est un format standardisé pour documenter si une vulnérabilité est exploitable dans votre produit.
Analogie : un constructeur auto identifie un défaut d’airbag (CVE). Sans VEX, tous les propriétaires reçoivent l’alerte et doivent vérifier manuellement si leur véhicule est concerné. Avec VEX, le constructeur envoie un document explicite : “Votre véhicule VIN ABC123 n’est pas affecté car vous avez la version 2.0 de l’airbag (sans défaut)”.
Dans le logiciel : votre SBOM liste des centaines de composants. Quand une CVE sort, les scanners alertent tous les produits contenant ce composant. VEX documente : “Dans mon produit, cette CVE n’est pas exploitable car [raison]”. scanners alertent sur toutes les images qui contiennent cette bibliothèque. Mais en réalité, votre code n’utilise peut-être même pas la fonction vulnérable. VEX permet de documenter cette réalité de façon standardisée.
Le problème : l’alert fatigue
Section intitulée « Le problème : l’alert fatigue »Un scan de vulnérabilités sur un SBOM complet peut remonter des centaines de CVE. En pratique, la majorité ne sont pas exploitables dans le contexte.
Scénario concret : une matinée de DevSecOps sans VEX
9h00 : Je lance grype sur mon image api-users:latest qui vient de builder.
Résultat : 237 vulnérabilités (87 critical, 102 high, 48 medium).
9h10 : Je commence à analyser. Première CVE : CVE-2024-1234 sur libxml2,
sévérité CRITICAL. Je vais sur la NVD, je lis l’exploit : il faut parser du XML
malveillant.
9h25 : Je vérifie mon code : on n’utilise jamais libxml2. C’est une
dépendance de l’image de base Alpine, mais mon API REST en Node.js ne touche pas
au XML. Faux positif #1.
9h40 : Deuxième CVE : CVE-2024-5678 sur openssl. Je lis : vulnérabilité dans
le handshake TLS. Je vérifie : mon API est derrière un ingress Nginx qui gère
tout le TLS. Mon conteneur ne fait jamais de TLS directement. Faux positif
#2.
10h30 : J’en suis à la 15ème CVE. 13 faux positifs. 2 vrais problèmes identifiés.
12h00 : J’ai traité 40 CVE sur 237. Je suis épuisé. Les 197 restantes ? Je les mets en “sourdine” mentale. Si une vraie vulnérabilité critique arrive cet après-midi, je risque de la manquer : c’est l’alert fatigue.
Situations courantes de faux positifs :
- Dépendance présente mais jamais appelée : le code vulnérable existe dans
l’image mais aucun chemin d’exécution ne l’atteint (exemple :
libxml2dans une API REST qui ne traite que du JSON) - Composant désactivé : une feature flag ou une configuration désactive la
fonction concernée (exemple : serveur web avec module
mod_phpdésactivé) - Contexte d’exploitation absent : la CVE nécessite un accès réseau que votre conteneur n’a pas (exemple : vulnérabilité SSH dans un pod sans service exposé)
- Déjà mitigé : un WAF, un contrôle réseau ou une sandbox neutralise l’attaque (exemple : vulnérabilité injection SQL mitigée par un WAF upstream)
Sans moyen de qualifier ces vulnérabilités, vos équipes passent leur temps à trier du bruit au lieu de corriger les vrais problèmes. VEX résout ce problème en documentant une fois pour toutes qu’une CVE n’est pas exploitable, pour ne plus avoir à le ré-analyser.
La solution : qualifier l’exploitabilité
Section intitulée « La solution : qualifier l’exploitabilité »VEX associe chaque CVE à un statut d’exploitabilité :
| Statut | Exemple |
|---|---|
| not_affected | CVE-2025-12084 (xml.dom.minidom) dans API JSON-only : code jamais exécuté |
| affected | GHSA-6c5p-j8vq-pqhj (python-jose) : utilisé pour JWT auth, upgrade requis |
| fixed | starlette 0.41.3→0.49.1 : vulnérabilité résolue (commit abc123) |
| under_investigation | CVE récente, analyse en cours, résultats attendus 23/12 |
L’avantage : ces statuts sont standardisés. Grype, Trivy, Dependency-Track les comprennent tous. Vous documentez une fois, tous vos outils appliquent automatiquement le filtrage.
Cas d’usage
Section intitulée « Cas d’usage »- Réduire le bruit : 12→11 CVE (faux positif filtré automatiquement)
- Conformité CRA/NIS2 : documenter pourquoi une CVE n’est pas exploitable
- Audits : prouver l’analyse de chaque CVE (VEX signable avec Cosign)
- Automatisation :
grype --vex app.vex.jsonapplique vos décisions
Points forts : standardisé (OpenVEX, CycloneDX, CSAF), intégré (Grype, Trivy, Dependency-Track), signable (Cosign), versionnable.
Prérequis
Section intitulée « Prérequis »Connaissances :
- Comprendre ce qu’est un SBOM et comment le générer avec Syft
- Avoir déjà utilisé un scanner de vulnérabilités (Grype ou Trivy)
- Connaître les bases des CVE
Outils à installer :
-sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
# Installation vexctl (optionnel) go install github.com/openvex/vexctl@latest
# Vérification syft version grype versionanchore/syft:v1.38.2 docker pull anchore/grype:v0.104.2 docker pullghcr.io/openvex/vexctl:v0.4.1Formats VEX
Section intitulée « Formats VEX »Les 3 formats standards
Section intitulée « Les 3 formats standards »| Format | Porté par | Cas d’usage | Lien |
|---|---|---|---|
| OpenVEX | Chainguard, Sigstore | DevSecOps, écosystème cloud-native | openvex.dev |
| CycloneDX VEX | OWASP | Intégré dans SBOM CycloneDX (v1.4+) | cyclonedx.org |
| CSAF VEX | OASIS | Grands éditeurs (Red Hat, Cisco), conformité | oasis-open.org |
Recommandation : pour un projet cloud-native, OpenVEX est le plus simple. Si vous utilisez déjà CycloneDX pour vos SBOM, CycloneDX VEX s’intègre nativement.
OpenVEX : format minimaliste
Section intitulée « OpenVEX : format minimaliste »OpenVEX = format JSON léger listant des statements sur l’exploitabilité de CVE.
Exemple :
{ "@context": "https://openvex.dev/ns/v0.2.0", "@id": "https://mon-org.com/vex/mon-app-v1.2.0", "author": "Security Team <security@mon-org.com>", "timestamp": "2024-12-20T10:00:00Z", "version": 1, "statements": [{ "vulnerability": { "name": "CVE-2024-1234" }, "products": ["pkg:oci/mon-app@sha256:abc123..."], "status": "not_affected", "justification": "vulnerable_code_not_in_execute_path", "impact_statement": "API n'utilise jamais XML, code vulnérable non exécuté" }]}Champs clés :
@id: identifiant unique du VEXproducts: format purl (pkg:oci/nom@sha256:digest)status:not_affected,affected,fixed,under_investigationjustification: code normalisé (scanners le comprennent)impact_statement: explication humaine (auditeurs, collègues) cette CVE critical ?”.
Justifications OpenVEX : comprendre chaque code
Section intitulée « Justifications OpenVEX : comprendre chaque code »La justification est un code normalisé qui explique pourquoi le statut
est ce qu’il est. C’est ce code que les scanners utilisent pour filtrer
automatiquement.
| Justification | Signification | Exemple concret |
|---|---|---|
component_not_present | Le composant vulnérable n’existe pas dans le produit | CVE sur postgresql-client mais vous utilisez MySQL : “Nous n’utilisons pas PostgreSQL, le package n’est pas installé.” |
vulnerable_code_not_present | Le code vulnérable a été supprimé (patch custom) | Vous avez recompilé nginx sans le module ngx_http_dav_module vulnérable : “Module DAV désactivé à la compilation, code absent du binaire.” |
vulnerable_code_not_in_execute_path | Le code existe mais n’est jamais appelé | libxml2 présent dans l’image mais votre app Node.js ne fait que du JSON : “Notre API REST ne parse jamais de XML, les fonctions libxml2 ne sont jamais invoquées.” |
vulnerable_code_cannot_be_controlled_by_adversary | L’attaquant ne peut pas atteindre le code | CVE sur endpoint admin https://app/admin/debug mais votre NetworkPolicy bloque tout accès externe : “Endpoint exposé uniquement sur localhost, inaccessible depuis l’extérieur.” |
inline_mitigations_already_exist | Des mitigations (WAF, sandbox) neutralisent l’exploit | CVE injection SQL mais vous avez un WAF ModSecurity en amont : “WAF bloque toutes les injections SQL, exploit impossible même si code vulnérable.” |
Comment choisir la bonne justification ?
- Le composant est-il installé ? Si non →
component_not_present - Le code vulnérable est-il présent ? Si patché/supprimé →
vulnerable_code_not_present - Le code peut-il être exécuté ? Si jamais appelé →
vulnerable_code_not_in_execute_path - Un attaquant peut-il y accéder ? Si bloqué réseau/firewall →
vulnerable_code_cannot_be_controlled_by_adversary - Y a-t-il une protection en amont ? Si WAF/sandbox →
inline_mitigations_already_exist
Astuce : en cas de doute, utilisez vulnerable_code_not_in_execute_path (le
plus courant) et détaillez dans l’impact_statement.
CycloneDX VEX : intégré au SBOM
Section intitulée « CycloneDX VEX : intégré au SBOM »CycloneDX 1.4+ intègre VEX directement dans le SBOM via la propriété
vulnerabilities. Au lieu d’avoir deux fichiers (sbom.json + vex.json),
vous avez un seul fichier qui contient à la fois :
- La liste des composants (section
components) - Les vulnérabilités connues avec leur statut d’exploitabilité (section
vulnerabilities)
Exemple détaillé :
{ "bomFormat": "CycloneDX", "specVersion": "1.6", "components": [ /* ... */ ], "vulnerabilities": [ { "id": "CVE-2024-1234", "source": { "name": "NVD" }, "ratings": [{ "severity": "high" }], "affects": [ { "ref": "pkg:apk/alpine/libxml2@2.9.14", "versions": [{ "version": "2.9.14", "status": "affected" }] } ], "analysis": { "state": "not_affected", "justification": "code_not_reachable", "response": ["will_not_fix"], "detail": "libxml2 n'est pas utilisé par l'application" } } ]}Explication de la section analysis (c’est le VEX) :
state: équivalent dustatusOpenVEX (not_affected,affected,resolved,in_triage)justification: raison technique (similaire à OpenVEX mais codes légèrement différents :code_not_reachable,requires_configuration,requires_environment, etc.)response: action prise (will_not_fix,update,rollback,workaround_available)detail: explication humaine (commeimpact_statementen OpenVEX)
Avantages :
- Un seul fichier à distribuer : pratique pour les audits, les clients, les pipelines CI/CD
- Intégration native avec Syft (génère CycloneDX), Grype (lit CycloneDX), Dependency-Track
Inconvénients :
- Régénération complète : si vous découvrez demain qu’une nouvelle CVE est un faux positif, vous devez régénérer tout le SBOM (pas juste le VEX)
- Taille du fichier : un SBOM peut faire 5 MB, si vous ajoutez 200 CVE avec VEX, ça peut doubler
Quand utiliser CycloneDX VEX vs OpenVEX ?
- CycloneDX VEX : si vous générez déjà des SBOM CycloneDX, que vous voulez un seul fichier, et que le VEX change peu (1 version par release)
- OpenVEX : si vous voulez mettre à jour le VEX indépendamment du SBOM (exemple : nouvelle CVE découverte, vous ajoutez un statement sans rebuilder l’image)
Créer votre premier VEX : pas-à-pas complet
Section intitulée « Créer votre premier VEX : pas-à-pas complet »Workflow complet : de l’image au VEX appliqué
Section intitulée « Workflow complet : de l’image au VEX appliqué »Voici un scénario réel complet, étape par étape. Vous venez de builder une image
Docker devops-status-api:test et vous voulez scanner les vulnérabilités, puis
créer un VEX pour les faux positifs.
-
Générer le SBOM de votre image
Fenêtre de terminal # Générer SBOM au format CycloneDX JSONsyft devops-status-api@sha256:0c4d2ee8... -o cyclonedx-json > devops-status-api.sbom.json# ✔ Cataloged packages [118 packages]Résultat : fichier
devops-status-api.sbom.json(620K). -
Scanner les vulnérabilités avec Grype
Fenêtre de terminal grype sbom:devops-status-api.sbom.json -o table# ✔ Scanned for vulnerabilities [12 vulnerability matches]# ├── by severity: 1 critical, 2 high, 7 medium, 2 low# python-jose 3.3.0 GHSA-6c5p-j8vq-pqhj Critical# python 3.12.12 CVE-2025-12084 Medium# ... 10 autres CVE ... -
Analyser chaque CVE pour déterminer si elle est exploitable
Pour
CVE-2025-12084(xml.dom.minidom) :- Description NVD : “When building nested elements using xml.dom.minidom…”
- Analyse code : API FastAPI ne traite que du JSON, aucun appel à
xml.dom.minidom - Conclusion :
vulnerable_code_not_in_execute_path
-
Créer le VEX avec vexctl
Fenêtre de terminal # Récupérer le digest de l'imageDIGEST=$(docker inspect devops-status-api:test | jq -r '.[0].Id')# sha256:0c4d2ee8...# Créer le VEX pour CVE-2025-12084vexctl create \--author="security@stephane-robert.info" \--product="pkg:oci/devops-status-api@sha256:0c4d2ee81f66cc0a4fc155b6cf233a1e435a6c2102e0b5a613ecd70ac3c55f4c" \--vuln="CVE-2025-12084" \--status="not_affected" \--justification="vulnerable_code_not_in_execute_path" \--impact-statement="Notre API FastAPI ne parse jamais de XML, uniquement du JSON. Le module xml.dom.minidom est présent dans Python 3.12 mais les fonctions vulnérables ne sont jamais appelées." \> devops-status-api.vex.json -
Ajouter d’autres CVE au même VEX
Pour documenter que
GHSA-6c5p-j8vq-pqhj(python-jose) nécessite une action :Fenêtre de terminal # vexctl peut merger des statementsvexctl create \--product="pkg:oci/devops-status-api@sha256:0c4d2ee81f66cc0a4fc155b6cf233a1e435a6c2102e0b5a613ecd70ac3c55f4c" \--vuln="GHSA-6c5p-j8vq-pqhj" \--status="affected" \--impact-statement="python-jose utilisé pour JWT auth. Upgrade vers 3.4.0+ requis." \| vexctl merge devops-status-api.vex.json - > devops-status-api-updated.vex.jsonmv devops-status-api-updated.vex.json devops-status-api.vex.json -
Rescanner avec le VEX appliqué
Fenêtre de terminal grype sbom:devops-status-api.sbom.json --vex devops-status-api.vex.json# ✔ Scanned for vulnerabilities [11 vulnerability matches]# ├── by severity: 1 critical, 2 high, 7 medium, 2 low# CVE-2025-12084 a disparu !
Vous venez de créer votre premier VEX ! CVE-2025-12084 ne pollue plus vos
rapports de scan. Chaque fois que Grype scannera
devops-status-api@sha256:0c4d2ee8..., seules les 11 CVE réellement
exploitables seront affichées.
Autres méthodes de création
Section intitulée « Autres méthodes de création »Le workflow ci-dessus montre la création manuelle de VEX en suivant chaque étape. Pour des workflows plus avancés (automation, fusion de VEX, gestion de multiples versions), consultez le guide complet vexctl qui détaille :
- Toutes les commandes vexctl (create, merge, validation)
- Scripts d’automatisation pour CVE récurrentes
- Intégration CI/CD (GitHub Actions, GitLab CI)
- Distribution avec Cosign (signature, attestation)
- Gestion multi-versions d’images
Intégrer VEX avec Grype : réduire le bruit de 90%
Section intitulée « Intégrer VEX avec Grype : réduire le bruit de 90% »Scan sans VEX : l’alert fatigue en action
Section intitulée « Scan sans VEX : l’alert fatigue en action »Voici ce qui se passe quand vous scannez une image classique sans VEX :
grype devops-status-api@sha256:0c4d2ee8... -o table# ✔ Scanned [12 vulnerability matches]# ├── 1 critical, 2 high, 7 medium, 2 low# python-jose 3.3.0 GHSA-6c5p-j8vq-pqhj Critical# python 3.12.12 CVE-2025-12084 Medium ← faux positif# ... 10 autresProblème : sur ces 12 CVE, au moins 1 est un faux positif avré (CVE-2025-12084 sur xml.dom.minidom). Les autres peuvent être :
- Des bibliothèques présentes mais jamais utilisées
- Des vulnérabilités déjà mitigées par votre architecture (WAF, Network Policy)
- Des CVE sur des composants désactivés
Vous devez manuellement trier chaque CVE pour savoir quoi traiter en priorié. C’est exactement ce que VEX automatise.
Scan avec VEX : bruit réduit
Section intitulée « Scan avec VEX : bruit réduit »Maintenant, même scan avec un fichier VEX :
grype devops-status-api@sha256:0c4d2ee8... --vex ./devops-status-api.vex.json# ✔ Scanned [11 vulnerability matches]# ├── 1 critical, 2 high, 7 medium, 2 low# CVE-2025-12084 filtré ✓Résultat : 11 CVE affichées (au lieu de 12). CVE-2025-12084 a été automatiquement filtrée par Grype grâce au VEX.
Ce qui s’est passé :
- Grype a lu le fichier
devops-status-api.vex.json - Pour chaque CVE détectée, il a vérifié si un statement VEX existe
- Pour CVE-2025-12084 : statement trouvé avec
status: not_affected→ CVE supprimée de l’output - Pour les autres CVE : aucun statement ou
status: affected→ CVE affichée normalement
Modes d’application VEX dans Grype : 3 façons de fournir le VEX
Section intitulée « Modes d’application VEX dans Grype : 3 façons de fournir le VEX »Grype supporte plusieurs méthodes pour appliquer un VEX. Voici les 3 modes les plus courants.
1. VEX externe (OpenVEX JSON)
Le VEX est un fichier séparé que vous passez explicitement à Grype :
grype sbom:./app.sbom.json --vex ./app.vex.jsonCas d’usage : vous générez le SBOM avec Syft (CycloneDX), puis vous créez un VEX OpenVEX à part. Avantage : vous pouvez mettre à jour le VEX sans rebuilder l’image ni régénérer le SBOM.
2. VEX intégré (CycloneDX avec section vulnerabilities)
Le VEX est dans le SBOM lui-même (CycloneDX 1.4+) :
grype sbom:./app-with-vex.cdx.json# Grype détecte automatiquement la section VEX, pas besoin de --vexCas d’usage : vous voulez distribuer un seul fichier (SBOM+VEX) pour simplifier. Utile pour les audits, les clients, les releases publiques. Inconvénient : si vous ajoutez une CVE au VEX, vous devez régénérer tout le SBOM.
3. Plusieurs sources VEX (cumul)
Vous pouvez fournir plusieurs fichiers VEX à Grype, qui les fusionnera :
grype nginx@sha256:4c0fdaa8b6341bfdeca5f18f7a2f0536b32447f805c90e1f2e79a6fa7e98f0b8 \ --vex ./vendor-vex.json \ --vex ./custom-vex.jsonCas d’usage : vous utilisez une image de base (Ubuntu, Alpine, Red Hat) qui
fournit son propre VEX vendor (vendor-vex.json), et vous ajoutez vos propres
statements (custom-vex.json) pour vos composants applicatifs.
Exemple concret :
vendor-vex.json: fourni par Red Hat, documente que CVE-2024-XXXX sursystemdn’affecte pas les conteneurs (systemd désactivé)custom-vex.json: votre équipe documente que CVE-2024-YYYY sur votre bibliothèquelibappest mitigée par un WAF
Grype applique les deux. Si un statement existe dans plusieurs VEX pour la même CVE, le plus spécifique (dernier fourni) gagne.
Distribuer les documents VEX
Section intitulée « Distribuer les documents VEX »Une fois vos VEX créés, vous devez les distribuer de façon sécurisée. Consultez le guide vexctl pour les workflows détaillés d’attachement avec Cosign, signature et distribution.
Méthodes de distribution
Section intitulée « Méthodes de distribution »Fichier artefact CI
Publication avec les releases GitHub :
app.vex.jsonSimple mais non signé
Attaché à l'image OCI
Avec Cosign (recommandé) :
app.vex.json mon-app:1.0.0Le VEX suit l’image dans la registry
Attestation signée
Pour la production :
cosign attest --predicate app.vex.json \ --type https://openvex.dev/ns/v0.2.0 \ mon-app:1.0.0Garantit l’authenticité du VEX
Automatiser VEX en CI/CD
Section intitulée « Automatiser VEX en CI/CD »Pour automatiser la création et l’application de VEX dans vos pipelines, consultez le guide vexctl - Section CI/CD qui propose des exemples complets pour GitHub Actions, GitLab CI et scripts bash.
Principe général :
- Build + SBOM : construire l’image et générer le SBOM
- Scan initial : détecter toutes les CVE avec Grype
- Génération VEX : créer VEX pour les faux positifs connus (script ou template)
- Re-scan avec VEX : filtrer les CVE non exploitables
- Fail/Pass : échouer la pipeline uniquement si CVE Critical/High réellement exploitables
- Distribution : pousser image + SBOM + VEX signés avec Cosign
Exemple GitHub Actions minimal
Section intitulée « Exemple GitHub Actions minimal »name: Scan with VEX
on: [push]
jobs: scan: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4
- name: Build image run: docker build -t mon-app:test .
- name: Generate SBOM uses: anchore/sbom-action@v0 with: image: mon-app:test format: cyclonedx-json output-file: sbom.json
- name: Create VEX for known false positives run: | DIGEST=$(docker inspect mon-app:test | jq -r '.[0].Id') vexctl create \ --product="pkg:oci/mon-app@${DIGEST}" \ --vuln="CVE-2024-1234" \ --status="not_affected" \ --justification="vulnerable_code_not_in_execute_path" \ --impact-statement="libxml2 non utilisé" \ > app.vex.json
- name: Scan with VEX uses: anchore/scan-action@v3 with: sbom: sbom.json vex: app.vex.json fail-build: true severity-cutoff: highPour des workflows plus avancés (multi-versions, VEX template, scripts d’auto-génération), voir le guide vexctl complet.
VEX et conformité réglementaire
Section intitulée « VEX et conformité réglementaire »Cyber Resilience Act (UE 2024)
Section intitulée « Cyber Resilience Act (UE 2024) »Le CRA impose de notifier les vulnérabilités exploitables sous 24h. VEX permet de documenter pourquoi certaines CVE ne nécessitent pas de notification :
Exemple : CVE-2024-1234 remontée par un scan automatique
- Sans VEX → notification obligatoire par précaution
- Avec VEX
not_affected→ documentation de non-exploitabilité, pas de notification
Directive NIS2 (UE 2024)
Section intitulée « Directive NIS2 (UE 2024) »NIS2 impose une gestion des risques fournisseurs. VEX aide à qualifier les vulnérabilités dans les composants tiers :
- Réception SBOM du fournisseur → scan → 50 CVE
- Application du VEX fourni → réduction à 3 CVE réellement exploitables
- Documentation de l’analyse pour audit
Executive Order US 14028
Section intitulée « Executive Order US 14028 »L’EO impose aux fournisseurs du gouvernement US de fournir SBOM + VEX. Le VEX doit documenter :
- Les CVE corrigées (statut
fixed) - Les CVE non exploitables (statut
not_affectedavec justification) - Les CVE en cours d’analyse (statut
under_investigationavec délai)
Bonnes pratiques
Section intitulée « Bonnes pratiques »1. Un VEX par version de produit
Section intitulée « 1. Un VEX par version de produit »mon-app-v1.2.0.vex.jsonmon-app-v1.2.1.vex.jsonmon-app-v1.3.0.vex.jsonChaque version d’image a son propre VEX. Ne réutilisez pas un VEX entre versions (les dépendances changent).
2. Documenter les justifications
Section intitulée « 2. Documenter les justifications »{ "status": "not_affected", "justification": "vulnerable_code_not_in_execute_path", "impact_statement": "libxml2 est présent dans Alpine 3.19 (image de base) mais notre application Go n'utilise aucune bibliothèque C. Le code vulnérable n'est jamais chargé en mémoire."}L’impact_statement est crucial pour les audits. Expliquez clairement le
raisonnement.
3. Réévaluer régulièrement
Section intitulée « 3. Réévaluer régulièrement »Un CVE marqué not_affected peut devenir affected si :
- Vous ajoutez une nouvelle fonctionnalité qui utilise le composant
- Une nouvelle variante d’exploit contourne les mitigations
- La configuration change (ouverture d’un port réseau…)
Recommandation : réviser les VEX à chaque changement majeur de l’application.
4. Centraliser dans Dependency-Track
Section intitulée « 4. Centraliser dans Dependency-Track »Dependency-Track supporte VEX nativement :
# Upload SBOM + VEXcurl -X POST https://dtrack.mon-org.com/api/v1/bom \ -H "X-Api-Key: $TOKEN" \ -H "Content-Type: multipart/form-data" \ -F "project=$PROJECT_UUID" \ -F "bom=@sbom-with-vex.cdx.json"
# Dependency-Track applique automatiquement le VEX# Les CVE not_affected sont marquées "suppressed"5. Signer les VEX en production
Section intitulée « 5. Signer les VEX en production »Un VEX non signé peut être falsifié. En production, signez avec Cosign :
# Signature keyless (OIDC GitHub)cosign sign-blob --bundle vex-signature.bundle app.vex.json
# Vérificationcosign verify-blob \ --bundle vex-signature.bundle \ --certificate-identity="https://github.com/mon-org/mon-app/.github/workflows/ci.yml@refs/heads/main" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ app.vex.jsonErreurs courantes
Section intitulée « Erreurs courantes »❌ Marquer toutes les CVE en not_affected
Section intitulée « ❌ Marquer toutes les CVE en not_affected »Symptôme : vous créez un VEX avec 200 CVE en not_affected sans analyse.
Problème : perte de confiance, audit impossible, risque réel non détecté.
Solution : analysez CVE par CVE. Si trop nombreuses, concentrez-vous sur critical/high uniquement.
❌ Ne pas versionner les VEX
Section intitulée « ❌ Ne pas versionner les VEX »Symptôme : un seul fichier app.vex.json pour toutes les versions.
Problème : CVE corrigée dans v1.3 mais VEX appliqué à v1.2 → fausse sécurité.
Solution : un VEX par digest d’image (pkg:oci/app@sha256:abc123...).
❌ Oublier d’appliquer le VEX dans les scans
Section intitulée « ❌ Oublier d’appliquer le VEX dans les scans »Symptôme : vous créez un VEX mais Grype continue d’afficher toutes les CVE.
Problème : le flag --vex n’est pas passé à Grype.
Solution : vérifiez dans les logs Grype : [INFO] VEX applied: X vulnerabilities suppressed.
Ressources
Section intitulée « Ressources »Guides sur ce site :
- SBOM — Le SBOM liste vos composants, VEX documente leur exploitabilité
- vexctl — CLI OpenVEX pour créer et manipuler des documents VEX
- Cosign — Signez vos VEX comme attestations liées à l’image
Spécifications :
Outils :
- vexctl — CLI OpenVEX (guide complet)
- Grype VEX support
- Dependency-Track VEX
Articles :
À retenir
Section intitulée « À retenir »3 points clés :
- VEX réduit le bruit : documentez les CVE non exploitables au lieu de les ignorer
- Intégration Grype :
grype --vex app.vex.jsonfiltre automatiquement les faux positifs - Conformité CRA/NIS2 : VEX est obligatoire pour justifier qu’une CVE n’est pas exploitable
Prochaines étapes :
- Scannez une de vos images avec Grype et identifiez 3-5 faux positifs
- Créez un VEX avec vexctl pour ces CVE
- Intégrez la génération de VEX dans votre pipeline CI/CD
- Distribuez vos VEX avec Cosign (signature + attestation)