Vous avez un SBOM parfait généré au build. Puis arrive une CVE critique. Vous vérifiez : "on n'utilise pas cette lib". Sauf qu'en production, un plugin chargé dynamiquement l'utilise. Le SBOM décrivait ce qui avait été assemblé, pas l'état opérationnel réellement observé.
L'OBOM (Operations Bill of Materials) cherche à capturer cet état opérationnel : librairies chargées dynamiquement, modules activés selon l'environnement, configurations runtime, services connectés. C'est un complément au SBOM, pas un remplacement.
Dans ce guide, vous apprendrez :
- Pourquoi l'OBOM est nécessaire et ce qu'il capture de plus qu'un SBOM
- Comment collecter les informations runtime sur Linux et dans les conteneurs
- Comment générer un OBOM avec les outils disponibles
- Comment corréler SBOM et OBOM pour une visibilité complète
Prérequis
Section intitulée « Prérequis »- Lecture du guide SBOM : comprendre le Software Bill of Materials
- Lecture du guide XBOM : au-delà du SBOM
- Notions de base sur Linux et les conteneurs
SBOM vs OBOM : le décalage build/runtime
Section intitulée « SBOM vs OBOM : le décalage build/runtime »Les deux inventaires ne sont pas produits au même moment ni à partir de la même source, et c'est précisément d'où vient l'écart. Le SBOM lit des fichiers de déclaration au moment où le logiciel est assemblé ; l'OBOM interroge des processus qui tournent. Entre les deux, il s'est passé un déploiement, des variables d'environnement, des plugins activés et des bibliothèques système fournies par l'hôte.
Ce que le SBOM capture... et ce qu'il manque
Section intitulée « Ce que le SBOM capture... et ce qu'il manque »Lisez ce tableau ligne par ligne comme une liste de questions auxquelles un SBOM seul ne répond pas. La colonne de droite indique à chaque fois quelle information supplémentaire l'observation du runtime apporte.
| Aspect | SBOM (build-time) | OBOM (runtime) |
|---|---|---|
| Moment | À la compilation/packaging | En production |
| Source | Manifestes (package.json, go.mod, etc.) | Processus en cours d'exécution |
| Dépendances | Déclarées et transitives | Effectivement chargées |
| Configuration | Valeurs par défaut | Configuration active |
| Modules optionnels | Listés comme optionnels | Activés ou non |
| Plugins | Potentiels | Réellement chargés |
Exemple concret : une application Python
Section intitulée « Exemple concret : une application Python »Le code ci-dessous choisit son pilote de base de données à partir d'une variable d'environnement et charge ses plugins en parcourant un dossier. Aucun outil d'analyse statique ne peut deviner ce qui sera réellement importé : la décision est prise au démarrage, sur la machine de production.
# app.py - Application avec chargement dynamiqueimport osimport importlib
# Le driver de base de données dépend de l'environnementdb_driver = os.getenv("DB_DRIVER", "sqlite")db_module = importlib.import_module(f"databases.{db_driver}")
# Les plugins sont chargés depuis un dossierfor plugin in os.listdir("plugins/enabled"): importlib.import_module(f"plugins.{plugin}")Ce que le SBOM voit (inventaire produit à partir du build ou de l'environnement packagé) :
flask==3.0.0databases-sqlite==0.9.0databases-postgres==0.9.0 # dépendance extrasplugin-analytics==1.2.0 # plugin disponibleplugin-cache==2.0.0 # plugin disponibleNote : cet exemple est simplifié à des fins pédagogiques. En réalité, les sources de divergence sont plus variées (wheels, entry points, montages runtime, etc.).
Ce qui tourne en production (runtime réel) :
flask==3.0.0databases-postgres==0.9.0 # ← Celui-ci est activéplugin-cache==2.0.0 # ← Seul ce plugin est activépsycopg2==2.9.9 # ← Dépendance transitive de postgreslibpq.so.5 # ← Librairie C chargée par psycopg2Le SBOM liste databases-sqlite (défaut) mais la production utilise databases-postgres. Si une CVE touche psycopg2, un SBOM build-time peut ne pas suffire à dire si cette dépendance est réellement active dans cet environnement. L'OBOM aide alors à distinguer présence théorique et usage effectif.
Ce que l'OBOM capture
Section intitulée « Ce que l'OBOM capture »Un OBOM ne se limite pas à une liste de paquets : il décrit un état de fonctionnement. Les informations utiles se répartissent en cinq familles, dont certaines s'obtiennent en une commande et d'autres demandent un travail d'enrichissement. Savoir laquelle est facile à collecter conditionne l'ordre dans lequel vous construirez votre inventaire.
Les 5 catégories d'informations runtime
Section intitulée « Les 5 catégories d'informations runtime »Le schéma regroupe ces familles et donne, pour chacune, le type de source à interroger.
Détail par catégorie
Section intitulée « Détail par catégorie »La colonne de droite est la plus utile : elle indique le type d'incident que chaque catégorie permet de trancher, depuis une CVE dans une bibliothèque C jusqu'à un paramètre TLS abaissé en production.
| Catégorie | Exemples | Pourquoi c'est important |
|---|---|---|
| Librairies chargées | libssl.so.3, libc.so.6, libpq.so.5 | CVE dans les libs C/système |
| Modules activés | Plugins WordPress, extensions Python | Code actif non visible au build |
| Configuration active | TLS_MIN_VERSION=1.2, DEBUG=false | Paramètres de sécurité effectifs |
| Services connectés | postgres://db:5432, redis://cache:6379 | Dépendances réseau |
| État système | Kernel 5.15, glibc 2.35 | Vulnérabilités système |
Collecter les informations runtime sur Linux
Section intitulée « Collecter les informations runtime sur Linux »Linux expose l'état d'un processus dans le pseudo-système de fichiers /proc, ce qui permet de l'interroger sans instrumenter l'application. Les quatre méthodes qui suivent vont du plus générique au plus spécifique : bibliothèques partagées, modules Python, paquets Node.js, puis variables d'environnement. Aucune ne suffit seule, elles se complètent. Toutes demandent au minimum le droit de lire /proc pour le compte propriétaire du processus.
Méthode 1 : Librairies partagées via /proc
Section intitulée « Méthode 1 : Librairies partagées via /proc »La méthode la plus directe pour lister les librairies chargées par un processus :
# Lister les librairies chargées par un processuscat /proc/$(pgrep -x python3)/maps | grep '\.so' | awk '{print $6}' | sort -u
# Exemple de sortie :/lib/x86_64-linux-gnu/libc.so.6/lib/x86_64-linux-gnu/libpthread.so.0/lib/x86_64-linux-gnu/libm.so.6/usr/lib/x86_64-linux-gnu/libssl.so.3/usr/lib/x86_64-linux-gnu/libcrypto.so.3/usr/lib/python3.11/lib-dynload/_ssl.cpython-311-x86_64-linux-gnu.so#!/bin/bash# Collecte les librairies chargées pour un processus
PID=${1:-}if [ -z "$PID" ]; then echo "Usage: $0 <PID>" exit 1fi
echo "=== Runtime libraries for PID $PID ==="echo ""
# Nom du processusPROC_NAME=$(cat "/proc/$PID/comm")echo "Process: $PROC_NAME"echo ""
# Librairies .so uniquesecho "Shared libraries:"grep -E '\.so(\.[0-9]+)*$' "/proc/$PID/maps" | \ awk '{print $6}' | \ sort -u | \ while read -r lib; do # Afficher le chemin de la librairie if [ -f "$lib" ]; then echo " - $lib" fi done
# Fichiers ouverts (fd)echo ""echo "Open files (non-trivial):"for fd in "/proc/$PID/fd"/*; do cible=$(readlink "$fd" 2>/dev/null) || continue case "$cible" in pipe:*|socket:*|anon_inode:*|/dev/*) continue ;; esac echo " $cible"done# Librairies chargées via lsoflsof -p $(pgrep -x python3) | grep -E '\.so' | awk '{print $9}' | sort -u
# Fichiers ouverts (config, data)lsof -p $(pgrep -x python3) | grep -E '\.(conf|yaml|json|env)' | awk '{print $9}'
# Connexions réseaulsof -p $(pgrep -x python3) -i -n | grep -E '(ESTABLISHED|LISTEN)'Méthode 2 : Modules Python chargés
Section intitulée « Méthode 2 : Modules Python chargés »Pour les applications Python, listez les modules effectivement importés :
import sysimport json
def get_loaded_modules(): """Retourne les modules Python chargés avec leur chemin.""" modules = {} for name, module in sys.modules.items(): if hasattr(module, '__file__') and module.__file__: modules[name] = { "path": module.__file__, "version": getattr(module, '__version__', None) } return modules
if __name__ == "__main__": print(json.dumps(get_loaded_modules(), indent=2))Exemple d'exécution via kubectl exec :
# Via un shell dans le conteneurkubectl exec -it myapp-pod -- python -c "import sys, jsonmods = {k: getattr(v, '__file__', 'builtin') for k, v in sys.modules.items()}print(json.dumps(mods, indent=2))"Méthode 3 : Packages Node.js chargés
Section intitulée « Méthode 3 : Packages Node.js chargés »Pour les applications Node.js :
// Ajouter en fin de votre app ou via un endpoint debug
function getLoadedModules() { const modules = {}; for (const [path, mod] of Object.entries(require.cache)) { if (path.includes('node_modules')) { const parts = path.split('node_modules/'); const pkgPath = parts[parts.length - 1].split('/')[0]; if (!modules[pkgPath]) { try { const pkg = require(`${pkgPath}/package.json`); modules[pkgPath] = pkg.version; } catch { modules[pkgPath] = 'unknown'; } } } } return modules;}
console.log(JSON.stringify(getLoadedModules(), null, 2));Méthode 4 : Variables d'environnement actives
Section intitulée « Méthode 4 : Variables d'environnement actives »Les variables d'environnement définissent souvent le comportement runtime :
# Lister les variables d'environnement d'un processuscat /proc/$(pgrep -x python3)/environ | tr '\0' '\n' | grep -E '^(DB_|API_|FEATURE_|DEBUG|LOG_LEVEL)'
# Dans un conteneur Kuberneteskubectl exec myapp-pod -- env | grep -E '^(DB_|API_|FEATURE_)'Collecter les informations dans les conteneurs
Section intitulée « Collecter les informations dans les conteneurs »Dans un conteneur, la même logique s'applique mais depuis l'extérieur : docker exec ouvre un processus dans les espaces de noms du conteneur et donne accès à son /proc. Deux angles sont possibles et ne donnent pas le même résultat : interroger les processus actifs, ou exporter le système de fichiers pour l'analyser avec un outil de composition. Le second est plus complet sur les paquets installés, le premier est le seul à voir ce qui est réellement chargé.
Scanner un conteneur en cours d'exécution
Section intitulée « Scanner un conteneur en cours d'exécution »Ces commandes supposent que les utilitaires interrogés existent dans l'image ; sur une image distroless ou construite FROM scratch, ni ps ni netstat ne seront présents.
# Lister les processus dans un conteneurdocker exec mycontainer ps aux
# Librairies chargées par le processus principal (PID 1 dans le conteneur)docker exec mycontainer cat /proc/1/maps | grep '\.so' | awk '{print $6}' | sort -u
# Connexions réseau activesdocker exec mycontainer netstat -tuln
# Packages installés (Debian/Ubuntu)docker exec mycontainer dpkg-query -W -f='${Package} ${Version}\n'
# Packages installés (Alpine)docker exec mycontainer apk list --installedScanner le filesystem d'un conteneur en cours d'exécution
Section intitulée « Scanner le filesystem d'un conteneur en cours d'exécution »Syft peut scanner une image Docker, mais aussi le filesystem exporté d'un conteneur :
# Scanner l'image (SBOM classique)syft myregistry/myapp:v1.0.0 -o cyclonedx-json > sbom.json
# Scanner le rootfs d'un conteneur en cours d'exécutionCONTAINER_ID=$(docker ps -qf "name=myapp")docker export $CONTAINER_ID | syft /dev/stdin -o cyclonedx-json > runtime-sbom.jsonScript de collecte OBOM complète
Section intitulée « Script de collecte OBOM complète »Ce script assemble les deux sources précédentes, paquets système et bibliothèques chargées, dans un document CycloneDX 1.6 minimal. Il est volontairement simple et sert de point de départ : la propriété obom:collection-method qu'il inscrit dans les métadonnées permettra plus tard de distinguer ce document d'un SBOM classique.
#!/bin/bash# Prototype de collecte partielle pour un conteneur# Utile pour démonstration ou expérimentation, insuffisant comme OBOM de production
CONTAINER=$1OUTPUT=${2:-obom.json}
if [ -z "$CONTAINER" ]; then echo "Usage: $0 <container_name_or_id> [output.json]" exit 1fi
echo "Collecting OBOM for container: $CONTAINER"
# Créer le JSON de sortiecat > "$OUTPUT" << EOF{ "bomFormat": "CycloneDX", "specVersion": "1.6", "version": 1, "metadata": { "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "component": { "name": "$CONTAINER", "type": "container" }, "properties": [ {"name": "obom:collection-method", "value": "runtime-inspection"} ] }, "components": [EOF
# Collecter les packages systèmeecho " Collecting system packages..."docker exec "$CONTAINER" sh -c 'if command -v dpkg-query >/dev/null; then dpkg-query -W -f "${Package}\t${Version}\n" 2>/dev/nullelif command -v apk >/dev/null; then apk list --installed 2>/dev/null | sed "s/-\([0-9]\)/ \1/"elif command -v rpm >/dev/null; then rpm -qa --queryformat "%{NAME}\t%{VERSION}\n" 2>/dev/nullfi' | while IFS=$'\t' read -r name version; do echo " {\"type\": \"library\", \"name\": \"$name\", \"version\": \"$version\", \"properties\": [{\"name\": \"source\", \"value\": \"os-package\"}]},"done >> "$OUTPUT"
# Collecter les librairies chargées du processus principal# Note : PID 1 n'est pas toujours le processus applicatif (peut être init/wrapper)echo " Collecting loaded libraries..."docker exec "$CONTAINER" cat /proc/1/maps 2>/dev/null | \ grep -E '\.so(\.[0-9]+)*$' | \ awk '{print $6}' | \ sort -u | \ while read lib; do name=$(basename "$lib" | sed 's/\.so.*//') echo " {\"type\": \"library\", \"name\": \"$name\", \"properties\": [{\"name\": \"source\", \"value\": \"loaded-library\"}, {\"name\": \"path\", \"value\": \"$lib\"}]}," done >> "$OUTPUT"
# Fermer le JSON (enlever la dernière virgule et fermer)sed -i '$ s/,$//' "$OUTPUT"echo " ]" >> "$OUTPUT"echo "}" >> "$OUTPUT"
echo "OBOM written to: $OUTPUT"Générer un OBOM avec cdxgen
Section intitulée « Générer un OBOM avec cdxgen »cdxgen est un générateur CycloneDX lié à l'écosystème OWASP/CycloneDX. Il propose des capacités utiles pour aller vers l'OBOM, notamment pour certaines images Linux et VMs via plugins, mais le support reste à valider selon le contexte.
Installation
Section intitulée « Installation »cdxgen se distribue comme paquet npm global et nécessite donc une installation Node.js sur la machine qui lance la collecte.
npm install -g @cyclonedx/cdxgenGénérer un OBOM
Section intitulée « Générer un OBOM »L'option --deep est celle qui change tout : sans elle, cdxgen se contente des manifestes de dépendances et produit un SBOM ordinaire.
# Pour un projet Node.js avec détection des dépendances runtimecdxgen -t nodejs -o obom.json --deep
# Pour une image Docker avec analyse approfondiecdxgen -t docker -o obom.json my-image:latest --deep
# Inclure les informations d'environnementcdxgen -t nodejs -o obom.json --include-envKubernetes : collecter l'OBOM de tous les pods
Section intitulée « Kubernetes : collecter l'OBOM de tous les pods »À l'échelle d'un cluster, la collecte se heurte à deux contraintes : kubectl exec doit être autorisé par le RBAC du compte utilisé, et chaque conteneur d'un pod doit être traité séparément. Prévoyez aussi le cas des pods sans shell : la commande échouera silencieusement et produira un fichier vide, ce qu'il faut savoir interpréter comme une absence de donnée et non comme une absence de composant.
Script de collecte à l'échelle du cluster
Section intitulée « Script de collecte à l'échelle du cluster »Le script parcourt tous les pods d'un namespace et écrit trois fichiers par conteneur : paquets, bibliothèques et connexions réseau.
#!/bin/bash# Collecte l'inventaire runtime de tous les pods
NAMESPACE=${1:-default}OUTPUT_DIR=${2:-./obom-collection}
mkdir -p "$OUTPUT_DIR"
echo "Collecting OBOM for namespace: $NAMESPACE"
# Lister tous les podskubectl get pods -n "$NAMESPACE" -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].name}{"\n"}{end}' | \while IFS=$'\t' read -r pod containers; do for container in $containers; do echo "Processing: $pod / $container"
# Collecter les packages (si possible) kubectl exec -n "$NAMESPACE" "$pod" -c "$container" -- sh -c ' if command -v dpkg-query >/dev/null; then dpkg-query -W -f "${Package}\t${Version}\n" elif command -v apk >/dev/null; then apk list --installed 2>/dev/null fi ' 2>/dev/null > "$OUTPUT_DIR/${pod}_${container}_packages.txt"
# Collecter les librairies chargées kubectl exec -n "$NAMESPACE" "$pod" -c "$container" -- \ cat /proc/1/maps 2>/dev/null | \ grep -E '\.so' | awk '{print $6}' | sort -u \ > "$OUTPUT_DIR/${pod}_${container}_libs.txt"
# Collecter les connexions réseau kubectl exec -n "$NAMESPACE" "$pod" -c "$container" -- \ netstat -tuln 2>/dev/null \ > "$OUTPUT_DIR/${pod}_${container}_network.txt" donedone
echo "Collection complete. Files in: $OUTPUT_DIR"Avec des outils spécialisés
Section intitulée « Avec des outils spécialisés »Ces outils ont des rôles différents, ne les confondez pas :
| Outil | Catégorie | Fonction |
|---|---|---|
| Trivy | Scan de composition | Analyse image/filesystem/k8s, pas inventaire runtime fin |
| Syft | Scan de composition | SBOM depuis images/filesystems |
| Sysdig | Télémétrie runtime | Observation runtime via agent eBPF |
| Falco | Détection comportementale | Alertes sur comportements anormaux, pas OBOM |
| KSOC | Visibilité Kubernetes | Runtime visibility via agent |
Corréler SBOM et OBOM
Section intitulée « Corréler SBOM et OBOM »Collecter les deux inventaires n'a d'intérêt que si vous les comparez. Trois écarts méritent une investigation : un composant présent au runtime mais absent du SBOM, une version qui diffère entre les deux documents, et un composant déclaré mais jamais chargé. Le premier cas est le plus sérieux : il signale du code qui s'exécute sans être passé par votre chaîne de construction.
Workflow de corrélation
Section intitulée « Workflow de corrélation »Le schéma résume l'enchaînement, de la production des deux documents jusqu'à l'identification des composants « fantômes ».
Script de comparaison
Section intitulée « Script de comparaison »Le script attend deux documents CycloneDX et classe les composants en quatre ensembles : présents des deux côtés, propres au SBOM, propres à l'OBOM et versions divergentes. C'est la catégorie obom_only qui doit déclencher une analyse.
#!/usr/bin/env python3"""compare-sbom-obom.py - Compare SBOM et OBOM pour détecter les écarts."""
import jsonimport sysfrom collections import defaultdict
def load_bom(path): """Charge un BOM CycloneDX et extrait les composants.""" with open(path) as f: bom = json.load(f)
components = {} for comp in bom.get("components", []): name = comp.get("name", "") version = comp.get("version", "unknown") components[name] = version return components
def compare(sbom_path, obom_path): """Compare SBOM et OBOM, retourne les écarts.""" sbom = load_bom(sbom_path) obom = load_bom(obom_path)
sbom_names = set(sbom.keys()) obom_names = set(obom.keys())
results = { "sbom_only": list(sbom_names - obom_names), # Déclaré, non chargé "obom_only": list(obom_names - sbom_names), # Chargé, non déclaré "both": list(sbom_names & obom_names), # Match "version_mismatch": [] # Versions différentes }
# Vérifier les versions for name in results["both"]: if sbom[name] != obom[name]: results["version_mismatch"].append({ "name": name, "sbom_version": sbom[name], "obom_version": obom[name] })
return results
if __name__ == "__main__": if len(sys.argv) != 3: print("Usage: compare-sbom-obom.py sbom.json obom.json") sys.exit(1)
results = compare(sys.argv[1], sys.argv[2])
print("=== Composants SBOM-only (déclarés mais non chargés) ===") for name in results["sbom_only"][:10]: print(f" - {name}")
print("\n=== Composants OBOM-only (chargés mais non déclarés) ===") for name in results["obom_only"][:10]: print(f" ⚠️ {name}")
print(f"\n=== Résumé ===") print(f"Match: {len(results['both'])}") print(f"SBOM-only: {len(results['sbom_only'])}") print(f"OBOM-only: {len(results['obom_only'])} ← À investiguer") print(f"Version mismatch: {len(results['version_mismatch'])}")Usage :
python compare-sbom-obom.py sbom.json obom.jsonIntégrer avec GUAC
Section intitulée « Intégrer avec GUAC »GUAC (Graph for Understanding Artifact Composition) peut ingérer des documents CycloneDX et SPDX ainsi que d'autres métadonnées supply chain, ce qui en fait un bon candidat pour corréler SBOM, attestations, vulnérabilités et, potentiellement, des inventaires opérationnels modélisés de manière compatible. La profondeur réelle de cette corrélation dépend toutefois de la qualité et de la structure des documents ingérés.
Ingérer SBOM et OBOM dans GUAC
Section intitulée « Ingérer SBOM et OBOM dans GUAC »guacone collect files accepte les deux documents sans distinction de nature : c'est vous qui donnez du sens à l'ensemble en les ingérant tous les deux, puis en interrogeant le graphe.
# Ingérer le SBOM (build-time)guacone collect files sbom.json
# Ingérer l'OBOM (runtime)guacone collect files obom.json
# Maintenant GUAC peut répondre à des requêtes comme :# "Quels composants sont en production mais pas dans le SBOM ?"# "Ce conteneur utilise-t-il une lib vulnérable à cette CVE ?"Bonnes pratiques OBOM
Section intitulée « Bonnes pratiques OBOM »Un OBOM est un instantané : sa valeur dépend directement de sa fraîcheur et de la discipline avec laquelle il est produit. Deux décisions structurent la démarche : à quelle fréquence collecter, et quelles données s'interdire de capturer. La seconde n'est pas négociable, un inventaire qui contient des secrets devient lui-même un actif à protéger.
Fréquence de collecte
Section intitulée « Fréquence de collecte »Le déclencheur le plus utile n'est pas le calendrier mais le déploiement : c'est à ce moment que l'état runtime change réellement.
| Environnement | Fréquence | Raison |
|---|---|---|
| Production | Quotidien ou après chaque déploiement | Détection rapide des dérives |
| Staging | À chaque déploiement | Validation avant prod |
| Dev/Test | Optionnel | Moins critique |
Ce qu'il faut collecter (et ne pas collecter)
Section intitulée « Ce qu'il faut collecter (et ne pas collecter) »La règle tient en une phrase : collectez des identifiants, jamais des valeurs. Le nom d'une variable d'environnement documente la configuration, sa valeur peut être un mot de passe.
| ✅ Collecter | ❌ Ne pas collecter |
|---|---|
| Noms des packages | Valeurs des secrets |
| Versions | Données utilisateur |
| Chemins des librairies | Contenu des fichiers de config sensibles |
| Noms des variables d'env | Valeurs des variables d'env secrètes |
| Connexions réseau (hosts) | Credentials dans les connexions |
Automatisation recommandée
Section intitulée « Automatisation recommandée »Trois déclencheurs couvrent l'essentiel des besoins et se mettent en place avec des objets Kubernetes standard : un Job après déploiement, un CronJob quotidien et une collecte manuelle en réponse à un incident.
-
À chaque déploiement
Générez un OBOM snapshot après le démarrage de l'application :
# Kubernetes Job post-deployapiVersion: batch/v1kind: Jobmetadata:name: collect-obomspec:template:spec:containers:- name: collectorimage: obom-collector:1.2.0command: ["./collect-obom.sh", "myapp-pod"] -
Quotidiennement
Collecte planifiée pour détecter les dérives (plugins activés, libs mises à jour) :
apiVersion: batch/v1kind: CronJobmetadata:name: daily-obomspec:schedule: "0 2 * * *" # 2h du matinjobTemplate:# ... -
Sur incident de sécurité
Collecte immédiate pour répondre à "est-ce qu'on utilise cette lib vulnérable ?"
Ce qu'un OBOM ne résout pas
Section intitulée « Ce qu'un OBOM ne résout pas »L'OBOM est un complément précieux, mais il ne faut pas remplacer un mythe ("le SBOM suffit") par un autre ("l'OBOM règle le runtime").
Ce que l'OBOM ne garantit pas
Section intitulée « Ce que l'OBOM ne garantit pas »Ces six limites sont structurelles : aucune amélioration de l'outillage ne les lève, parce qu'observer un système à un instant donné ne dit rien de son origine ni de son avenir.
| Limitation | Explication |
|---|---|
| Intégrité de la chaîne de build | L'OBOM observe, il ne prouve pas l'origine |
| Absence de dérive future | Snapshot à un instant T, pas surveillance continue |
| Couverture complète | Dépend de la méthode de collecte et des privilèges |
| Exploitabilité d'une CVE | Présence ≠ vulnérabilité exploitable (c'est le rôle du VEX) |
| Conformité à lui seul | Un élément parmi d'autres dans une posture de sécurité |
| Détection automatique de tout | Plugins/comportements très dynamiques peuvent échapper |
L'OBOM complète, il ne remplace pas
Section intitulée « L'OBOM complète, il ne remplace pas »L'OBOM s'intègre dans un ensemble :
- SBOM : ce qui a été assemblé
- Attestations : preuves de provenance et d'intégrité
- VEX : exploitabilité des vulnérabilités
- Télémétrie runtime : observation continue
- OBOM : état opérationnel observé à un instant T
Niveaux de maturité
Section intitulée « Niveaux de maturité »Situez-vous sur cette échelle pour planifier votre progression :
Niveau 1, Fondations
Section intitulée « Niveau 1, Fondations »Le point de départ : vous savez ce que contient l'image livrée, mais rien de ce qui se passe après son démarrage.
- SBOM d'image finale
- Scan de vulnérabilités
- Inventaire packages OS
Niveau 2, Observation partielle
Section intitulée « Niveau 2, Observation partielle »Vous prenez des instantanés du runtime et les comparez à la main : suffisant pour répondre à une question ponctuelle, trop lent pour un incident large.
- Snapshot runtime (libs chargées, variables d'env)
- Comparaison SBOM / snapshot manual
- Alertes sur écarts majeurs
Niveau 3, Corrélation
Section intitulée « Niveau 3, Corrélation »L'introspection descend jusqu'aux modules du langage et la comparaison est automatisée, avec un historique qui permet de dater une dérive. C'est l'objectif réaliste pour la plupart des équipes.
- Introspection langage (Python/Node/Java)
- Corrélation multi-source automatisée
- Enrichissement vulnérabilités
- Stockage historisé
Niveau 4, Décision pilotée
Section intitulée « Niveau 4, Décision pilotée »L'inventaire alimente un graphe interrogeable et les décisions de risque se prennent à partir de ses réponses, sans collecte manuelle préalable. Peu d'organisations y sont, et ce niveau ne se construit pas avant le précédent.
- OBOM structuré et normalisé
- Corrélation continue (GUAC ou équivalent)
- Knowledge graph interrogeable
- Décision de risque intégrée au workflow
À retenir
Section intitulée « À retenir »Les 5 points essentiels de ce guide :
- SBOM et OBOM sont complémentaires, le SBOM décrit ce qui a été assemblé, l'OBOM observe l'état opérationnel réel
- Plusieurs méthodes de collecte :
/proc/<pid>/maps, introspection langage, scan de filesystem, chacune avec ses limites - La corrélation SBOM/OBOM révèle les écarts, composants "fantômes" absents du SBOM mais actifs en production
- L'OBOM n'est pas magique, c'est un snapshot partiel, pas une surveillance continue ni une preuve d'intégrité
- Automatisez la collecte, après chaque déploiement et quotidiennement, sans jamais collecter les secrets
Prochaines étapes
Section intitulée « Prochaines étapes »XBOM : au-delà du SBOM
Comprenez l'écosystème complet des BOM (SBOM, OBOM, CBOM, etc.) Lire le guide
GUAC
Corrélez SBOM, OBOM et vulnérabilités dans un graphe de connaissances Lire le guide GUAC
Syft
Générez des SBOM au build pour compléter vos OBOM Lire le guide Syft
VEX
Documentez l'exploitabilité des vulnérabilités détectées Lire le guide VEX