SCA : de l'inventaire à la priorité
Lancez deux outils SCA sur le même projet : ils donneront rarement la même liste.
Pour comprendre pourquoi, on suit une application fictive à travers cinq niveaux d'analyse, puis on ouvre le capot : arbre de dépendances, comparaison de versions, atteignabilité, VEX. L'article prolonge Le SCA expliqué simplement, mais se lit seul.
L'application d'exemple
Voici une application fictive, « CommandeFacile », un site de commande en ligne écrit en Java. Ses développeurs ont déclaré 6 dépendances directes ; une fois l'arbre complet reconstitué, elle en contient 38, dont 32 indirectes.
Quatre correspondances sont signalées, dont l'homonyme C, erroné. Tout est inventé ici, y compris les CVE, pour illustrer le raisonnement. Un bon outil doit comprendre que ces quatre cas ne demandent pas la même réaction :
2 vraies expositionsLe composant est bien là et sa partie fautive sert
La bibliothèque de journal (directe). Le site lui transmet des textes saisis par les visiteurs. Sa CVE figure au catalogue KEV.
Une bibliothèque de lecture de fichiers XMLXML : un format de fichier texte structuré par des balises (indirecte). Le site s'en sert, mais seulement la nuit, pour des fichiers de partenaires connus, sur un serveur non accessible depuis Internet.
2 piègesDeux cas à vérifier avant de les écarter
Une bibliothèque de traitement d'images (indirecte). La fonctionfonction : un morceau de code qui fait une tâche précise, appelé par le reste du programme fautive existe, mais aucun appel n'a été trouvé dans le périmètre analysé.
Une bibliothèque de dates (indirecte). Un outil qui cherche par nom de produit la confond avec un autre produit du même nom, qui, lui, a une CVE.
Avant le détail niveau par niveau, voici l'arbre complet. Cliquez sur les niveaux et regardez les alertes se trier :
Cette animation a besoin de JavaScript. En résumé : 38 composants, 4 alertes initiales.
Niveau 1 : l'inventaire seul
38 composants listés0 réponse sur le risque
Le premier niveau reconstitue la liste, avec les versions exactes, directes et indirectes : c'est la liste de composition du logiciel, ou SBOMSBOM : Software Bill of Materials : la liste de courses du développeur, avec chaque composant et sa version. Elle s'écrit en général dans un format standard, par exemple CycloneDX (norme ECMA-424) ou SPDX (norme ISO/IEC 5962).
C'est indispensable, et ce n'est pas encore de l'analyse de risque. Quand une faille comme Log4Shell tombe, on peut au moins répondre à « sommes-nous concernés ? » sans fouiller chaque projet à la main.
Niveau 2 : la correspondance avec les CVE, par nom
4 alertes1 mauvaise identification
Le deuxième niveau compare chaque composant aux bases de vulnérabilités. Sur notre exemple, A, B, C et D ressortent, mais C est une erreur.
La difficulté vient de la façon d'identifier un composant. Certains outils reconstituent un nom de produit générique à partir de ce qu'ils trouvent dans les fichiers, puis le cherchent dans la base de vulnérabilités du gouvernement américain (la NVDNVD : National Vulnerability Database : la base américaine qui décrit les CVE). OWASPOWASP : une fondation qui publie des guides et des outils libres de sécurité applicative Dependency-Check, par exemple, décrit cette démarche : il collecte des indices pour déterminer un identifiant de produit (CPECPE : un identifiant de produit utilisé par la NVD pour dire quels logiciels une faille touche), puis cherche les CVECVE : Common Vulnerabilities and Exposures : le numéro unique d'une faille connue, comme CVE-2021-44228 associées. Quand deux produits portent un nom proche, la confusion est possible, et le rapprochement entre un paquet et un CPE reste approximatif.
À retenir :une alerte d'identification fausse n'est pas un détail.
Elle coûte une enquête, et elle apprend à l'équipe à ne plus croire l'outil.
Niveau 3 : la correspondance par version et par écosystème
3 alertesC écartée
D'autres sources fonctionnent autrement : elles publient les vulnérabilités directement par gestionnaire de paquetsgestionnaire de paquets : l'outil qui installe les briques d'un projet, comme npm ou Maven (Maven pour Java, npm pour JavaScript, PyPI pour Python) et par plage de versions. La base ouverte OSVOSV : Open Source Vulnerabilities : une base publique de vulnérabilités, classées par écosystème agrège ainsi les vulnérabilités publiées par GitHub, PyPA ou RustSec, réparties sur plus de 50 écosystèmes, avec un format commun qui relie chaque vulnérabilité à des versions précises.
La différence tient à l'identifiant. Le CPE décrit un produit (« log4j, de chez apache »), choisi par les analystes de la NVD. Le purl (package URL) décrit un paquet, tel que le gestionnaire de paquets le connaît : l'écosystème, le nom exact, la version.
CPE cpe:2.3:a:apache:log4j:2.14.1:*:*:*:*:*:*:*
purl pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1
Sur notre exemple, la correspondance est ici exacte : C disparaît, car identifiée par son écosystème et son nom exact de paquet, la bibliothèque de dates n'est pas le produit visé par la CVE. Les versions de A, B et D restent dans les plages vulnérables.
Aucune base n'est pour autant complète, et deux bases couvrent rarement les mêmes vulnérabilités au même moment. Début 2024, la NVD a pris un retard massif dans l'enrichissement des CVE : des milliers de failles publiées sans les CPE qui disent quels produits elles touchent. Un outil qui ne vivait que de la NVD est devenu aveugle sur une partie des nouvelles failles, sans forcément le dire.
À retenir :demandez toujours à un outil d'où viennent ses données de vulnérabilités. Un bon outil croise plusieurs sources et dit, pour chaque alerte, laquelle l'a déclenchée.
Niveau 4 : l'atteignabilité
2 alertes prioritairesB en priorité basse
Un composant est un outil avec beaucoup de gestes possibles. La CVE de B concerne un seul de ces gestes dans la bibliothèque d'images, et l'analyse ne trouve aucun chemin d'appel vers cette fonction, directement ou par une autre brique, dans le périmètre étudié.
Le code de l'application
Appelle des fonctions de ses dépendances.
La fonction fautive
Est-elle sur un chemin d'appel qui part de l'application ?
Pour le savoir, l'outil dessine une carte des appels (qui appelle qui) et vérifie si un chemin mène à la fonction vulnérable. Google présente cette idée dans OSV-Scanner sous le nom d'analyse d'appels.
Encore faut-il savoir quelle fonction est vulnérable (via l'avis, une base enrichie ou le correctif) et prendre en charge le langage. L'exemple le plus abouti est Go : sa base de vulnérabilités indique les fonctions touchées, et l'outil officiel govulncheck ne remonte une faille que si votre code peut appeler l'une d'elles. Ailleurs, c'est plus fragile : en Java, la réflexion et l'injection de dépendances cachent une partie des appels ; en JavaScript, la nature dynamique du langage fait le reste.
Elle comporte aussi un risque : les appels indirects, par réflexion ou par configuration, sont difficiles à suivre, et un outil qui conclut trop vite « non atteignableatteignable : se dit d'une faille dont l'application peut réellement atteindre la partie fautive » peut écarter une vraie faille. Une priorité basse exige donc de vérifier la couverture de l'analyse et la configuration déployée. Si la fonction ou le langage n'est pas pris en charge, le résultat reste inconnu : l'absence de chemin trouvé ne suffit pas à supprimer l'alerte.
Niveau 5 : le contexte et les signaux d'exploitation
1 urgente, 1 planifiéeA en tête
Restent A et D, toutes deux réellement présentes et appelées. Le dernier niveau les classe avec des signaux extérieurs et le contexte de l'application :
| Signal | Ce qu'il dit | Ce qu'il ne dit pas |
|---|---|---|
| Note de gravité de base (CVSS) | La gravité théorique de la faille | Si elle est exploitée, ni si elle vous touche : la note de base (CVSS-B) ignore votre environnement, que CVSS 4.0 (2023) traite à part |
| KEV (catalogue de la CISA) | Des attaques réelles ont été constatées | Si votre configuration est visée, ni toutes les exploitations du monde |
| EPSS (FIRST) | Une probabilité d'exploitation constatée dans le monde sous 30 jours, mise à jour chaque jour (0,02 = 2 %), à ne pas confondre avec le percentile | Un fait : c'est une estimation statistique |
| Exposition | Le composant est-il joignable depuis Internet ? | La gravité de la faille elle-même |
Dans notre exemple, A cumule tout : composant direct, fonction appelée avec une saisie de visiteur, serveur ouvert à Internet, CVE au KEV. D est réelle mais enfermée derrière un réseau interne, avec des fichiers de partenaires connus. Elle reste à corriger, avec une échéance écrite (par exemple la prochaine mise à jour planifiée), après avoir vérifié que ce réseau et ces fichiers sont réellement maîtrisés.
À retenir :aucun de ces signaux ne remplace votre contexte. Une faille au KEV dans une fonction que vous n'appelez pas peut peser moins qu'une faille moyenne branchée sur votre formulaire de connexion.
Sous le capot : reconstruire l'arbre
Tout part de l'inventaire, et l'inventaire est plus sournois qu'il n'y paraît. Un projet déclare ses envies dans un manifeste (package.json, pom.xml, pyproject.toml), souvent sous forme de plages : ^4.17.0 veut dire « n'importe quelle 4.x à partir de 4.17.0 ».
Les versions réellement retenues sont écrites dans le fichier de verrouillagefichier de verrouillage : un fichier qui fige la version exacte de chaque brique installée (package-lock.json, poetry.lock…), et ce qui finit dans l'application livrée peut encore différer (des dépendances de développement exclues, par exemple). Avec le seul manifeste, sans résolution ni inventaire réel, l'outil devine, et il devine souvent mal.
Ensuite, chaque écosystème résout les conflits à sa façon, et ça change ce qui finit dans l'application :
| Écosystème | Quand deux briques veulent deux versions | Conséquence pour le SCA |
|---|---|---|
| npm | Les deux versions peuvent cohabiter, chacune dans son sous-dossier | Une même brique peut apparaître trois fois, en trois versions : il faut les voir toutes |
| Maven | « Le plus proche gagne » : la déclaration la plus proche du projet l'emporte, sauf si une section dependencyManagement impose une version | La version déclarée au fond de l'arbre n'est pas forcément celle qui tourne |
| Go | Sélection de version minimale : la plus petite version qui satisfait tout le monde | Résultat reproductible, que l'outil peut recalculer exactement |
| Python (pip) | Une seule version par environnement, choisie à l'installation | Sans fichier de verrouillage, deux installations du même projet peuvent différer |
Et il y a les passagers clandestins. Une brique peut embarquer une copie d'une autre à l'intérieur de son propre paquet, parfois même renommée (les Javaistes parlent de fat JAR et de shading). Elle peut alors disparaître du graphe de dépendances publié.
Pendant Log4Shell, beaucoup d'équipes ont découvert du Log4j caché ainsi : les outils qui inspectaient le contenu des archives retrouvaient ces copies, ceux qui ne lisaient que les manifestes passaient à côté.
Enfin, une image de conteneur contient aussi les paquets du système (Debian, Alpine, Red Hat…), lus dans les bases du gestionnaire de paquets du système. Et là, nouveau piège : les distributions corrigent souvent une faille sans changer le numéro de version d'origine (un backport).
Un outil qui ne lit pas les bulletins de sécurité de la distribution criera au loup sur un paquet déjà corrigé.
À retenir :deux outils qui ne reconstruisent pas le même arbre ne peuvent pas donner le même rapport. Avant de comparer les alertes, comparez les inventaires.
Comparer deux versions, ce n'est pas comparer deux textes
Question piège : 1.10 est-il plus grand que 1.9 ? Pour un humain, oui.
Pour un ordinateur qui compare des textes caractère par caractère, non : après « 1. », il compare « 1 » et « 9 », et conclut que 1.10 est plus petit que 1.9.
Félicitations, votre outil maison vient de déclarer vulnérable une version corrigée.
Et chaque écosystème a ses propres règles :
- Python (PEP 440) :
1.0rc1passe avant1.0, et1.0.post1après. - Maven :
1.0-alpha<1.0-beta<1.0, avec une liste de mots réservés. - Debian :
1:2.3-4, où le1:(« l'époque ») écrase tout le reste. - npm : le versionnage sémantique, avec ses pré-versions
2.0.0-beta.3.
C'est pour ça que le format OSV décrit une plage touchée par des événements (« introduite en », « corrigée en ») et précise quelle règle de comparaison appliquer : versionnage sémantique, règles de l'écosystème, ou historique Git.
VEX : écrire ses décisions dans un format que les machines lisent
Quand vous baissez la priorité de B ou repoussez D, la raison doit survivre à votre mémoire, et idéalement voyager avec votre logiciel. C'est le rôle du VEXVEX : un document qui dit, pour une faille connue, si un produit est réellement touché.
Pour chaque couple « produit, faille », il donne un statut : touché, non touché, corrigé ou en cours d'analyse. Et un « non touché » doit être justifié, par exemple :
{
"vulnerability": { "name": "CVE-XXXX-YYYY" },
"products": [ { "@id": "pkg:maven/com.exemple/commandefacile@3.2.0" } ],
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path"
}
Ce fragment, au format OpenVEX (CSAF et CycloneDX en ont aussi un), dit en substance : « la brique fautive est là, mais le code vulnérable n'est jamais exécuté ». Pour B, ce statut exige une justification vérifiée pour le produit et sa version. Reporter D le laisse « touché », avec échéance et mesures suivies.
Ce fragment n'est pas un document complet : pour l'échanger, il faut aussi les métadonnées requises par OpenVEX, dont l'auteur, la date et la version du document.
Et la loi, dans tout ça ?
Pour les produits relevant du Cyber Resilience Act sur le marché européen, les fabricants devront documenter les composants, notamment par un SBOM, dès le 11 décembre 2027 ; les notifications des vulnérabilités activement exploitées et incidents graves s'appliquent depuis le 11 septembre 2026, avec alerte sous 24 h puis notification sous 72 h après prise de connaissance.
Pour conclure
Entre une liste de CVE et une liste de risques, il y a un inventaire exact, une correspondance par écosystème et par version plutôt que par nom, une atteignabilité vérifiée et le contexte d'exposition. Sur CommandeFacile, quatre alertes deviennent une urgence, une correction planifiée, une priorité basse et une erreur d'identification écartée. Avant de comparer deux outils, comparez leurs inventaires, puis la qualité de leur tri, et gardez chaque décision par écrit.
Les mots de cet article · 13 mots
- SBOM
- La liste des composants d'un logiciel, avec leurs versions.
- CycloneDX, SPDX
- Deux formats standard de SBOM.
- CVE
- Le numéro unique attribué à une faille connue, comme CVE-2021-44228.
- CPE
- Un identifiant générique de produit, utilisé pour relier un composant à des CVE.
- NVD
- La base de vulnérabilités du gouvernement américain, tenue par le NIST.
- OSV
- Une base ouverte de vulnérabilités, organisée par écosystème et par versions.
- Fichier de verrouillage
- Le fichier qui fige les versions exactes de toutes les dépendances.
- Atteignabilité
- La possibilité d'atteindre la partie fautive d'un composant par un chemin d'appel, dans le périmètre analysé.
- KEV
- Le catalogue de la CISA des failles dont l'exploitation est constatée.
- EPSS
- Une estimation de 0 à 1 de la probabilité d'exploitation sous 30 jours.
- VEX
- Vulnerability Exploitability eXchange : un document qui dit si un produit est touché par une faille connue.
- purl
- Package URL : un identifiant de paquet qui précise l'écosystème, le nom exact et la version.
- Backport
- Une correction appliquée par une distribution sans changer le numéro de version d'origine.
Pour choisir un outil (outils du marché, questions à poser, essais à faire) : Choisir un SCA sans se faire avoir. Pour revoir les bases : Le SCA expliqué simplement. Et pour le code que l'équipe écrit elle-même : Le SAST en profondeur.
Lire ensuite : Choisir un SCA sans se faire avoir