Intégrer audit CVE et génération SBOM dans le CI/CD pour systèmes embarqués baremetal (C++ / Rust)
Pipeline concret pour générer des SBOM CycloneDX et auditer les CVE en CI/CD sur firmware baremetal mixte C++ / Rust, dans le contexte du Cyber Resilience Act (CRA)
Intégrer audit CVE et génération SBOM dans le CI/CD pour systèmes embarqués baremetal (C++ / Rust)
Pourquoi maintenant : le CRA change la donne
Le Cyber Resilience Act (Règlement UE 2024/2847), adopté en octobre 2024 et pleinement applicable en décembre 2027, impose à tout produit comportant des composants numériques mis sur le marché européen :
- un SBOM exploitable par les autorités de surveillance ;
- une gestion documentée des vulnérabilités sur toute la durée de support ;
- une notification ENISA sous 24h en cas de vulnérabilité activement exploitée ;
- des mises à jour de sécurité gratuites pendant toute la “période de support” déclarée.
Côté US, l’EO 14028 et le SSDF NIST 800-218 tirent dans la même direction. Côté entités essentielles, NIS2 ajoute sa couche. Plus aucun fabricant sérieux ne livrera de firmware sans inventaire et veille CVE structurée.
Au-delà de l’obligation réglementaire, il y a une réalité plus pragmatique. Le jour où une CVE 9.8 tombe sur mbedTLS un vendredi soir — et ce jour arrive régulièrement —, votre capacité à répondre « on a douze produits affectés, ils sont identifiés, les patches partent lundi » au lieu de « on va vérifier la semaine prochaine » fait la différence entre une astreinte gérée et un week-end de panique avec le service juridique. Le SBOM, c’est d’abord pour vous-même, pas pour le régulateur.
Pour l’embarqué baremetal, c’est particulièrement douloureux :
- code souvent vendoré (SDK constructeur copié-collé dans le repo) ;
- pas de gestionnaire de paquets standard en C++ ;
- cycles produit de 5 à 15 ans sur le marché ;
- mises à jour OTA pas toujours possibles (devices déployés en champ, contraintes flash, RAM, énergie).
Cet article propose un pipeline concret pour générer SBOM et auditer CVE en CI/CD, avec un focus sur les pratiques qui passent à l’échelle dans un projet baremetal C++ ou Rust.
Comprendre les artefacts
SBOM
Un SBOM (Software Bill of Materials) est l’inventaire structuré des composants logiciels d’un produit. Deux formats dominent :
- SPDX (ISO/IEC 5962:2021) : historique, riche pour les licences, sérialisable en JSON, YAML, RDF, tag-value.
- CycloneDX (OWASP) : orienté sécurité, JSON ou XML, plus compact, plus largement adopté côté chaîne d’outils CI/CD.
Le CRA accepte les deux. CycloneDX est plus simple à intégrer en pratique et son écosystème de scanners est plus mature. C’est notre choix par défaut dans cet article.
CVE et bases d’avis
Les CVE (Common Vulnerabilities and Exposures) sont des identifiants standardisés mais leur enrichissement dépend de plusieurs bases :
- NVD (NIST) : référence historique, mais souvent en retard de plusieurs semaines depuis 2024.
- OSV (Google) : excellente couverture Rust/Cargo, GitHub Advisories, agrège plusieurs sources, API moderne.
- GHSA : GitHub Security Advisories, surtout pour les écosystèmes hébergés sur GitHub.
- RUSTSEC : la base spécifique Rust, intégrée à
cargo-audit.
En 2026, la stratégie raisonnable est OSV en source primaire, complétée par NVD via un scanner type Trivy ou Grype.
VEX
VEX (Vulnerability Exploitability eXchange) est une déclaration formelle qu’une CVE n’affecte pas votre produit (code mort, mitigation appliquée, configuration spécifique). Sans VEX, un projet C++ moyennement gros remonte typiquement 200 à 400 alertes au premier scan complet. L’équipe sécurité ferme l’onglet en se disant « on regardera plus tard », l’équipe firmware ne rouvre jamais le rapport. C’est le cimetière classique des programmes SBOM bien intentionnés. Investir dans la génération automatique de VEX dès le départ change radicalement la trajectoire.
Deux formats coexistent :
- CycloneDX VEX : les statements VEX sont intégrées directement dans le SBOM CycloneDX (ou dans un document CycloneDX séparé de type
vex). Avantage : un seul format pour le SBOM et le VEX, pas de fichier supplémentaire à gérer. Inconvénient : plus verbeux, et le VEX est couplé au cycle de vie du SBOM. - OpenVEX : format indépendant (openvex.dev), porté par Chainguard et la Linux Foundation. Document JSON autonome, versionné séparément du SBOM. Avantage : on peut mettre à jour le VEX sans régénérer le SBOM, et la spécification est plus simple à implémenter. C’est le format le mieux supporté par OSV-Scanner (flag
--experimental-vex).
En pratique, OpenVEX est le choix le plus pragmatique aujourd’hui : fichier vex.json versionné dans le repo, consommé nativement par OSV-Scanner et importable dans Dependency-Track. C’est le format utilisé dans les exemples de cet article.
Rust baremetal : le terrain le plus simple
Cargo apporte un manifeste explicite, un fichier de verrouillage, et un écosystème entièrement indexé sur OSV. Tout l’outillage SBOM/CVE en bénéficie immédiatement, y compris en no_std cross-compilé.
Génération du SBOM
# Format CycloneDX
# SBOM embarqué directement dans le binaire (clé pour l'embarqué)
cargo-auditable est une pépite pour le baremetal : le SBOM est intégré dans une section ELF dédiée (.dep-v0) du binaire produit. Trois bénéfices :
- Traçabilité a posteriori : on peut récupérer l’inventaire d’un firmware déployé en analysant le binaire récupéré du device.
- Audit chain robuste : impossible de “perdre” le SBOM en route.
- OTA fiable : chaque image flashée embarque sa propre vérité.
Récupération depuis un binaire déployé :
Audit CVE
# Scan rapide contre RUSTSEC
# Politique complète (licences, sources, doublons, CVE)
cargo-deny permet d’encoder votre politique dans deny.toml, versionnée dans le repo :
[]
= ["https://github.com/rustsec/advisory-db"]
= "deny"
= "warn"
= [
# Justification VEX requise pour chaque ignore
{ = "RUSTSEC-2023-0071", = "RSA timing attack: pas de RSA dans firmware, only Ed25519" }
]
[]
= "warn"
= [
{ = "openssl", = "use rustls/ring in baremetal context" }
]
[]
= ["MIT", "Apache-2.0", "BSD-3-Clause", "ISC"]
= 0.93
C++ baremetal : la vraie difficulté
Pas de gestionnaire universel, et c’est là que tout devient compliqué. Si vous travaillez sur un projet baremetal qui a quelques années, vous reconnaissez probablement la scène : un dossier third_party/ qui ressemble à un grenier, une copie de mbedTLS dont personne ne sait précisément quelle version (et qui a peut-être été patchée localement en 2021, allez savoir), un SDK constructeur livré sur clé USB en 2019, un README qui ment poliment sur les versions. C’est la norme, pas l’exception.
Si vous démarrez un projet neuf, des gestionnaires comme Conan 2 ou vcpkg peuvent générer des SBOMs CycloneDX depuis leur graphe de dépendances. Conan 2 supporte nativement la cross-compilation baremetal (profils toolchain, os=baremetal) et dispose d’un générateur SBOM intégré (SBOMGenerator). vcpkg est envisageable avec des triplets custom (arm-none-eabi.cmake), mais l’écosystème de ports baremetal reste mince. Ces outils résolvent élégamment le problème du SBOM quand on peut les adopter.
En pratique, la majorité des projets embarqués existants n’utilisent ni l’un ni l’autre — et c’est le cas traité ci-dessous.
Code vendoré : la réalité de 80% des projets
SDK constructeur (STM32Cube, Nordic nRF SDK, NXP MCUXpresso, Renesas FSP) copié dans third_party/, plus quelques bibliothèques (mbedTLS, MCUboot, FatFS, lwIP, tinyusb…). Aucune lockfile, parfois même aucune trace de version dans le repo.
Stratégie pragmatique en quatre étapes :
1. Manifeste manuel versionné dans third_party/MANIFEST.yaml :
components:
- name: mbedtls
version: 3.5.2
license: Apache-2.0
purl: pkg:github/Mbed-TLS/mbedtls@v3.5.2
source_url: https://github.com/Mbed-TLS/mbedtls/releases/tag/v3.5.2
sha256: 3e1be86b...
modules_active:
modules_inactive:
- name: STM32CubeF4
version: 1.28.0
license: BSD-3-Clause-Clear
purl: pkg:github/STMicroelectronics/STM32CubeF4@v1.28.0
modules_active:
modules_inactive:
- name: mcuboot
version: 2.1.0
license: Apache-2.0
purl: pkg:github/mcu-tools/mcuboot@v2.1.0
modules_active:
modules_inactive:
Les champs modules_active et modules_inactive sont la clé de voûte du VEX automatique. On les déclare au même endroit que le reste de l’inventaire — pas besoin de parser les headers de chaque bibliothèque. Un seul script générique lit ces champs et produit les statements not_affected pour tous les composants.
2. Conversion automatique en CycloneDX par script, déclenchée à chaque build :
# scripts/manifest_to_cdx.py
=
=
3. Détection de dérive : un test CI vérifie que chaque sous-dossier de third_party/ a une entrée correspondante dans le manifeste, et inversement. Si quelqu’un ajoute une bibliothèque sans mettre à jour MANIFEST.yaml, le build casse. C’est la garantie que le SBOM reste fidèle à la réalité.
#!/usr/bin/env bash
# scripts/check_thirdparty_drift.sh
MANIFEST="third_party/MANIFEST.yaml"
# Composants déclarés dans le manifeste
DECLARED=
# Sous-dossiers versionnés dans git (pas besoin de find, git connaît déjà son arbre)
PRESENT=
# Vérifier qu'il n'y a pas de dossier non déclaré
UNDECLARED=
if [; then
fi
# Vérifier qu'il n'y a pas d'entrée fantôme dans le manifeste
MISSING=
if [; then
fi
On s’appuie sur git ls-tree plutôt que find : git connaît déjà l’arborescence, pas besoin de scanner le filesystem. Les noms dans MANIFEST.yaml doivent correspondre un-pour-un aux entrées de third_party/ dans le tree git.
4. Génération automatique de VEX depuis les champs modules_inactive du manifeste. Un script unique parcourt tous les composants et produit des statements OpenVEX not_affected pour chaque module déclaré inactif — quelle que soit la bibliothèque. Pas besoin de parser les headers de chaque lib : le manifeste est la source de vérité, et le script reste générique.
Audit CVE en C++
Une fois le SBOM CycloneDX produit, les outils convergent et fonctionnent identiquement sur le SBOM Rust ou C++ :
# Trivy
# Grype
# OSV-Scanner
Pour le baremetal, OSV-Scanner et Trivy en complément offrent la meilleure couverture : OSV pour les bibliothèques OSS bien indexées, Trivy pour les bases NVD plus larges et les composants moins courants.
Pipeline CI/CD complet
Exemple GitHub Actions, structure transposable directement à GitLab CI ou Jenkins. Il n’est pas parfait — il ne le sera jamais — mais il tourne tel quel sur un projet existant et s’enrichit progressivement. Mieux vaut un pipeline bancal qui tourne qu’un pipeline parfait qui n’existe pas.
name: firmware-ci
on:
jobs:
build-rust:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: thumbv7em-none-eabihf
- run: cargo install cargo-auditable cargo-cyclonedx cargo-deny
- name: Build with embedded SBOM
run: cargo auditable build --release --target thumbv7em-none-eabihf
- name: Generate CycloneDX
run: cargo cyclonedx --format json -- --target thumbv7em-none-eabihf
- name: Policy check
run: cargo deny check
- uses: actions/upload-artifact@v4
with:
name: sbom-rust
path: bom.json
build-cpp:
runs-on: ubuntu-latest
container: ghcr.io/myorg/arm-none-eabi-toolchain:13.2
steps:
- uses: actions/checkout@v4
- name: Verify third_party integrity
run: scripts/check_thirdparty_drift.sh
- name: Build firmware
run: |
cmake -B build -DCMAKE_TOOLCHAIN_FILE=cmake/cortex-m4.cmake
cmake --build build
- name: Generate SBOM from manifest
run: python scripts/manifest_to_cdx.py > sbom.cdx.json
- name: Generate VEX from build config
run: python scripts/config_to_vex.py > vex.json
- uses: actions/upload-artifact@v4
with:
name: sbom-cpp
path: |
sbom.cdx.json
vex.json
scan:
needs:
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
- name: OSV scan with VEX
uses: google/osv-scanner-action@v1
with:
scan-args: |
--sbom=sbom-rust/bom.json
--sbom=sbom-cpp/sbom.cdx.json
--experimental-vex=sbom-cpp/vex.json
- name: Trivy scan
uses: aquasecurity/trivy-action@master
with:
scan-type: sbom
scan-ref: sbom-cpp/sbom.cdx.json
severity: HIGH,CRITICAL
exit-code: 1
publish:
needs: scan
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
- name: Push SBOM to Dependency-Track
run: |
curl -X POST https://dtrack.myorg.com/api/v1/bom \
-H "X-Api-Key: ${{ secrets.DTRACK_API_KEY }}" \
-F "project=${{ env.PROJECT_UUID }}" \
-F "bom=@sbom-cpp/sbom.cdx.json"
- name: Sign artifacts with cosign
run: |
cosign sign-blob --yes sbom-cpp/sbom.cdx.json \
--output-signature sbom.sig \
--output-certificate sbom.crt
Le gate de sévérité
Trois stratégies, du plus laxiste au plus strict :
- Warning only : on logge, on ne bloque pas. Acceptable seulement les premières semaines de mise en place.
- Bloquant à HIGH/CRITICAL : pratique courante en cible.
- Bloquant avec VEX obligatoire : tout HIGH/CRITICAL bloque sauf déclaration VEX justifiée. C’est la cible CRA.
Compter trois à six mois entre l’étape 1 et l’étape 3 sur un projet existant.
VEX en pratique
Un fichier vex.json versionné, format OpenVEX :
Quatre justifications standard à connaître : component_not_present, vulnerable_code_not_present, vulnerable_code_not_in_execute_path, vulnerable_code_cannot_be_controlled_by_adversary.
Spécificités firmware baremetal
Bootloader. MCUboot est quasi-standard. À tracker dans le SBOM avec sa version et sa configuration (algorithmes crypto activés, support TLV, slot count). La majorité des CVE bootloader relèvent du choix de configuration plus que du code lui-même.
Crypto. mbedTLS et wolfSSL concentrent l’essentiel des CVE de l’embarqué. Vérifiez quels modules sont réellement compilés, pas tout le repo source. Déclarer les modules actifs et inactifs dans le MANIFEST.yaml (champs modules_active / modules_inactive) et générer automatiquement les statements VEX not_affected divise par cinq à dix le bruit dans Dependency-Track.
Reproductibilité. Pinner la toolchain (image Docker versionnée), les flags de compilation, le SOURCE_DATE_EPOCH pour les timestamps embarqués dans le SBOM. Sans cela, votre SBOM dérive entre deux builds identiques et vos hashes ne correspondent plus aux artefacts livrés. C’est un motif de non-conformité CRA.
Traçabilité OTA. Associer chaque image OTA signée à son SBOM dans Dependency-Track. En cas de CVE postérieure à la livraison, on identifie en minutes les serial numbers déployés à patcher — au lieu de plusieurs jours de fouille manuelle.
Mémoire flash contrainte. Sur des MCU classe Cortex-M0/M0+, embarquer le SBOM dans le binaire (cargo-auditable) coûte entre 1 et 5 kB. À budgétiser dès la conception. Sur les MCU plus contraints, on stocke le SBOM hors-device, indexé par hash du firmware.
Panorama des outils libres
Voici l’écosystème open-source complet pour bâtir une chaîne SBOM/CVE de bout en bout, sans dépendance à un SaaS commercial. Tout est auto-hébergeable.
Génération SBOM
- Syft (Anchore) — Générateur polyvalent : scanne sources, archives, containers et binaires. Sortie SPDX et CycloneDX. Indispensable en filet de sécurité pour découvrir les composants vendorés oubliés du manifeste.
- cargo-cyclonedx — CycloneDX depuis un projet Cargo, supporte les targets cross-compilées.
- cargo-auditable — Embarque le SBOM dans la section ELF du binaire Rust. Pas d’équivalent côté C++.
- cargo-sbom — Alternative à cargo-cyclonedx, sortie SPDX.
- CycloneDX CLI (
cyclonedx-cli) — Merge, validation, conversion SPDX↔CycloneDX, signature de SBOMs. - Conan 2 SBOM Generator — Sortie native CycloneDX depuis le graphe Conan.
- ScanCode Toolkit (nexB) — Analyse licences et provenance par scan de code source. Lourd mais exhaustif. Utile pour caractériser un SDK fournisseur dont l’origine exacte est floue.
- microsoft/sbom-tool — Génération SPDX 2.2, focus paquets OS et langages courants.
- bom (kubernetes-sigs) — Outil SPDX simple, pratique pour scripter la composition de SBOMs multiples (firmware + bootloader + assets).
Scan CVE
- Trivy (Aqua Security) — Scanner polyvalent : SBOM, containers, IaC, secrets. Bases NVD + GHSA + Aqua. Couverture excellente, rapide, intégration CI native.
- Grype (Anchore) — Scanner orienté SBOM (CycloneDX, SPDX, Syft JSON). Plus léger que Trivy, parfait pour pipelines focalisés vulnérabilités. Bien apparié à Syft.
- OSV-Scanner (Google) — Source primaire OSV.dev, meilleure latence sur les nouvelles CVE Cargo. Support OpenVEX natif.
- cargo-audit — Spécifique Rust, base RUSTSEC.
- cargo-deny — Politique étendue Rust : CVE, licences, sources, doublons, dans un même
deny.toml.
Comparaison rapide Trivy / Grype / OSV-Scanner
| Critère | Trivy | Grype | OSV-Scanner |
|---|---|---|---|
| Sources CVE | NVD, GHSA, Aqua DB | NVD, GHSA, Anchore DB | OSV.dev (agrégateur multi-sources) |
| Formats SBOM acceptés | CycloneDX, SPDX | CycloneDX, SPDX, Syft JSON | CycloneDX, SPDX, lockfiles natifs |
| Latence nouvelles CVE | Moyenne | Moyenne | Faible (priorité OSV) |
| Couverture Rust / Cargo | Bonne | Bonne | Excellente |
| Couverture C/C++ vendoré | Limitée (dépend du purl) | Limitée | Limitée |
| Support VEX | OpenVEX (expérimental) | CycloneDX VEX | OpenVEX natif |
| Vitesse | Rapide | Très rapide | Rapide |
| Empreinte | Modérée (DB embarquée) | Légère | Légère |
| Cas idéal | Pipeline polyvalent (containers + SBOM + IaC) | Pipeline orienté SBOM pur | Couverture langages modernes, latence VEX |
Verdict pratique : OSV-Scanner en primaire pour la latence CVE et le support VEX natif, Trivy en secondaire pour étendre la couverture NVD sur les composants C/C++ vendorés. Grype reste excellent si vous êtes déjà dans la chaîne Anchore (Syft + Grype + Quill côté signature). Faire tourner les trois en parallèle reste raisonnable : runtime cumulé sous deux minutes, et les recoupements éliminent les angles morts.
Gestion continue (plateformes)
- OWASP Dependency-Track — La référence libre. Ingère SBOMs CycloneDX, re-scanne quotidiennement contre NVD/OSV/GHSA, expose API REST et webhooks. Auto-hébergement Docker simple. C’est l’épine dorsale recommandée pour le monitoring post-livraison.
- DefectDojo — Plateforme DevSecOps plus large : agrège résultats SAST (Static Application Security Testing — analyse statique du code source), DAST (Dynamic Application Security Testing — tests de sécurité à l’exécution), SCA (Software Composition Analysis — inventaire et audit des dépendances tierces), SBOM, triage, métriques, intégration Jira. Plus lourde à exploiter ; pertinente si vous avez d’autres flux sécurité à fédérer.
VEX et provenance
- vexctl (Chainguard) — Génération, manipulation, attachement de documents OpenVEX.
- openvex/go-vex — Bibliothèque Go pour produire OpenVEX programmatiquement (utile pour générer du VEX automatique depuis votre manifeste).
- in-toto — Cadre d’attestation pour la chaîne d’approvisionnement : qui a fait quoi, quand, sur quel artefact.
- cosign (Sigstore) — Signature d’artefacts (SBOM, firmware) sans gestion de clés (keyless via OIDC) ou avec clés classiques.
- SLSA verifier — Vérification de provenance niveau SLSA.
Analyse binaire firmware (spécifique baremetal)
Quand un fournisseur livre un firmware sans SBOM, ou pour vérifier ce qui est réellement dans le binaire :
- binwalk — Extraction et analyse de firmware. Identifie sections, systèmes de fichiers embarqués, signatures connues.
- EMBA (Embedded Analyzer) — Plateforme d’analyse firmware automatisée : extraction, identification composants, scan CVE sur composants détectés. Particulièrement adaptée aux audits réglementaires.
- cwe_checker (Fraunhofer) — Détection de patterns CWE dans des binaires via Ghidra. Trouve des classes de bugs sécurité par analyse statique binaire.
- Ghidra (NSA) — Désassembleur de référence, extensible. Investigation manuelle approfondie.
- radare2 / Cutter — Alternatives plus légères à Ghidra.
- FACT (Firmware Analysis and Comparison Tool) — Plateforme web d’analyse comparative de firmwares (utile pour comparer deux versions livrées).
Exemple complet : auditer un firmware fournisseur sans SBOM
Cas typique en industriel : un sous-traitant livre firmware.elf pour intégration sur votre carte. Pas de SBOM, juste un changelog vague. Voici un flux d’investigation reproductible en cinq étapes, applicable à du pur baremetal Cortex-M.
Étape 1 — Identification des composants par chaînes
# Recherche de signatures de bibliothèques connues
# Résultat typique :
# Mbed TLS 3.5.2
# lwIP 2.1.3
# MCUboot v2.1.0-rc1
Les bibliothèques C bien tenues exposent presque toujours leur version dans une constante *_VERSION_STRING. C’est la première source d’identification, fiable à 80%.
Étape 2 — Analyse structurelle
# Sections, taille, point d'entrée
# Symboles : confirme la présence de modules spécifiques
| |
# Extraction de ressources embarquées (certificats, FAT, blobs)
L’analyse des symboles donne un inventaire factuel des modules réellement linkés : si seuls les symboles mbedtls_aes_* et mbedtls_sha256_* apparaissent, c’est un argument concret pour exiger du fournisseur un SBOM et un VEX couvrant uniquement les modules effectivement présents.
Étape 3 — Détection de patterns CWE
# Filtrer les findings critiques
cwe_checker détecte par exemple : usages de strcpy non bornés, double-free potentiels, déréférencements de pointeurs non vérifiés. À traiter comme indications, pas comme preuves.
Étape 4 — Reconstitution du SBOM
À partir des composants identifiés, alimenter un manifeste dédié au firmware fournisseur :
# third_party/MANIFEST-vendor-firmware.yaml
firmware: vendor-acme-firmware
version: 4.2.1
sha256: 8a4f3c...
components:
- name: mbedtls
version: 3.5.2
detection: strings + symbols
confidence: high
modules_active:
modules_inactive: # base auto VEX
- name: lwip
version: 2.1.3
detection: strings
confidence: high
- name: mcuboot
version: 2.1.0-rc1
detection: strings (partial)
confidence: medium # rc, à confirmer
Étape 5 — Scan CVE et génération VEX
# Conversion en CycloneDX
# Génération VEX automatique depuis modules_inactive
# Scan
Limites à assumer. Ce flux ne donne pas une couverture exhaustive — impossible sans le code source du fournisseur. Il permet en revanche :
- d’identifier les composants à tracker prioritairement en veille CVE ;
- d’exiger un SBOM officiel au fournisseur en argumentant sur des éléments concrets ;
- de constituer une pièce d’audit défensive en cas de litige post-incident ;
- de bloquer les régressions silencieuses entre deux livraisons (en automatisant le flux dans la CI sur chaque drop fournisseur).
À propos d’EMBA. EMBA orchestre les étapes 1 à 3 de manière automatisée, mais a été conçue pour des firmwares Linux ou hybrides (passerelles IoT, routeurs). Pour du pur baremetal Cortex-M sans filesystem, son apport est marginal — le flux manuel ci-dessus reste plus pertinent. EMBA prend tout son sens dès qu’un filesystem (squashfs, jffs2, FAT) est présent dans le binaire.
SAST complémentaire (analyse statique)
Le SBOM couvre les composants tiers (SCA), le SAST couvre votre propre code. Les deux sont nécessaires pour le CRA :
- Semgrep (open core) — Règles YAML, rapide, registre communautaire.
- CodeQL — Gratuit pour projets open-source, payant en privé.
- cppcheck — Analyse statique C/C++ classique.
- clang-tidy — Avec checkers
cert-*etbugprone-*, base sérieuse pour C++ embarqué. - flawfinder, RATS — Détection d’usages de fonctions C historiquement dangereuses.
- clippy — Pour Rust, fourni avec rustup.
Licences et conformité
- FOSSology — Plateforme libre d’analyse de licences à grande échelle.
- REUSE tool (FSFE) — Vérifie que chaque fichier source de votre repo a une licence et un copyright SPDX déclarés en en-tête.
- licensecheck — Outil léger de détection de licences dans un repo.
Stack minimale viable
Pour une équipe qui démarre, voici la combinaison recommandée :
- SBOM :
cargo-auditable+cargo-cyclonedxcôté Rust ; manifeste manuel + script CycloneDX côté C++ ; Syft en filet de sécurité sur le binaire final. - Scan : OSV-Scanner primaire + Trivy ou Grype complémentaire en CI.
- Plateforme : Dependency-Track auto-hébergé pour la veille post-livraison.
- VEX :
vexctlplus génération auto depuis la config build. - Provenance :
cosignpour signer SBOM et firmware. - Audit binaire ponctuel :
binwalkou EMBA sur les firmwares fournisseurs.
Comptez trois à quatre semaines d’intégration sur un projet existant.
Bonnes pratiques organisationnelles
L’outillage est la partie facile, et c’est presque tout ce dont parle cet article jusqu’ici. Le reste, ce sont des décisions humaines, et c’est là que les programmes SBOM réussissent ou échouent.
- Versionner les SBOM comme du code, dans un repo séparé ou un registry (Dependency-Track, ou attestation Cosign sur OCI registry).
- Automatiser la veille post-livraison : Dependency-Track re-scanne quotidiennement les SBOMs publiés contre les nouvelles CVE. Sans cela, vous ratez les CVE qui sortent après votre build, ce qui est le cas le plus fréquent en embarqué.
- Séparer CVE-as-build-gate et CVE-as-runtime-watch : le pipeline bloque les régressions à la PR, le watcher continu déclenche un ticket Jira sur tout firmware en service touché par une CVE nouvelle.
- Documenter chaque ignore et chaque VEX. Sans justification écrite, l’audit CRA est raté. La justification doit pointer vers une preuve technique (config flag, code review, test).
- Former les équipes. La dette SBOM se crée en deux jours, se résorbe en six mois. Faire passer le sujet en revue d’architecture.
- Mesurer. Trois métriques de pilotage : temps moyen entre publication CVE et triage, ratio VEX justifiés / ignores bruts, couverture SBOM (pourcentage de composants vendorés présents dans le manifeste).
Conclusion
Le contraste entre les deux écosystèmes est frappant. Côté Rust, le pipeline SBOM/CVE est presque clé-en-main : Cargo.lock fournit l’inventaire, cargo-auditable embarque le SBOM dans le binaire, cargo-deny encode la politique, OSV-Scanner scanne contre des bases à jour — trois jours de travail pour un pipeline propre, et l’écosystème vous porte. Côté C++ baremetal, chaque étape demande de l’outillage maison : manifeste manuel, script de conversion CycloneDX, détection de dérive, génération VEX depuis le manifeste. Ça fonctionne, mais c’est fragile et ça repose sur la discipline de l’équipe.
Ce décalage devrait peser dans les décisions d’architecture. Pour les nouveaux projets, Rust en no_std offre un avantage structurel : non seulement la sécurité mémoire, mais aussi toute la chaîne d’audit CVE et de conformité CRA qui vient “gratuitement” avec l’écosystème Cargo. Pour les projets C++ existants, une migration progressive — commencer par les composants les plus exposés (crypto, réseau, bootloader) — est une trajectoire raisonnable. C’est d’ailleurs l’approche que nous suivons chez ADNT : notre bootloader A/B pour RP2040 est écrit en Rust, avec un Cargo.lock auditable et un SBOM générable en une commande. Le SBOM et le VEX en C++ restent nécessaires sur le code legacy, mais chaque module migré vers Rust est un module de moins à maintenir manuellement dans le manifeste.
L’automatisation du scan CVE en CI/CD n’est pas un luxe. C’est le seul moyen de tenir la promesse du CRA sur la durée : un firmware qui vit dix ans sur le terrain ne peut pas dépendre d’un audit annuel. Le pipeline doit tourner à chaque commit, le watcher doit scanner chaque nuit, et le VEX doit se générer sans intervention humaine. Tout ce qui n’est pas automatisé finit par être oublié.
Démarrez par le SBOM, même imparfait. Un SBOM à 80% bien automatisé bat à chaque fois un SBOM parfait généré à la main une fois par an. Et si vous avez le choix du langage pour votre prochain projet firmware, le constat est clair : l’écosystème Rust est prêt, l’écosystème C++ demande qu’on le construise soi-même.
L’horizon réglementaire est dans dix-huit mois. C’est court pour rattraper, c’est largement suffisant pour démarrer proprement.
Références et liens utiles
Réglementation
- Cyber Resilience Act — Règlement (UE) 2024/2847, texte officiel sur EUR-Lex
- NIS2 — Directive (UE) 2022/2555, EUR-Lex
- Executive Order 14028 — Improving the Nation’s Cybersecurity, Maison Blanche
- NIST SSDF (SP 800-218) — Secure Software Development Framework, NIST CSRC
- NTIA — The Minimum Elements For a Software Bill of Materials, juillet 2021
- ENISA — Good Practices for Supply Chain Cybersecurity, 2023
Spécifications
- CycloneDX — cyclonedx.org
- SPDX — spdx.dev (ISO/IEC 5962:2021)
- OpenVEX — openvex.dev
- OSV Schema — ossf.github.io/osv-schema
- SLSA — slsa.dev
- in-toto — in-toto.io
- Package URL (purl) spec — github.com/package-url/purl-spec
Génération SBOM
- Syft — github.com/anchore/syft
- cargo-cyclonedx — github.com/CycloneDX/cyclonedx-rust-cargo
- cargo-auditable — github.com/rust-secure-code/cargo-auditable
- CycloneDX CLI — github.com/CycloneDX/cyclonedx-cli
- Conan — conan.io
- ScanCode Toolkit — github.com/aboutcode-org/scancode-toolkit
- Microsoft sbom-tool — github.com/microsoft/sbom-tool
- bom (kubernetes-sigs) — github.com/kubernetes-sigs/bom
Scan CVE
- Trivy — github.com/aquasecurity/trivy
- Grype — github.com/anchore/grype
- OSV-Scanner — github.com/google/osv-scanner — base : osv.dev
- cargo-audit — github.com/rustsec/rustsec
- cargo-deny — github.com/EmbarkStudios/cargo-deny
- RustSec Advisory DB — rustsec.org
Plateformes de gestion continue
- OWASP Dependency-Track — dependencytrack.org
- DefectDojo — defectdojo.com — github.com/DefectDojo/django-DefectDojo
VEX et provenance
- vexctl (Chainguard) — github.com/openvex/vexctl
- go-vex — github.com/openvex/go-vex
- Sigstore / cosign — sigstore.dev
- SLSA verifier — github.com/slsa-framework/slsa-verifier
Analyse binaire firmware
- binwalk — github.com/ReFirmLabs/binwalk
- EMBA — github.com/e-m-b-a/emba
- cwe_checker (Fraunhofer FKIE) — github.com/fkie-cad/cwe_checker
- Ghidra — ghidra-sre.org
- radare2 / Cutter — rada.re / cutter.re
- FACT — fkie-cad.github.io/FACT_core
SAST et licences
- Semgrep — semgrep.dev
- CodeQL — codeql.github.com
- cppcheck — cppcheck.sourceforge.io
- clang-tidy — clang.llvm.org/extra/clang-tidy
- flawfinder — dwheeler.com/flawfinder
- FOSSology — fossology.org
- REUSE tool (FSFE) — reuse.software
Lectures complémentaires
- MCUboot Security — documentation officielle du bootloader, docs.mcuboot.com/security
- OpenSSF Scorecard — github.com/ossf/scorecard
- CISA — SBOM Sharing Lifecycle Report, 2023
- OWASP — Software Component Verification Standard (SCVS) — owasp.org/www-project-software-component-verification-standard