← Blog

Intégrer audit CVE et génération SBOM dans le CI/CD pour systèmes embarqués baremetal (C++ / Rust)

Pipeline 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)

· 22 min read
embeddedrustc++ci-cdsecuritysbomfirmware

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, 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 !

Aujourd’hui, des services comme GitHub vous proposent déjà des outils qui permettent de faire l’audit de votre code, tels que :

  • Dependabot : analyse les dépendances déclarées dans les manifestes (Cargo.toml, CMakeLists.txt, etc.) et ouvre des PR de mise à jour quand une CVE est détectée. Pratique pour les projets Cargo, moins pour du C++ baremetal sans gestionnaire de paquets.
  • CodeQL : analyse statique du code source pour détecter des patterns de vulnérabilités (buffer overflow, use-after-free, etc.). Utile en complément d’un audit CVE basé sur le SBOM, mais pas un substitut.
  • GitHub Security Advisories : permet de déclarer des vulnérabilités spécifiques à votre projet, avec un processus de triage et de publication. Idéal pour formaliser votre politique de divulgation responsable (PSIRT) et communiquer avec vos clients.

mais pour du baremetal, la réalité est plus complexe. Les pratiques classiques de l’embarqué (code vendoré, absence de gestionnaire de paquets, cycles longs, contraintes matérielles) rendent la génération de SBOM et l’audit CVE plus ardus que dans les applications web ou desktop.

Dans cet article, nous allons proposer un pipeline pour tenter de faire le lien entre les exigences CRA et la réalité des projets embarqués baremetal, avec un focus sur les pratiques qui passent à l’échelle dans un projet mixte C++ / Rust.

Les défis spécifiques du firmware baremetal

Souvent, dans les projets baremetal, on trouve les caractéristiques suivantes qui compliquent la gestion de la sécurité et la conformité CRA :

  • code vendoré (SDK constructeur copié-collé dans le repo) ;
  • pas de gestionnaire de paquets standard en C/C++ ;
  • cycles produit longs, de 5 à 15 ans sur le marché ;
  • mises à jour OTA pas toujours possibles (devices déployés en champ, contraintes flash, RAM, énergie).

Nous allons donc essayer de proposer des solutions pragmatiques, adaptées à ces contraintes, et qui peuvent être mises en place progressivement sans attendre la perfection.

Nous ne prétendons pas avoir une solution miracle qui règle tout, mais plutôt un ensemble de pratiques et d’outils qui, combinés, permettent de construire une chaîne SBOM/CVE robuste et évolutive. Nous sommes ouverts aux échanges : n’hésitez pas à partager vos retours d’expérience ou vos propres astuces en commentaire !

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.

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é.

C’est un point en plus dans l’utilisation de Rust pour le firmware : la conformité CRA est plus facile à atteindre, et la maintenance post-livraison plus gérable. Les outils Cargo (cargo-cyclonedx, cargo-auditable, cargo-deny) permettent de construire une chaîne SBOM/CVE robuste avec un effort d’intégration minimal.

Génération du SBOM

# Format CycloneDX
cargo install cargo-cyclonedx
cargo cyclonedx --format json --target thumbv7em-none-eabihf

# SBOM embarqué directement dans le binaire (clé pour l'embarqué)
cargo install cargo-auditable
cargo auditable build --release --target thumbv7em-none-eabihf

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 :

  1. 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.
  2. Audit chain robuste : impossible de “perdre” le SBOM en route.
  3. OTA fiable : chaque image flashée embarque sa propre vérité.

repository cargo-auditable

Récupération depuis un binaire déployé :

cargo install rust-audit-info
rust-audit-info firmware.elf

Audit CVE

cargo install cargo-audit cargo-deny

# Scan rapide contre RUSTSEC
cargo audit

# Politique complète (licences, sources, doublons, CVE)
cargo deny check

cargo-deny permet d’encoder votre politique dans deny.toml, versionnée dans le repo :

[advisories]
db-urls = ["https://github.com/rustsec/advisory-db"]
yanked = "deny"
unmaintained = "warn"
ignore = [
  # Justification VEX requise pour chaque ignore
  { id = "RUSTSEC-2023-0071", reason = "RSA timing attack: pas de RSA dans firmware, only Ed25519" }
]

[bans]
multiple-versions = "warn"
deny = [
  { name = "openssl", reason = "use rustls/ring in baremetal context" }
]

[licenses]
allow = ["MIT", "Apache-2.0", "BSD-3-Clause", "ISC"]
confidence-threshold = 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 également envisageable.

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é

SDK constructeur (STM32Cube, Nordic nRF SDK, NXP MCUXpresso, Renesas FSP, Raspberry Pi Pico SDK) 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: [aes, ecdh, ecdsa, sha256, ccm, gcm]
    modules_inactive: [rsa, dhe, md5, des, rc4, pkcs5, pkcs12, aria, camellia]

  - name: STM32CubeF4
    version: 1.28.0
    license: BSD-3-Clause-Clear
    purl: pkg:github/STMicroelectronics/STM32CubeF4@v1.28.0
    modules_active: [hal_gpio, hal_spi, hal_uart, hal_tim]
    modules_inactive: [hal_usb, hal_eth, hal_can, hal_i2s, hal_dac]

  - name: mcuboot
    version: 2.1.0
    license: Apache-2.0
    purl: pkg:github/mcu-tools/mcuboot@v2.1.0
    modules_active: [ecdsa_p256, swap_scratch]
    modules_inactive: [rsa_2048, direct_xip, ram_load]

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
import yaml, json, uuid
from datetime import datetime, timezone

manifest = yaml.safe_load(open("third_party/MANIFEST.yaml"))

sbom = {
    "bomFormat": "CycloneDX",
    "specVersion": "1.5",
    "serialNumber": f"urn:uuid:{uuid.uuid4()}",
    "version": 1,
    "metadata": {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "component": {
            "type": "firmware",
            "name": "myproduct-firmware",
            "version": "1.2.3"
        }
    },
    "components": [
        {
            "type": "library",
            "name": c["name"],
            "version": c["version"],
            "purl": c["purl"],
            "licenses": [{"license": {"id": c["license"]}}],
            "hashes": [{"alg": "SHA-256", "content": c["sha256"]}]
                       if "sha256" in c else []
        } for c in manifest["components"]
    ]
}
print(json.dumps(sbom, indent=2))

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
set -euo pipefail

MANIFEST="third_party/MANIFEST.yaml"

# Composants déclarés dans le manifeste
DECLARED=$(yq -r '.components[].name' "$MANIFEST" | sort)

# Sous-dossiers versionnés dans git (pas besoin de find, git connaît déjà son arbre)
PRESENT=$(git ls-tree --name-only HEAD third_party/ \
          | xargs -I{} basename {} | sort)

# Vérifier qu'il n'y a pas de dossier non déclaré
UNDECLARED=$(comm -23 <(echo "$PRESENT") <(echo "$DECLARED"))
if [ -n "$UNDECLARED" ]; then
  echo "ERREUR: dossiers dans third_party/ absents du manifeste:"
  echo "$UNDECLARED"
  exit 1
fi

# Vérifier qu'il n'y a pas d'entrée fantôme dans le manifeste
MISSING=$(comm -13 <(echo "$PRESENT") <(echo "$DECLARED"))
if [ -n "$MISSING" ]; then
  echo "ERREUR: entrées du manifeste sans dossier correspondant:"
  echo "$MISSING"
  exit 1
fi

echo "OK: manifeste et third_party/ sont synchronisés."

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
trivy sbom sbom.cdx.json --severity HIGH,CRITICAL --exit-code 1

# Grype
grype sbom:sbom.cdx.json --fail-on high

# OSV-Scanner
osv-scanner --sbom=sbom.cdx.json

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: [push, pull_request]

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: [build-rust, build-cpp]
    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 :

  1. Warning only : on logge, on ne bloque pas. Acceptable seulement les premières semaines de mise en place.
  2. Bloquant à HIGH/CRITICAL : pratique courante en cible.
  3. 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 :

{
  "@context": "https://openvex.dev/ns/v0.2.0",
  "@id": "https://myorg.com/vex/firmware-1.2.3",
  "author": "security@myorg.com",
  "timestamp": "2026-04-15T10:00:00Z",
  "statements": [
    {
      "vulnerability": {"name": "CVE-2024-XXXX"},
      "products": [{"@id": "pkg:github/myorg/firmware@1.2.3"}],
      "status": "not_affected",
      "justification": "vulnerable_code_not_in_execute_path",
      "impact_statement": "Function mbedtls_pkcs5_pbes2 absente du build, MBEDTLS_PKCS5_C non défini"
    }
  ]
}

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.

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èreTrivyGrypeOSV-Scanner
Sources CVENVD, GHSA, Aqua DBNVD, GHSA, Anchore DBOSV.dev (agrégateur multi-sources)
Formats SBOM acceptésCycloneDX, SPDXCycloneDX, SPDX, Syft JSONCycloneDX, SPDX, lockfiles natifs
Latence nouvelles CVEMoyenneMoyenneFaible (priorité OSV)
Couverture Rust / CargoBonneBonneExcellente
Couverture C/C++ vendoréLimitée (dépend du purl)LimitéeLimitée
Support VEXOpenVEX (expérimental)CycloneDX VEXOpenVEX natif
VitesseRapideTrès rapideRapide
EmpreinteModérée (DB embarquée)LégèreLégère
Cas idéalPipeline polyvalent (containers + SBOM + IaC)Pipeline orienté SBOM purCouverture 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.

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-* et bugprone-*, 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 :

  1. SBOM : cargo-auditable + cargo-cyclonedx côté Rust ; manifeste manuel + script CycloneDX côté C++ ; Syft en filet de sécurité sur le binaire final.
  2. Scan : OSV-Scanner primaire + Trivy ou Grype complémentaire en CI.
  3. Plateforme : Dependency-Track auto-hébergé pour la veille post-livraison.
  4. VEX : vexctl plus génération auto depuis la config build.
  5. Provenance : cosign pour signer SBOM et firmware.

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. 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.

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 14028Improving the Nation’s Cybersecurity, Maison Blanche
  • NIST SSDF (SP 800-218)Secure Software Development Framework, NIST CSRC
  • NTIAThe Minimum Elements For a Software Bill of Materials, juillet 2021
  • ENISAGood Practices for Supply Chain Cybersecurity, 2023

Spécifications

Génération SBOM

Scan CVE

Plateformes de gestion continue

VEX et provenance

SAST et licences

Lectures complémentaires