SCA

Enrichissement CPE pour les bibliothèques C++ tierces

Combler le manque de CPE et de PURL dans les builds Conan pour que les vulnérabilités de vos dépendances C++ soient bien rapprochées de la NVD.

L’analyse de la composition logicielle (SCA) fonctionne bien pour npm, Maven ou PyPI. Pour C et C++, elle est à la traîne depuis des années. Les bibliothèques étaient copiées dans l’arborescence des sources, installées depuis le système d’exploitation ou compilées avec des scripts maison, et rien n’enregistrait quelles versions finissaient dans un produit.

Conan a changé cela. Il donne aux projets C et C++ un graphe de dépendances résolu et versionné, et donc la base d’une vraie SBOM. Mais il reste un écart entre savoir quelles bibliothèques vous utilisez et être alerté quand l’une d’elles a une vulnérabilité. Dans la plupart des pipelines Conan que j’ai vus, cet écart est l’identifiant CPE manquant. La SBOM existe, le scanner de vulnérabilités tourne, le tableau de bord est vert, et les CVE de votre version d’OpenSSL ou de libcurl n’apparaissent jamais.

Cet article explique pourquoi et comment corriger le problème avec une petite étape d’enrichissement facile à maintenir.

Générer la SBOM à partir du graphe de build

Pour le C++, la SBOM doit provenir du graphe de dépendances que Conan résout au moment du build. C’est l’enregistrement de référence de ce qui est entré dans le build : chaque dépendance directe et transitive, avec sa version exacte.

L’analyse binaire ne la remplace pas. Elle reconnaît des chaînes de version, des empreintes de fichiers ou des motifs d’octets dans le code compilé, et les builds de release C++ la mettent en échec de plusieurs façons :

  • Les builds de release sont généralement dépouillés de leurs symboles, il n’y a donc rien sur quoi s’appuyer.
  • L’édition de liens statique fusionne le code des bibliothèques dans votre binaire, et les frontières entre composants disparaissent.
  • Les bibliothèques compilées avec des correctifs ou des options de compilation inhabituelles ne ressemblent plus aux binaires de référence que le scanner connaît.

L’analyse binaire reste une deuxième couche utile pour le code qui contourne Conan, comme les sources copiées ou les bibliothèques système. Mais le graphe de build est votre source de vérité.

Deux outils peuvent produire la SBOM :

  • cdxgen lit conan.lock (Conan 1 et 2) ou conanfile.txt et écrit une SBOM CycloneDX. Il ne lit pas les dépendances déclarées uniquement dans conanfile.py : créez donc un fichier de verrouillage avec conan lock create et versionnez-le.
  • Conan 2 dispose de ses propres outils SBOM expérimentaux, cyclonedx_1_4 et cyclonedx_1_6, que vous appelez depuis un hook. Ils travaillent sur le graphe complet sans fichier de verrouillage, mais la fonctionnalité est encore signalée comme susceptible de changements incompatibles.

Le manque : pas de CPE

Voici ce que cdxgen produit aujourd’hui pour un composant Conan :

{
  "type": "library",
  "name": "openssl",
  "version": "3.3.2",
  "purl": "pkg:conan/[email protected]"
}

Il y a une Package URL (PURL), mais pas de CPE. C’est important, car les bases de vulnérabilités ne recherchent pas les composants par leur nom. Chaque source utilise son propre identifiant :

Source de vulnérabilitésRapprochement parCouvre les paquets Conan ?
NIST National Vulnerability Database (NVD)CPEOui, via la CPE. C'est là que se trouvent la plupart des CVE C et C++.
OSVPURLEn partie, via son écosystème ConanCenter
Sonatype OSS IndexPURLEn partie, pour les paquets qu'il connaît
GitHub Advisory DatabasePURLNon. L'écosystème Conan n'est pas couvert.

Pour la plupart des autres écosystèmes, une PURL vous donne une bonne couverture. Pour le C++, les vulnérabilités de bibliothèques comme OpenSSL, zlib, libcurl ou libxml2 sont enregistrées dans la NVD sous des identifiants CPE, et les sources fondées sur la PURL n’en couvrent qu’une partie. Sans CPE, votre SBOM ne peut pas être rapprochée de la source qui compte le plus.

OWASP Dependency-Track le montre clairement. Il rapproche les données de la NVD de la CPE présente dans votre SBOM. La version 4 avait une option de correspondance approximative qui tentait de deviner les CPE à partir des noms de composants. Dependency-Track 5 l’a supprimée : un composant sans CPE n’obtient donc aucun résultat de la NVD. Si vous utilisez Dependency-Track, notre guide pour déployer Dependency-Track 5 avec Docker Compose décrit l’installation.

L’échec est silencieux. L’analyse se termine, aucune vulnérabilité n’est signalée et le tableau de bord paraît propre. Non pas parce que vos dépendances sont sûres, mais parce que l’outil n’a pas pu les rechercher.

Pourquoi la CPE ne peut pas être générée automatiquement

Un identifiant CPE 2.3 pour une bibliothèque ressemble à ceci :

cpe:2.3:a:<vendor>:<product>:<version>:*:*:*:*:*:*:*

Le a signifie application. Les noms de fournisseur et de produit viennent du dictionnaire CPE de la NVD, que le NIST construit indépendamment de tout registre de paquets. Beaucoup de ces noms ont été fixés des années avant Conan, et ils correspondent souvent mal au nom du paquet Conan :

Paquet ConanFournisseur NVDProduit NVD
opensslopensslopenssl
expatlibexpatexpat
libcurlhaxxlibcurl
libxml2xmlsoftlibxml2
libjpeg-turbolibjpeg-turbo, et d.r.commander pour les anciennes versionslibjpeg-turbo

Aucune règle ne mène de libcurl à haxx. Et une recherche par mot-clé dans le dictionnaire renvoie aussi des paquets de distribution : chercher libpng renvoie d’abord les entrées redhat:libpng de Red Hat, et non le projet amont libpng:libpng. La seule méthode fiable est qu’une personne consulte une fois par bibliothèque le dictionnaire CPE de la NVD, regarde quels fournisseur et produit utilisent les CVE récentes, et consigne le résultat.

C’est aussi pourquoi les générateurs de SBOM ne le font pas. Pour npm ou Maven, une correspondance heuristique fonctionne assez bien. Pour le C++, elle produirait de mauvaises CPE, et une mauvaise CPE est pire qu’aucune : elle échoue tout aussi silencieusement.

Deux autres points à connaître sur les CPE :

  • Une bibliothèque sans historique de CVE n’a peut-être pas encore de CPE. Le NIST crée surtout des entrées CPE en analysant des CVE. Ce n’est pas grave : il n’y a rien à rapprocher. Mais la correspondance doit être revue dès que la première CVE apparaît.
  • La NVD enrichit moins de CVE. Depuis avril 2026, le NIST ajoute des données CPE surtout aux CVE du catalogue Known Exploited Vulnerabilities de la CISA et aux logiciels utilisés par l’administration fédérale américaine ; les autres CVE sont marquées “Not Scheduled” et risquent de ne jamais recevoir de données CPE. Le rapprochement par CPE reste indispensable pour le C++, mais il ne suffit plus à lui seul. C’est pourquoi vous ajoutez aussi des PURL et plus d’une source de vulnérabilités.

La solution : des métadonnées CPE maintenues et une étape de post-traitement

L’approche comporte deux parties :

  1. Consigner une fois, pour chaque bibliothèque tierce, le fournisseur et le produit CPE vérifiés.
  2. Après la génération de la SBOM, ajouter la CPE à chaque composant, ainsi qu’une PURL si le générateur n’en a pas mis.

Ne stockez que le fournisseur et le produit, jamais la chaîne CPE complète. Le post-traitement ajoute la version tirée de la SBOM. Quand vous passez d’OpenSSL 3.3.2 à 3.4.1, rien ne change dans vos métadonnées CPE, et la CPE ne peut pas se désynchroniser de la version que vous livrez réellement.

Où conserver les métadonnées

Si vous maintenez vos propres recettes Conan, placez les métadonnées dans la recette. Elle contient déjà le nom, la page d’accueil et la licence du composant, et la CPE y a sa place :

class OpensslConan(ConanFile):
    name = "openssl"
    homepage = "https://www.openssl.org"
    license = "Apache-2.0"

    # Verified against the NVD CPE dictionary
    cpe_vendor = "openssl"
    cpe_product = "openssl"

Conan ignore les attributs qu’il ne connaît pas, le build n’est donc pas affecté. Un court script peut collecter les valeurs de toutes les recettes sans les exécuter :

"""Collect cpe_vendor/cpe_product from Conan recipes without executing them."""
import ast
import json
import pathlib
import sys

def recipe_metadata(path):
    tree = ast.parse(path.read_text(encoding="utf-8"))
    for node in tree.body:
        if isinstance(node, ast.ClassDef):
            values = {}
            for stmt in node.body:
                if isinstance(stmt, ast.Assign) and isinstance(stmt.value, ast.Constant):
                    for target in stmt.targets:
                        if isinstance(target, ast.Name):
                            values[target.id] = stmt.value.value
            if "name" in values:
                return values
    return None

mapping = {}
for recipe in pathlib.Path(sys.argv[1]).rglob("conanfile.py"):
    meta = recipe_metadata(recipe)
    if meta and meta.get("cpe_vendor") and meta.get("cpe_product"):
        mapping[meta["name"]] = {"cpe_vendor": meta["cpe_vendor"], "cpe_product": meta["cpe_product"]}

json.dump(mapping, sys.stdout, indent=2, sort_keys=True)

Si vous consommez les recettes directement depuis ConanCenter, vous ne pouvez pas y ajouter d’attributs. Tenez plutôt un fichier de correspondance dans un dépôt, relu comme du code :

{
  "expat":   { "cpe_vendor": "libexpat", "cpe_product": "expat" },
  "libcurl": { "cpe_vendor": "haxx",     "cpe_product": "libcurl" },
  "libxml2": { "cpe_vendor": "xmlsoft",  "cpe_product": "libxml2" }
}

Si vous passez par un remote privé qui sert de proxy à ConanCenter, par exemple dans Artifactory, maintenez un fichier de correspondance central pour tout ce qui y transite, afin que chaque projet en profite. Les configurations mixtes fonctionnent aussi : fusionnez les valeurs de vos propres recettes avec le fichier de correspondance des paquets publics.

Le post-traitement

L’étape d’enrichissement s’exécute juste après la génération de la SBOM et avant tout envoi :

"""Add CPE and Conan PURL identifiers to a CycloneDX SBOM."""
import json
import sys

sbom_path, map_path, out_path = sys.argv[1:4]
sbom = json.load(open(sbom_path, encoding="utf-8"))
cpe_map = json.load(open(map_path, encoding="utf-8"))

missing = []
for comp in sbom.get("components", []):
    name, version = comp.get("name"), comp.get("version")
    if not name or not version:
        continue
    comp.setdefault("purl", f"pkg:conan/{name}@{version}")
    entry = cpe_map.get(name)
    if entry:
        comp["cpe"] = f"cpe:2.3:a:{entry['cpe_vendor']}:{entry['cpe_product']}:{version}:*:*:*:*:*:*:*"
    elif "cpe" not in comp:
        missing.append(f"{name}@{version}")

json.dump(sbom, open(out_path, "w", encoding="utf-8"), indent=2)

for item in missing:
    print(f"WARNING: no CPE mapping for {item}", file=sys.stderr)

Après enrichissement, le composant ressemble à ceci :

{
  "type": "library",
  "name": "openssl",
  "version": "3.3.2",
  "purl": "pkg:conan/[email protected]",
  "cpe": "cpe:2.3:a:openssl:openssl:3.3.2:*:*:*:*:*:*:*"
}

Validez le résultat par rapport au schéma CycloneDX avant de l’envoyer, par exemple avec la CycloneDX CLI ou cyclonedx-python-lib.

Deux détails pratiques. D’abord, les versions Conan ne correspondent pas toujours à celles de la NVD. ConanCenter utilise des versions comme cci.20230101 pour les bibliothèques sans release, et la NVD écrit parfois les versions autrement que les tags amont. Vérifiez quelques CVE par bibliothèque pour confirmer que les formats de version concordent, et ajoutez une surcharge de version dans la correspondance quand ce n’est pas le cas. Ensuite, traitez chaque avertissement comme un manque à combler : ajoutez la correspondance, ou consignez pourquoi la bibliothèque n’en a pas.

Le pipeline complet

Assemblé, le pipeline ressemble à ceci :

  1. Conan résout le graphe de dépendances et récupère les paquets via votre remote privé.
  2. Le build compile et effectue l’édition de liens.
  3. cdxgen, ou le hook SBOM de Conan, génère la SBOM de base.
  4. Le post-traitement ajoute les CPE et les PURL, signale les composants sans correspondance et valide le résultat.
  5. Les binaires et la SBOM enrichie sont stockés ensemble dans votre dépôt d’artefacts.
  6. Dependency-Track, ou une autre plateforme SCA, ingère la SBOM, la rapproche de la NVD, d’OSV et d’autres sources, et continue de la surveiller.

Ce dernier point est facile à sous-estimer. Quand une CVE est publiée pour une bibliothèque que vous avez livrée il y a six mois, Dependency-Track la rapproche des SBOM dont il dispose déjà et vous alerte, mais seulement si la CPE était présente lors de l’envoi de la SBOM. Ce rapprochement rétroactif est exactement ce qu’attend le Cyber Resilience Act de l’UE : identifier les composants de votre produit et les surveiller pour détecter les vulnérabilités pendant toute la période d’assistance. Notre guide CRA pour les PME détaille ces obligations.

Ajoutez aussi une barrière de build. Faites échouer le build quand un composant présentant une vulnérabilité critique est introduit, avertissez pour les vulnérabilités élevées et signalez le reste. Les développeurs apprennent alors l’existence d’une dépendance vulnérable dans l’exécution du pipeline qui l’a introduite, au moment où la correction coûte le moins. La surveillance continue rattrape ce qui n’était pas connu au moment du build. Il vous faut les deux.

Cas particuliers

Les bibliothèques internes que d’autres équipes de votre organisation compilent et publient comme paquets Conan n’ont pas d’entrée dans la NVD et ne doivent pas recevoir de CPE. Donnez-leur une PURL avec un qualificatif repository_url qui pointe vers votre remote privé, pour qu’elles restent visibles dans l’inventaire et dans les contrôles de licences.

Les dépendances hors du graphe Conan sont le véritable angle mort : code source copié, bibliothèques installées par le gestionnaire de paquets de la machine de build, et tout ce qui est récupéré avec FetchContent ou ExternalProject de CMake. Elles n’atteignent jamais la SBOM. Déplacez-les dans des recettes Conan quand c’est possible. Sinon, ajoutez-les à la SBOM lors du post-traitement avec une CPE maintenue à la main, et vérifiez régulièrement s’il en apparaît de nouvelles.

Pour les bibliothèques C++ qui ne figurent dans aucun registre, il n’existe pas encore de bon type de PURL ; pkg:generic existe, mais peu de sources de vulnérabilités s’en servent pour le rapprochement. La communauté PURL discute d’un nouveau type pour les logiciels non empaquetés, et la PURL elle-même est devenue une norme internationale, ECMA-427, en décembre 2025. En attendant un type pour les logiciels non empaquetés, la CPE reste l’identifiant le plus fiable pour ces bibliothèques.

Concevoir le programme autour de ceux qui l’utilisent

L’enrichissement corrige les données. Le succès du programme SCA dépend de sa capacité à répondre aux bonnes questions à chaque étape du développement :

Conception
  • Comment aider les développeurs à choisir des composants sûrs ?
Implémentation
  • Comment empêcher les développeurs d'importer de mauvais composants ?
Build
  • Comment faciliter la création d'une SBOM exacte pour un logiciel existant ?
Vérification
  • Comment la QA peut-elle vérifier l'absence de nouveaux résultats SCA avant la release ?
Exploitation
  • Comment respecter les exigences de sécurité après la release ?

Et il doit servir chaque équipe qui en dépend, pas seulement l’équipe sécurité :

ÉquipeCe qu'elle attend du programme SCA
GRC et juridiqueUne vue du risque global lié aux composants tiers dans toute l'entreprise
AppSecUn tableau de bord pour surveiller, trier et suivre, et des réponses aux questions des autres services
DevOpsDes notifications sur les nouveaux résultats, et un processus clair pour ajouter de nouveaux dépôts
DéveloppeursDes notifications sur les nouveaux résultats, et un outil SCA que chaque développeur peut utiliser pour les évaluer et les trier
SupportDes exports de SBOM à la demande des clients, des décisions consignées sur les vulnérabilités pour répondre aux questions, et des documents VEX
VentesUne description claire de l'approche SCA pour les appels d'offres, des SBOM avec un nombre acceptable de vulnérabilités, et des documents VEX récents

Un programme qui ne produit des résultats que pour l’équipe sécurité s’enlise. Un programme qui donne au support un document VEX à envoyer et aux ventes une réponse pour un questionnaire d’appel d’offres est utilisé.

Le déploiement

Un plan réaliste pour une base de code existante dure environ huit semaines :

  1. Semaines 1 et 2, audit. Lancez votre générateur de SBOM sur le build actuel et vérifiez combien de composants ont une CPE. Inventoriez vos recettes, vérifiez quelles bibliothèques figurent dans le dictionnaire CPE de la NVD, et commencez par les plus risquées : cryptographie, réseau et parseurs.
  2. Semaines 3 à 5, enrichissement. Ajoutez le fournisseur et le produit CPE dans chaque recette tierce ou dans le fichier de correspondance, vérifiés par rapport à la NVD. Faites des métadonnées CPE une exigence de revue pour toute nouvelle recette, et documentez les composants internes à part.
  3. Semaines 6 et 7, construction du post-traitement. Ajoutez-le au pipeline après la génération de la SBOM, validez la sortie par rapport au schéma CycloneDX et testez de bout en bout : envoyez une SBOM enrichie et vérifiez que des CVE connues apparaissent.
  4. Semaine 8, activation de la surveillance. Faites ingérer les SBOM enrichies par votre plateforme SCA, configurez des alertes pour les vulnérabilités critiques et élevées, ajoutez la barrière de build et confiez le processus aux équipes qui le feront tourner.

À retenir

  • Générez les SBOM C++ à partir du graphe de dépendances de Conan et considérez l’analyse binaire comme une deuxième couche.
  • Les générateurs de SBOM ajoutent au mieux une PURL pkg:conan. Ils n’ajoutent pas de CPE, et pour le C++, c’est la CPE qui vous relie à la NVD.
  • Vérifiez à la main les noms de fournisseur et de produit CPE dans le dictionnaire de la NVD, une fois par bibliothèque, et stockez-les sans la version.
  • Ajoutez la CPE lors d’une étape de post-traitement et traitez chaque composant sans correspondance comme un manque à combler.
  • Ne comptez pas sur la seule NVD : depuis 2026, elle enrichit moins de CVE, combinez donc le rapprochement par CPE avec des sources fondées sur la PURL.
  • Suivez ce qui contourne Conan : code copié, bibliothèques système et dépendances récupérées par CMake.

Pour être accompagné dans la conception ou la revue de votre programme SCA, réservez un appel gratuit.