Aller au contenu

VEX : réduire les faux positifs dans les scans de vulnérabilités

Mise à jour :

« 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 ?

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

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 :

Faux positifs courants résolus par VEX
VEX documente les faux positifs pour filtrer automatiquement les rapports de scan
  • 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 : libxml2 dans 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_php dé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é

Comparaison sans VEX vs avec VEX
Avec VEX, les équipes se concentrent sur les vraies vulnérabilités

VEX associe chaque CVE à un statut d’exploitabilité :

StatutExemple
not_affectedCVE-2025-12084 (xml.dom.minidom) dans API JSON-only : code jamais exécuté
affectedGHSA-6c5p-j8vq-pqhj (python-jose) : utilisé pour JWT auth, upgrade requis
fixedstarlette 0.41.3→0.49.1 : vulnérabilité résolue (commit abc123)
under_investigationCVE 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

  • 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.json applique vos décisions

Points forts : standardisé (OpenVEX, CycloneDX, CSAF), intégré (Grype, Trivy, Dependency-Track), signable (Cosign), versionnable.

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 :

Terminal window
-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 version

Formats VEX

Les 3 formats standards

FormatPorté parCas d’usageLien
OpenVEXChainguard, SigstoreDevSecOps, écosystème cloud-nativeopenvex.dev
CycloneDX VEXOWASPIntégré dans SBOM CycloneDX (v1.4+)cyclonedx.org
CSAF VEXOASISGrands é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

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 VEX
  • products : format purl (pkg:oci/nom@sha256:digest)
  • status : not_affected, affected, fixed, under_investigation
  • justification : code normalisé (scanners le comprennent)
  • impact_statement : explication humaine (auditeurs, collègues) cette CVE critical ?”.

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.

JustificationSignificationExemple concret
component_not_presentLe composant vulnérable n’existe pas dans le produitCVE sur postgresql-client mais vous utilisez MySQL : “Nous n’utilisons pas PostgreSQL, le package n’est pas installé.”
vulnerable_code_not_presentLe 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_pathLe 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_adversaryL’attaquant ne peut pas atteindre le codeCVE 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_existDes mitigations (WAF, sandbox) neutralisent l’exploitCVE 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 ?

  1. Le composant est-il installé ? Si non → component_not_present
  2. Le code vulnérable est-il présent ? Si patché/supprimé → vulnerable_code_not_present
  3. Le code peut-il être exécuté ? Si jamais appelé → vulnerable_code_not_in_execute_path
  4. Un attaquant peut-il y accéder ? Si bloqué réseau/firewall → vulnerable_code_cannot_be_controlled_by_adversary
  5. 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

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 :

  1. La liste des composants (section components)
  2. 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 du status OpenVEX (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 (comme impact_statement en 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

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.

  1. Générer le SBOM de votre image

    Terminal window
    # Générer SBOM au format CycloneDX JSON
    syft 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).

  2. Scanner les vulnérabilités avec Grype

    Terminal window
    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 ...
  3. 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
  4. Créer le VEX avec vexctl

    Terminal window
    # Récupérer le digest de l'image
    DIGEST=$(docker inspect devops-status-api:test | jq -r '.[0].Id')
    # sha256:0c4d2ee8...
    # Créer le VEX pour CVE-2025-12084
    vexctl 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
  5. Ajouter d’autres CVE au même VEX

    Pour documenter que GHSA-6c5p-j8vq-pqhj (python-jose) nécessite une action :

    Terminal window
    # vexctl peut merger des statements
    vexctl 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.json
    mv devops-status-api-updated.vex.json devops-status-api.vex.json
  6. Rescanner avec le VEX appliqué

    Terminal window
    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

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%

Scan sans VEX : l’alert fatigue en action

Voici ce qui se passe quand vous scannez une image classique sans VEX :

Terminal window
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 autres

Problè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

Maintenant, même scan avec un fichier VEX :

Terminal window
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é :

  1. Grype a lu le fichier devops-status-api.vex.json
  2. Pour chaque CVE détectée, il a vérifié si un statement VEX existe
  3. Pour CVE-2025-12084 : statement trouvé avec status: not_affected → CVE supprimée de l’output
  4. 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

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 :

Terminal window
grype sbom:./app.sbom.json --vex ./app.vex.json

Cas 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+) :

Terminal window
grype sbom:./app-with-vex.cdx.json
# Grype détecte automatiquement la section VEX, pas besoin de --vex

Cas 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 :

Terminal window
grype nginx@sha256:4c0fdaa8b6341bfdeca5f18f7a2f0536b32447f805c90e1f2e79a6fa7e98f0b8 \
--vex ./vendor-vex.json \
--vex ./custom-vex.json

Cas 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 sur systemd n’affecte pas les conteneurs (systemd désactivé)
  • custom-vex.json : votre équipe documente que CVE-2024-YYYY sur votre bibliothèque libapp est 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

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

Fichier artefact CI

Publication avec les releases GitHub :

Terminal window
app.vex.json

Simple mais non signé

Attaché à l'image OCI

Avec Cosign (recommandé) :

Terminal window
app.vex.json mon-app:1.0.0

Le VEX suit l’image dans la registry

Attestation signée

Pour la production :

Terminal window
cosign attest --predicate app.vex.json \
--type https://openvex.dev/ns/v0.2.0 \
mon-app:1.0.0

Garantit l’authenticité du VEX

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 :

  1. Build + SBOM : construire l’image et générer le SBOM
  2. Scan initial : détecter toutes les CVE avec Grype
  3. Génération VEX : créer VEX pour les faux positifs connus (script ou template)
  4. Re-scan avec VEX : filtrer les CVE non exploitables
  5. Fail/Pass : échouer la pipeline uniquement si CVE Critical/High réellement exploitables
  6. Distribution : pousser image + SBOM + VEX signés avec Cosign

Exemple GitHub Actions minimal

name: Scan with VEX
on: [push]
jobs:
scan:
runs-on: ubuntu-latest
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: high

Pour 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

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

Guide CRA complet

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

Guide NIS2 complet

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_affected avec justification)
  • Les CVE en cours d’analyse (statut under_investigation avec délai)

Bonnes pratiques

1. Un VEX par version de produit

mon-app-v1.2.0.vex.json
mon-app-v1.2.1.vex.json
mon-app-v1.3.0.vex.json

Chaque version d’image a son propre VEX. Ne réutilisez pas un VEX entre versions (les dépendances changent).

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

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

Dependency-Track supporte VEX nativement :

Terminal window
# Upload SBOM + VEX
curl -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

Un VEX non signé peut être falsifié. En production, signez avec Cosign :

Terminal window
# Signature keyless (OIDC GitHub)
cosign sign-blob --bundle vex-signature.bundle app.vex.json
# Vérification
cosign 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.json

Erreurs courantes

❌ 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

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

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

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 :

Articles :

À retenir

3 points clés :

  1. VEX réduit le bruit : documentez les CVE non exploitables au lieu de les ignorer
  2. Intégration Grype : grype --vex app.vex.json filtre automatiquement les faux positifs
  3. 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)