ADR 020 â Vuln management : VEX pour l'inexploitable, risk acceptance pour l'exploitable acceptĂ© (2026-06-19)
Statut
Accepté (2026-06-19) et validé hors infra (scan Trivy local : la CVE migrée en
VEX est bien neutralisée par --vex, le gate reste vert). Premier ADR du projet
transverse de gestion continue des vulnérabilités.
Le comment (pédagogique) est dans le guide Comprendre le vuln management.
Contexte
Le scan de sécurité est aujourd'hui build-time : Trivy (fs + image) bloque au
build les CVE HIGH/CRITICAL fixables. Quand une CVE est fixée upstream mais qu'on
ne peut pas la patcher (le correctif casserait une dépendance), on l'a jusqu'ici mise
dans .trivyignore (cas vécu #95/#96 : deux CVE starlette).
Limite de cette approche : .trivyignore mélange deux situations trÚs différentes
sous une mĂȘme « liste Ă taire ».
- Une CVE peut ĂȘtre prĂ©sente mais inexploitable (le code vulnĂ©rable n'est jamais atteint par l'application). La taire est lĂ©gitime, mais en commentaire libre, sans format standard ni traçabilitĂ©.
- Une CVE peut ĂȘtre rĂ©ellement atteignable, et on choisit d'accepter le risque (impact limitĂ©, pas de correctif applicable). La taire « comme les autres » masque le fait qu'on porte un vrai risque.
Mettre les deux dans le mĂȘme fichier, avec la mĂȘme mĂ©canique, empĂȘche de distinguer « je prouve que je ne suis pas concernĂ© » de « je suis concernĂ© mais j'assume ».
DĂ©cision 1 â VEX (OpenVEX) pour les CVE non exploitables
On déclare les CVE non exploitables dans un document VEX (Vulnerability
Exploitability eXchange) au format OpenVEX, .vex/openvex.json, consommé par
Trivy via --vex.
- Statut
not_affected+ justification dans un vocabulaire fermé, machine-lisible (ex.vulnerable_code_not_in_execute_path). - Premier cas : CVE-2026-48818 (
starlette, SSRF/NTLM viaStaticFiles). L'application n'enregistre aucunStaticFileset n'appelle aucunmount()(vĂ©rifiĂ© par revue de code) : le code vulnĂ©rable n'est jamais dans le chemin d'exĂ©cution ânot_affected/vulnerable_code_not_in_execute_path. - Format standard et interopĂ©rable (rĂ©utilisable par d'autres scanners), avec
timestamp(date de la dĂ©claration) â auditabilitĂ©.
Alternative écartée : garder ces CVE dans .trivyignore. Rejeté car non standard,
justification en commentaire libre, et surtout : confond inexploitabilité prouvée et
risque accepté.
DĂ©cision 2 â Risk acceptance documentĂ©e pour l'exploitable acceptĂ©
Une CVE atteignable mais dont on accepte le risque reste dans .trivyignore,
mais explicitement étiquetée « risque accepté » avec :
- la preuve d'atteignabilitĂ© (oĂč le code vulnĂ©rable est appelĂ©),
- l'analyse d'impact (pourquoi le risque est tolérable),
- une date de revue et le lien vers l'issue de dette.
Cas : CVE-2026-54283 (starlette, DoS via request.form()). Le /login utilise
OAuth2PasswordRequestForm (confirmé app/routers/auth.py), donc le code est
atteignable. Impact limité (DoS CPU sur un endpoint, ni fuite ni RCE). Risque accepté,
revue au 2026-09-19 ou au déblocage de #96.
On n'utilise pas un VEX not_affected pour ce cas : ce serait déclarer faussement
une inexploitabilité. VEX = exploitabilité, pas acceptation de risque.
Conséquences
.trivyignorene contient plus que : (1) CVE OS Debian unfixed (documentation, déjà masquées par--ignore-unfixed) et (2) risques acceptés exploitables, datés.- Les CVE non exploitables vivent dans
.vex/openvex.json, déclaration standard. - Les 3 gates Trivy (
fs-scan+ les 2 invocationsimage-scan) consomment--vex. Le SBOM CycloneDX (#68) reste exhaustif (inventaire, pas un gate). - Process réutilisable par les autres clusters (homelab, platform) : le découpage
VEX / risk acceptance est indépendant d'AWS. Suivi workspace
projects/vuln-mgmt/.
CritĂšres de validation
- [x] Scan local sans
--vex: CVE-2026-48818 réapparaßt (preuve qu'elle n'est plus dans.trivyignore). - [x] Scan local avec
--vex: CVE-2026-48818 supprimée, CVE-2026-54283 masquée par.trivyignore, gatefs-scanvert (exit 0). - [x]
glab ci lintvalide le pipeline modifié. - [ ] Pipeline MR +
developverts (gateimage-scanavec--vexsur l'image réelle).
Mise Ă jour (2026-06-30) â dette #96 levĂ©e
Les deux CVE starlette qui motivaient ces exceptions sont corrigées par le bump
starlette 1.3.1 (avec fastapi 0.138 + prometheus-fastapi-instrumentator 8.0.2),
proposé par Renovate (Phase 2 du vuln management) et validé par la CI : le test
d'intégration tests/test_metrics.py prouve que /metrics fonctionne toujours, ce qui
invalide l'hypothÚse bloquante d'origine. Un scan Trivy fs sans filtre ne détecte
plus CVE-2026-48818 ni CVE-2026-54283.
En conséquence : le statement VEX CVE-2026-48818 et l'entrée .trivyignore
CVE-2026-54283 sont retirés (exceptions devenues mortes) et l'issue #96 est
fermée. Le pattern VEX reste cùblé (.vex/openvex.json à statements vides, flags
--vex en place) pour resservir. Leçon : une remédiation se termine par le nettoyage
des exceptions, sinon on laisse des angles morts qui masqueraient de futures CVE.