Un SBOM CycloneDX est l’inventaire machine des composants d’un logiciel, avec leurs versions et leurs identifiants. Pour une application conteneurisée, il en faut deux : un SBOM du code, produit à partir du lockfile par cdxgen, et un SBOM de chaque image, produit par syft, qui voit aussi les paquets système. Cet article explique pourquoi les deux sont nécessaires, comment les générer, comment les scanner avec grype, et ce qu’il faut en faire ensuite.
Qu’est-ce qu’un SBOM ?
Un SBOM (Software Bill of Materials) est la liste structurée de tout ce qu’embarque un logiciel : bibliothèques, paquets du système d’exploitation, binaires, avec pour chacun une version et un identifiant standard. Cet identifiant est le plus souvent un purl (package URL), par exemple pkg:npm/esbuild@0.25.1 ou pkg:apk/alpine/openssl@..., ce qui permet à un outil de le comparer à une base de vulnérabilités sans ambiguïté.
Un SBOM n’est pas un rapport de vulnérabilités. Il décrit ce qui est présent ; l’analyse vient après, et peut être refaite à volonté sur le même inventaire.
Pourquoi choisir le format CycloneDX ?
CycloneDX est un format de SBOM porté par l’OWASP et orienté sécurité, qui sait représenter nativement le VEX (l’exploitabilité d’une vulnérabilité). Il se sérialise en JSON ou en XML, et sa version de spécification est indiquée dans chaque document (specVersion).
L’alternative principale est SPDX, plus orientée licences. Les deux sont reconnus ; le choix dépend surtout des outils en aval. Dependency-Track, grype et syft lisent CycloneDX nativement.
Un point pratique compte : la version de spécification. Un outil récent peut émettre une version que l’outil en aval ne lit pas encore. Fixer explicitement la version produite (par exemple 1.6) évite qu’une mise à jour du générateur casse l’ingestion.
Pourquoi faut-il un SBOM du code et un SBOM de l’image ?
Parce que le lockfile et l’image ne contiennent pas les mêmes choses. Le lockfile décrit les dépendances du langage, alors que l’image livrée contient aussi une distribution (paquets apk ou Debian), un runtime, parfois des outils de construction oubliés. Un audit fondé sur le seul lockfile ne voit rien de cette couche.
| Inventaire | Source analysée | Outil | Ce qu’il voit |
|---|---|---|---|
| Code | arbre de sources et lockfile | cdxgen | dépendances npm directes et transitives |
| Image | image construite (ou archive) | syft | paquets système, runtime, node_modules réellement copiés |
Le cas est concret dans saas-core : la CI était verte alors que l’image admin portait 21 vulnérabilités critiques, parce que pnpm audit et OSV-Scanner ne lisent que le lockfile, jamais les paquets système des images. C’est ce constat qui a motivé le scan des images construites.
Comment générer un SBOM du code avec cdxgen ?
cdxgen analyse un dépôt et produit un SBOM CycloneDX à partir des manifestes et lockfiles qu’il reconnaît. Pour un monorepo pnpm, on lui indique le type de projet, la récursion et la version de spécification :
npx --yes @cyclonedx/cdxgen@11 -r . -t pnpm -o sbom-app.cdx.json --spec-version 1.6
-r parcourt le dépôt récursivement, -t pnpm restreint l’analyse à l’écosystème pnpm, -o désigne le fichier de sortie. Lancé après un pnpm install --frozen-lockfile, le résultat décrit exactement le lockfile commité.
Comment générer un SBOM d’image avec syft ?
syft inventorie le contenu d’une image de conteneur, couche système comprise, et sait l’écrire en CycloneDX. Il lit une image depuis un registre, depuis le démon Docker, ou depuis une archive produite par docker save. L’archive a l’avantage de ne dépendre d’aucune socket : elle fonctionne aussi avec un Docker en mode rootless.
docker save mon-image:abc1234 -o api.tar
syft docker-archive:api.tar -q -o cyclonedx-json@1.6=sbom-api.cdx.json
La syntaxe -o format=fichier écrit la sortie dans un fichier ; le suffixe @1.6 fixe la version de spécification. Sans format explicite, syft peut produire une version que Dependency-Track rejette (Unrecognized specVersion 1.7, panne relevée dans la documentation de saas-core).
Comment scanner une image à partir de son SBOM avec grype ?
grype compare un SBOM, ou directement une image, à sa base de vulnérabilités et rend la liste des correspondances, avec la sévérité et l’état du correctif. Scanner le SBOM plutôt que l’image garantit que l’inventaire archivé et l’analyse portent sur la même chose.
grype sbom:sbom-api.cdx.json -q -o json=grype-api.json
La sortie JSON indique pour chaque correspondance si l’amont a publié une version corrigée (fix.state). C’est l’information qui permet une règle de blocage raisonnable : bloquer ce qui peut être corrigé par une montée de version, lire le reste sans bloquer.
Que faire du SBOM après la CI ?
Un SBOM sert à trois usages : prouver ce qui a été livré, réévaluer les composants dans le temps, et le remettre à un client qui le demande. Archiver le SBOM comme artefact de CI couvre le premier usage, mais un fichier archivé ne dit rien le lendemain d’une divulgation.
La réévaluation continue demande un outil qui conserve les SBOM par version et les recompare chaque jour aux bases de vulnérabilités, comme OWASP Dependency-Track, décrit dans l’article consacré à Dependency-Track. La provenance de l’image elle-même relève de la signature, traitée dans l’article sur Cosign et SLSA.
Le contexte réglementaire pèse aussi. La documentation de saas-core cite le Cyber Resilience Act européen comme l’un des déclencheurs de ce chantier, avec une obligation de SBOM attendue à partir de 2027 pour les produits comportant des éléments numériques ; elle classe son applicabilité au socle comme « à évaluer ».
Comment saas-core l’intègre
saas-core produit trois familles de SBOM CycloneDX 1.6, toutes en CI, et applique une règle de blocage sur les images construites.
- Code.
.github/workflows/sbom.yml, à chaque push surmain:pnpm install --frozen-lockfile, puis cdxgen avec la commande ci-dessus. Le résultat est archivé en artefactsbom-app. - Images amont. Le même workflow inventorie PostgreSQL et Redis avec
anchore/sbom-action(syft), au formatcyclonedx-json. Limite documentée : ces deux images sont inventoriées par étiquette, alors que l’environnement d’essai les déploie par empreinte. - Images applicatives. Le job
imagesde.github/workflows/ci.ymlconstruit les ciblesapi,workeretadminà chaque PR et à chaque push surmain, sans les pousser, puis lancescripts/sbom/scan-images.mjs. Ce script faitdocker save, produit le SBOM avec syft, le scanne avec grype, et écritsbom-<nom>.cdx.jsonetgrype-<nom>.json, archivés en artefactsbom-images-<sha>. syft et grype tournent en conteneur, épinglés par version et par empreinte dansscripts/sbom/tools.mjs(syft v1.52.0, grype v0.119.0). - Règle de blocage.
scripts/sbom/image-scan-policy.mjsfait échouer le job pour toute vulnérabilité critique dont l’amont a publié un correctif, sauf exception deimage-scan-exceptions.json, qui exige un identifiant, une raison et une date d’échéance. Les vulnérabilités élevées et les critiques sans correctif sont comptées dans le résumé du job, sans bloquer. - Rescan hebdomadaire.
.github/workflows/image-scan.ymlreconstruit les trois images demainchaque lundi à 05:00 UTC et relance le même script, avec la base de vulnérabilités du jour. Il ne notifie personne : son échec se lit dans l’onglet Actions. - Après la CI. Les SBOM d’images servent ensuite d’entrée à un Dependency-Track local, alimenté par
pnpm sbom:sync, hors du chemin de livraison.
Les images api et worker sont aussi vérifiées dans le même job : elles ne doivent embarquer ni npm, ni npx, ni corepack, ni yarn, dont les dépendances remontaient au scan.
En résumé
Un SBOM du code et un SBOM de l’image répondent à deux questions différentes, et seul le second voit la couche système. Fixer la version CycloneDX, scanner le SBOM plutôt que l’image, et bloquer sur ce qui est corrigeable rend le dispositif tenable au quotidien.
Ce qu’inclut le kit saas-core, workflows de SBOM et de scan compris, est détaillé sur la page des tarifs.