Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Dependency-Track : centraliser la gestion des SBOM

51 min de lecture
Améliorer cette page

dependency-track logo

Dependency-Track est une plateforme open source de l'OWASP qui centralise l'analyse des vulnérabilités de tous vos projets à partir de leurs SBOM. Vous lui envoyez l'inventaire de chaque application, il le corrèle en continu avec les bases de vulnérabilités (NVD, GitHub Advisories, OSV) et signale les failles activement exploitées grâce aux catalogues KEV. Une CVE publiée demain sur une dépendance présente dans 50 projets vous alerte sans qu'aucun build ne soit relancé.

Ce guide s'adresse aux équipes DevOps et sécurité qui savent lancer un conteneur et appeler une API avec curl. Il installe Dependency-Track 5.2.0 avec Docker Compose ou Helm, importe des SBOM produits par Trivy et Syft, explique pourquoi un SBOM Syft n'affiche aucun graphe de dépendances et comment le corriger, puis branche alertes et politiques sur le signal KEV. Toutes les commandes ont été rejouées en lab le 14 octobre 2026.

Le parcours suit le trajet réel d'un SBOM : il est produit par un scanner comme Trivy ou Syft, transmis à Dependency-Track via l'API REST, puis corrélé en continu avec les bases de vulnérabilités et les catalogues KEV. Les deux dernières étapes font la différence avec un scanner ponctuel : les notifications, qui préviennent quand une CVE apparaît sur un composant déjà inventorié, et les politiques, qui transforment un constat en règle opposable.

  • Installer Dependency-Track 5.2 avec Docker Compose ou le chart Helm 2.x
  • Activer les sources de vulnérabilités (NVD, OSV, GitHub) et les catalogues KEV
  • Importer des SBOM via l'API REST et automatiser l'envoi dans vos pipelines CI/CD
  • Comprendre pourquoi un SBOM Syft n'a pas de graphe de dépendances, et le réparer
  • Prioriser avec les alertes et les politiques filtrées sur les vulnérabilités exploitées
  • Ancrage SOCLE : ce guide met en œuvre SOCLE-VLN-INV-1, la corrélation multi-sources reliée aux artefacts par SBOM, SOCLE-VLN-INV-2, l'enrichissement par KEV et EPSS, et SOCLE-VLN-INV-3, l'inventaire continu

Un scanner comme Trivy ou Grype détecte les vulnérabilités à un instant T, au moment du build. Dependency-Track garde au contraire l'inventaire de tous vos projets et le réévalue à chaque mise à jour des bases. C'est la seule façon de répondre vite à trois questions qui reviennent toujours :

  • Une nouvelle CVE est publiée pour Log4j : quels projets l'embarquent ?
  • Un auditeur demande la preuve qu'une vulnérabilité a été corrigée partout.
  • L'équipe sécurité veut un tableau de bord de l'exposition de l'entreprise.

Le tableau ci-dessous résume la différence de nature entre les deux approches, qui sont complémentaires : le scanner bloque un build, Dependency-Track surveille ce qui est déjà livré.

ApprocheScanner ponctuel (Trivy, Grype)Dependency-Track
Quand ?À chaque build ou scanEn continu, à chaque synchronisation des bases
Périmètre1 projet à la foisTout le portefeuille
Nouvelles CVEDétectées au prochain scanAlerte sans nouveau build
Exploitation réelleSelon l'outilDrapeau KEV et score EPSS (probabilité d'exploitation) par vulnérabilité
HistoriqueNonOui, métriques en séries temporelles

Cinq notions conditionnent la qualité des résultats. Le SBOM décrit ce que contient votre application, le graphe de dépendances dit qui tire quoi, le PURL et le CPE identifient chaque composant, les sources de vulnérabilités apportent les CVE connues, et les catalogues KEV disent lesquelles sont exploitées. Un maillon faible se traduit toujours de la même manière : des composants réellement vulnérables absents du tableau de bord.

Qu'est-ce qu'un SBOM et son graphe de dépendances ?

Section intitulée « Qu'est-ce qu'un SBOM et son graphe de dépendances ? »

Un SBOM (Software Bill of Materials) est la liste complète des composants d'un logiciel, comme la liste d'ingrédients d'un produit alimentaire. Au format CycloneDX, il porte deux blocs distincts : components, l'inventaire à plat, et dependencies, les relations entre composants. C'est ce second bloc qui permet à Dependency-Track de dessiner l'arbre et de dire qu'une faille de qs arrive par express. Le point d'entrée de l'arbre est le composant décrit dans metadata.component, le projet lui-même.

{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"metadata": {
"component": { "bom-ref": "demo-app", "type": "application", "name": "demo-app" }
},
"components": [
{ "bom-ref": "pkg:npm/express@4.18.2", "name": "express", "version": "4.18.2",
"purl": "pkg:npm/express@4.18.2" },
{ "bom-ref": "pkg:npm/qs@6.11.0", "name": "qs", "version": "6.11.0",
"purl": "pkg:npm/qs@6.11.0" }
],
"dependencies": [
{ "ref": "demo-app", "dependsOn": ["pkg:npm/express@4.18.2"] },
{ "ref": "pkg:npm/express@4.18.2", "dependsOn": ["pkg:npm/qs@6.11.0"] }
]
}

Dependency-Track accepte CycloneDX (format OWASP, recommandé) pour les SBOM et les documents VEX (déclarations d'exploitabilité). Les générateurs courants sont Trivy, Syft et cdxgen. Pour aller plus loin sur le contenu d'un inventaire, voyez le guide SBOM.

Le PURL (Package URL) identifie un paquet dans son écosystème, par exemple pkg:npm/lodash@4.17.20 ou pkg:apk/alpine/zlib@1.3.1-r2. Le CPE (Common Platform Enumeration) est l'identifiant historique de la NVD, par exemple cpe:2.3:a:lodash:lodash:4.17.20:*:*:*:*:*:*:*. La distinction compte en v5 : l'analyseur interne rapproche la NVD uniquement par CPE, et les sources GitHub Advisories et OSV par PURL. Un SBOM sans CPE ne recevra donc aucun résultat de la NVD.

pkg:npm/lodash@4.17.20
pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1
pkg:apk/alpine/openssl@3.5.4-r0
cpe:2.3:a:lodash:lodash:4.17.20:*:*:*:*:*:*:*

Dependency-Track recopie (mirroring) les bases publiques dans sa propre base PostgreSQL, puis compare vos composants à cette copie locale, sans appel extérieur au moment de l'analyse. Sur une installation neuve, seule la NVD est active : nous l'avons mesuré sur la 5.2.0, GitHub et OSV sont désactivés par défaut. Pour des paquets open source identifiés par PURL, OSV est la source la plus utile.

SourceIdentifiantRapprochementPar défautPrérequis
NVD (NIST)CVE-CPEActivéeAucun, téléchargement des flux annuels
GitHub AdvisoriesGHSA-PURLDésactivéeJeton GitHub sans scope, ou GitHub App
OSV (Google)VariablePURLDésactivéeAucun, choix des écosystèmes

À côté de ces miroirs, des analyseurs interrogent des services externes au moment de l'analyse : OSS Index (compte Sonatype obligatoire), Snyk, Trivy (serveur Trivy), VulnDB et Checkmarx. Tous sont désactivés par défaut. L'analyseur interne, actif par défaut, est celui qui exploite les miroirs.

Les catalogues KEV : la preuve d'une exploitation réelle

Section intitulée « Les catalogues KEV : la preuve d'une exploitation réelle »

Un catalogue KEV (Known Exploited Vulnerabilities) liste les vulnérabilités exploitées dans la nature. Là où le CVSS (note de gravité de 0 à 10) note une gravité théorique, une entrée KEV dit que des attaquants s'en servent déjà, ce qui en fait le meilleur critère de priorité. Dependency-Track 5.1 et plus recopient trois catalogues : CISA KEV et ENISA EU KEV, actifs par défaut et sans inscription, et VulnCheck KEV, désactivé par défaut et soumis à un jeton gratuit. Le rapprochement se fait par identifiant CVE, y compris à travers les alias : une alerte GitHub GHSA-jfh8-c2jp-5v3q est marquée KEV parce qu'elle correspond à CVE-2021-44228 (Log4Shell).

Installer Dependency-Track 5.2 avec Docker Compose

Section intitulée « Installer Dependency-Track 5.2 avec Docker Compose »

Dependency-Track se déploie en deux conteneurs et une base : l'apiserver, qui porte toute la logique, le frontend, une application web statique qui interroge l'API, et PostgreSQL. Le fichier ci-dessous reprend le démarrage rapide officiel avec les images épinglées par digest. Dans cette configuration, l'API répond sur le port 8080 et l'interface sur le 8081, l'inverse de nombreux exemples v4.

La consommation mémoire vient surtout de la synchronisation des bases au premier démarrage. Pendant la recopie complète de la NVD, l'apiserver a consommé 683 Mio sur la limite de 2 Gio du fichier officiel, et PostgreSQL 335 Mio.

  • Docker ou Podman avec Compose v2
  • 4 Go de RAM libres pour un essai, davantage en production
  • PostgreSQL 14 ou plus, ici fourni par le fichier Compose
  • Un accès HTTPS sortant vers nvd.nist.gov (NVD), storage.googleapis.com (OSV), www.cisa.gov (CISA KEV), raw.githubusercontent.com (ENISA EU KEV), epss.empiricalsecurity.com (EPSS) et, si vous activez GitHub Advisories, api.github.com

Les mots de passe sont volontairement triviaux pour un essai local : les ports sont publiés sur 127.0.0.1 uniquement. En production, la base contient l'inventaire complet de vos composants, information précieuse pour un attaquant qui cherche une cible vulnérable.

  1. Créer le fichier docker-compose.yml

    name: dependency-track
    services:
    apiserver:
    image: ghcr.io/dependencytrack/apiserver:5.2.0@sha256:7411c390182e6e42156ccf2ab033aa9120a1f37b7ce59e8c2da0d77ec23c0a5f
    depends_on:
    postgres:
    condition: service_healthy
    deploy:
    resources:
    limits:
    memory: 2g
    environment:
    DT_DATASOURCE_URL: "jdbc:postgresql://postgres:5432/dtrack"
    DT_DATASOURCE_USERNAME: "dtrack"
    DT_DATASOURCE_PASSWORD: "dtrack"
    ports:
    - "127.0.0.1:8080:8080"
    volumes:
    - "apiserver-data:/data"
    restart: unless-stopped
    frontend:
    image: ghcr.io/dependencytrack/frontend:5.2.0@sha256:7e68cc134284d6206e813212cf2a20c72f438d76f2094c0f05fd5d5e14602dda
    environment:
    API_BASE_URL: "http://localhost:8080"
    ports:
    - "127.0.0.1:8081:8080"
    restart: unless-stopped
    postgres:
    image: postgres:18.6-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873
    environment:
    POSTGRES_DB: "dtrack"
    POSTGRES_USER: "dtrack"
    POSTGRES_PASSWORD: "dtrack"
    healthcheck:
    test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
    interval: 5s
    timeout: 3s
    retries: 3
    volumes:
    - "postgres-data:/var/lib/postgresql"
    restart: unless-stopped
    volumes:
    apiserver-data: {}
    postgres-data: {}
  2. Démarrer les services

    Fenêtre de terminal
    docker compose up -d

    Compose attend que PostgreSQL soit healthy avant de lancer l'apiserver, grâce à condition: service_healthy. La migration du schéma s'exécute au premier démarrage.

  3. Vérifier que l'API répond

    Fenêtre de terminal
    curl -s http://localhost:8080/api/version | jq .

    En lab, l'API a répondu 13 secondes après docker compose up -d. La sortie doit afficher "version": "5.2.0" :

    {
    "framework": {
    "uuid": "e56f644e-31f0-4bbf-be71-c6a5a019b805",
    "timestamp": "2026-10-08T09:04:45Z",
    "version": "5.2.0",
    "name": "Alpine"
    },
    "uuid": "7cd762ce-27e3-407c-9d2c-b6067a09c313",
    "application": "Dependency-Track",
    "timestamp": "2026-10-08T09:05:46Z",
    "version": "5.2.0"
    }
  4. Ouvrir l'interface

    Ouvrez http://localhost:8081 et connectez-vous avec admin / admin. Dependency-Track impose un nouveau mot de passe dès la première connexion.

La v5 sépare l'API publique d'un serveur de gestion sur le port 9000, qui expose la santé et les métriques Prometheus. Le fichier Compose ne publie pas ce port : on l'interroge depuis le conteneur, avec wget puisque l'image 5.2 ne contient plus curl. L'ancien /api/v1/health de la v4 renvoie désormais 404.

Fenêtre de terminal
docker compose exec apiserver wget -q -Y off -T 3 -O - http://127.0.0.1:9000/health

La réponse doit commencer par {"status":"UP" et lister les migrations à l'état COMPLETED. Second point à connaître : les secrets saisis dans l'interface (jetons GitHub, VulnCheck, webhooks) sont chiffrés en base par une clé KEK (Key Encryption Key, la clé qui protège les secrets) créée au premier démarrage dans /data/.dependency-track/keys/secret-management-kek.json, donc dans le volume apiserver-data. Une sauvegarde de PostgreSQL sans ce volume restaure des secrets indéchiffrables : sauvegardez les deux ensemble.

Le chart communautaire dependency-track 2.x cible la v5 (la version 2.7.0 déploie la 5.2.0). Il ne crée pas de PostgreSQL et ne génère pas la clé KEK : il faut fournir les deux. Le chart hyades, qui servait d'aperçu de la v5, est déprécié. Cette séquence a été rejouée sur un cluster kind jetable, avec un PostgreSQL 18.6 déployé à part dans le même namespace.

Fenêtre de terminal
helm repo add dependencytrack https://dependencytrack.github.io/helm-charts
helm repo update
kubectl create namespace dependency-track
# Identifiants de la base (PostgreSQL 14+ déjà joignable sous le nom "postgresql")
kubectl -n dependency-track create secret generic dtrack-db \
--from-literal=username=dtrack --from-literal=password='MotDePasseFort'
# Clé de chiffrement des secrets (KEK), à conserver hors du cluster
kubectl -n dependency-track create secret generic dtrack-kek \
--from-literal=kek="$(openssl rand -base64 32)"
helm install dependency-track dependencytrack/dependency-track \
--version 2.7.0 \
--namespace dependency-track \
--set database.jdbcUrl=jdbc:postgresql://postgresql:5432/dtrack \
--set database.existingSecret.name=dtrack-db \
--set secretManagement.database.kek.existingSecret.name=dtrack-kek \
--set apiServer.image.tag=sha256:7411c390182e6e42156ccf2ab033aa9120a1f37b7ce59e8c2da0d77ec23c0a5f \
--set frontend.image.tag=sha256:7e68cc134284d6206e813212cf2a20c72f438d76f2094c0f05fd5d5e14602dda \
--set frontend.apiBaseUrl=https://dtrack-api.mondomaine.fr

Le chart accepte un digest dans image.tag et rend alors docker.io/dependencytrack/apiserver@sha256:…, le même digest que sur ghcr.io. Vérifiez le déploiement avec kubectl -n dependency-track get pods : l'apiserver passe Ready une fois la migration terminée. La clé KEK générée par openssl rand est au format config, simple mais sans rotation ; le mode keyset (Tink) la permet.

Deux points restent à votre charge. La base PostgreSQL doit exister avant l'installation : une instance managée de votre fournisseur ou un opérateur Kubernetes comme CloudNativePG ; en lab, un simple Deployment PostgreSQL 18.6 et son Service nommé postgresql ont suffi. L'API doit être joignable à l'URL passée dans frontend.apiBaseUrl, puisque c'est le navigateur qui l'appelle : exposez le service dependency-track-api-server (port 8080) par un Ingress ou une Gateway, au même titre que le frontend.

La migration v4 vers v5 est manuelle et ne se fait pas en changeant le tag de l'image. L'instance v4 doit être en 4.14.2 au minimum et arrêtée pendant l'opération ; l'outil v4-migrator (ghcr.io/dependencytrack/v4-migrator) recopie les données dans une base v5 neuve. Les modèles de notification v4 sont à réécrire (schéma Protobuf, valeurs préfixées comme GROUP_NEW_VULNERABILITY), et la recherche floue Lucene disparaît. La procédure officielle, avec la répétition à blanc recommandée, est dans le guide Migrating from v4 to v5. La v4 reste en maintenance jusqu'en décembre 2026.

Trois réglages rendent l'installation exploitable : le compte admin, livré avec un mot de passe public, les sources de vulnérabilités, sans lesquelles l'outil inventorie sans alerter, et la clé d'API que consommeront vos pipelines. Tant que la première synchronisation NVD n'est pas terminée, un projet peut afficher zéro vulnérabilité tout en étant affecté.

Tant que le mot de passe initial n'a pas été changé, l'API de connexion renvoie FORCE_PASSWORD_CHANGE avec un code 401 au lieu d'un jeton. Le changement se fait dans l'interface à la première connexion, ou par l'API :

Fenêtre de terminal
# Première tentative : renvoie FORCE_PASSWORD_CHANGE (HTTP 401)
curl -s -X POST "http://localhost:8080/api/v1/user/login" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'username=admin&password=admin'
# Saisir le nouveau mot de passe sans qu'il apparaisse dans l'historique du shell
read -rs -p "Nouveau mot de passe admin : " NEWPASS; echo
# Changer le mot de passe (HTTP 200 attendu)
curl -s -X POST "http://localhost:8080/api/v1/user/forceChangePassword" \
--data-urlencode 'username=admin' --data-urlencode 'password=admin' \
--data-urlencode "newPassword=${NEWPASS}" --data-urlencode "confirmPassword=${NEWPASS}"
# Obtenir un jeton de session pour la suite
TOKEN=$(curl -s -X POST "http://localhost:8080/api/v1/user/login" \
--data-urlencode 'username=admin' --data-urlencode "password=${NEWPASS}")

--data-urlencode envoie le même formulaire que -d, mais encode les caractères spéciaux d'un mot de passe fort (&, +, %), qui casseraient sinon la requête.

La page Administration > Vulnerability Sources regroupe la NVD, GitHub et OSV ; chaque panneau propose Mirror now pour lancer la première synchronisation sans attendre la planification. La v5 télécharge la NVD sous forme de flux annuels : il n'y a plus de clé d'API NVD à saisir.

Active par défaut. La recopie complète part de l'année en cours et remonte jusqu'à 2002 : en lab, elle a traité 420 230 enregistrements en 14 min 19 s. La progression se suit dans les journaux :

Fenêtre de terminal
docker compose logs -f apiserver | grep -E 'NvdVulnDataSource|Completed mirror'

Rappel : la NVD ne produit de résultat que pour les composants qui portent un CPE.

Les catalogues CISA KEV et ENISA EU KEV se synchronisent seuls peu après le premier démarrage. En lab, le journal a affiché 1 741 assertions CISA et 39 assertions ENISA moins d'une minute après le lancement. Le réglage se fait dans Administration > KEV Sources, et la planification par défaut est quotidienne à 02:00 UTC.

Fenêtre de terminal
docker compose logs apiserver | grep MirrorKevDataSourceActivity

Si le miroir CISA échoue depuis certains hébergeurs, la documentation officielle propose de remplacer l'URL du flux par le miroir GitHub de la CISA, https://raw.githubusercontent.com/cisagov/kev-data/main/known_exploited_vulnerabilities.json. VulnCheck KEV, plus large et souvent plus rapide, demande un jeton communautaire gratuit.

Un pipeline s'authentifie avec une clé d'API rattachée à une équipe qui ne porte que les permissions nécessaires. Depuis la 5.2, les comptes de service (préfixe svc:) sont l'alternative recommandée par le projet pour les nouvelles intégrations ; les clés d'équipe restent prises en charge et c'est ce chemin, rejoué en lab, que montre ce guide. Un compte de service se crée dans Administration > Access Management ; sa procédure et la fédération d'identité de charge (workload identity, qui supprime la clé longue durée) sont décrites dans la documentation officielle, Service accounts.

  1. Créer l'équipe

    Fenêtre de terminal
    TEAM_UUID=$(curl -s -X PUT "http://localhost:8080/api/v1/team" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"name":"CI-CD-Integration"}' | jq -r .uuid)
    echo "$TEAM_UUID"
  2. Accorder les permissions

    Fenêtre de terminal
    for PERM in BOM_UPLOAD PROJECT_CREATION_UPLOAD VIEW_PORTFOLIO VIEW_VULNERABILITY; do
    curl -s -o /dev/null -w "$PERM %{http_code}\n" -X POST \
    "http://localhost:8080/api/v1/permission/${PERM}/team/${TEAM_UUID}" \
    -H "Authorization: Bearer $TOKEN"
    done

    Chaque ligne doit afficher le code 200.

  3. Générer la clé

    Fenêtre de terminal
    curl -s -X PUT "http://localhost:8080/api/v1/team/${TEAM_UUID}/key" \
    -H "Authorization: Bearer $TOKEN" | jq -r .key

    La clé commence par odt_ et compte 46 caractères. La réponse contient aussi un maskedKey (odt_aHdRHy6Z****…) : c'est la seule forme que l'interface affichera ensuite.

Une équipe CI/CD n'a besoin que des quatre premières lignes du tableau : VIEW_PORTFOLIO et VIEW_VULNERABILITY servent à relire les résultats dans le pipeline. Les permissions suivantes autorisent la modification des verdicts d'analyse et des politiques : accordées à un pipeline, elles permettraient à une clé compromise de marquer une vulnérabilité comme non applicable et de faire disparaître l'alerte au lieu de la corriger. La v5 ajoute aussi des variantes fines comme VULNERABILITY_ANALYSIS_READ.

PermissionRôle
BOM_UPLOADEnvoyer des SBOM
PROJECT_CREATION_UPLOADCréer le projet automatiquement à l'envoi
VIEW_PORTFOLIOLire les projets et composants
VIEW_VULNERABILITYLire les vulnérabilités
VULNERABILITY_ANALYSISModifier l'analyse des vulnérabilités
PORTFOLIO_MANAGEMENTGérer les projets et composants
POLICY_MANAGEMENTGérer les politiques
SYSTEM_CONFIGURATIONConfiguration système

Dependency-Track ne scanne rien lui-même : il consomme des SBOM produits ailleurs. La qualité de l'analyse dépend donc du générateur, de ses identifiants (PURL, CPE) et de son bloc dependencies. Un composant sans identifiant exploitable reste listé mais n'est jamais rapproché d'une CVE, et un SBOM sans relation vers la racine donne un projet sans arbre.

Les deux outils produisent du CycloneDX 1.7 dans les versions testées (Trivy 0.75.0, Syft 1.54.1). Les commandes sont équivalentes ; leur différence de contenu fait l'objet de la section « Syft ou Trivy ».

Fenêtre de terminal
# Trivy : un projet (lit package-lock.json, pom.xml, go.sum...)
trivy fs --format cyclonedx --output sbom.json .
# Trivy : une image de conteneur
trivy image --format cyclonedx --output nginx-sbom.json \
nginx:1.29.2-alpine@sha256:61e01287e546aac28a3f56839c136b31f590273f3b41187a36f46f6a03bbfe22
# Syft : un projet
syft dir:. -o cyclonedx-json=sbom-syft.json
# Syft : une image, sans inventaire de fichiers (voir plus bas)
SYFT_FILE_METADATA_SELECTION=none syft \
nginx:1.29.2-alpine@sha256:61e01287e546aac28a3f56839c136b31f590273f3b41187a36f46f6a03bbfe22 \
-o cyclonedx-json=nginx-sbom-syft.json

Les fichiers Syft portent un suffixe -syft parce qu'ils passent par une correction du graphe avant l'envoi (voir la section « Syft ou Trivy »). Les exemples d'envoi qui suivent utilisent le sbom.json de Trivy.

Un projet d'exemple : une application Java avec Log4j 2.14.1

Section intitulée « Un projet d'exemple : une application Java avec Log4j 2.14.1 »

Pour reproduire les sorties de ce guide (vulnérabilités KEV, alerte, violations de politique), créez un projet Maven minimal qui déclare log4j-core 2.14.1, la version touchée par Log4Shell. Trivy lit le pom.xml et en tire trois composants : l'application, log4j-core et log4j-api.

Fenêtre de terminal
mkdir legacy-app && cd legacy-app
cat > pom.xml <<'EOF'
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>fr.exemple</groupId>
<artifactId>legacy-app</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
</project>
EOF
trivy fs --format cyclonedx --output sbom.json .

Les avis Maven arrivent par OSV (écosystème Maven) ou GitHub Advisories : activez l'un des deux avant l'envoi, sans quoi le projet reste à zéro vulnérabilité.

Le formulaire multipart (POST /api/v1/bom) est la méthode la plus simple et ne souffre d'aucune limite de longueur de ligne de commande. Le champ autoCreate crée le projet s'il n'existe pas, à partir du couple projectName et projectVersion. Corollaire : une faute de frappe sur le nom crée un projet parallèle au lieu d'échouer.

Fenêtre de terminal
read -rs -p "Clé d'API : " API_KEY; echo
RESP=$(curl -s -X POST "http://localhost:8080/api/v1/bom" \
-H "X-Api-Key: $API_KEY" \
-F "autoCreate=true" \
-F "projectName=legacy-app" \
-F "projectVersion=1.0.0" \
-F "bom=@sbom.json")
echo "$RESP" | jq .
BOM_TOKEN=$(echo "$RESP" | jq -r .token)
PROJECT_UUID=$(echo "$RESP" | jq -r .projectUuid)

Les deux méthodes renvoient un jeton de traitement et, en v5, l'UUID du projet. Les variables BOM_TOKEN et PROJECT_UUID capturées ici servent à toutes les commandes suivantes du guide :

{
"token": "01a13926-efdb-75a1-a1d7-903fb5297923",
"projectUuid": "f43f0501-daf5-4bb0-b5e9-a1324b80bf6c"
}

La taille maximale d'un SBOM est de 100 Mio par défaut (DT_BOM_UPLOAD_MAX_SIZE_BYTES) ; au-delà, l'API répond 413.

Le traitement est asynchrone : le jeton permet de savoir quand l'inventaire et la première analyse sont terminés.

Fenêtre de terminal
curl -s "http://localhost:8080/api/v1/bom/token/${BOM_TOKEN}" \
-H "X-Api-Key: $API_KEY"

La sortie passe de "processing":true à :

{"processing":false,"status":"COMPLETED"}

Syft ou Trivy : pourquoi le graphe de dépendances reste vide

Section intitulée « Syft ou Trivy : pourquoi le graphe de dépendances reste vide »

Avec Syft 1.54.1, Dependency-Track 5.2 importe bien les composants et leurs relations, mais le projet n'a aucune dépendance directe : la vue Dependency Graph n'a pas de point de départ et reste vide. Avec Trivy 0.75.0, le même projet a un arbre complet. La cause n'est pas le format des bom-ref en PURL : Syft n'écrit aucune entrée dependencies pour le composant racine décrit dans metadata.component, alors que Trivy y rattache l'application ou le système d'exploitation.

Le schéma compare les deux arbres obtenus pour le même projet Node.js : à gauche, la racine Trivy est reliée au fichier de verrouillage, qui mène aux bibliothèques ; à droite, la racine Syft reste orpheline alors que les bibliothèques sont bien reliées entre elles.

Deux arbres de dépendances du même projet : avec Trivy, la racine metadata.component est reliée à package-lock.json puis à express et qs ; avec Syft, express et qs sont reliés via demo-app, mais rien ne part de la racine.SBOM Syft 1.54.1Projetmetadata.componentdemo-app 1.0.0express 4.18.2qs 6.11.0SBOM Trivy 0.75.0Projetmetadata.componentpackage-lock.jsonexpress 4.18.2qs 6.11.0
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Le test a porté sur deux cibles analysées par les deux outils, puis envoyées dans quatre projets d'une instance 5.2.0 : un projet Node.js (express 4.18.2, lodash 4.17.20, 70 paquets dans package-lock.json) et l'image nginx:1.29.2-alpine. La colonne de droite est la valeur renvoyée par GET /api/v1/dependencyGraph/project/{uuid}/directDependencies.

CibleGénérateurComposantsRelations dependsOnLien depuis la racineDépendances directes du projet
Projet Node.jsTrivy 0.75.070128Oui1 (package-lock.json)
Projet Node.jsSyft 1.54.171129Non0
Image nginxTrivy 0.75.071179Oui1 (alpine)
Image nginxSyft 1.54.11 306165Non0

Les relations entre bibliothèques existent dans les deux cas : 28 composants du projet Syft portent bien leurs propres enfants dans Dependency-Track. Il manque seulement le premier maillon. Deuxième écart sur l'image : Syft inventorie par défaut les fichiers possédés par les paquets (1 235 composants de type file), qui gonflent le projet sans rien apporter à l'analyse. SYFT_FILE_METADATA_SELECTION=none ramène l'inventaire à 71 composants.

En attendant que Syft produise ce lien, une transformation jq ajoute l'entrée manquante : elle rattache au composant racine toutes les bibliothèques dont aucun autre composant ne dépend, c'est-à-dire les dépendances de premier niveau.

Fenêtre de terminal
jq '(.metadata.component["bom-ref"]) as $root
| ([.dependencies[]?.dependsOn[]?] | unique) as $children
| ([.components[] | select(.type == "library") | .["bom-ref"]] - $children) as $top
| .dependencies = ([.dependencies[]? | select(.ref != $root)] + [{ref: $root, dependsOn: $top}])' \
sbom-syft.json > sbom-syft-graphe.json

Envoyé dans Dependency-Track, le SBOM corrigé donne 1 dépendance directe au projet Node.js (demo-app@1.0.0, que Syft déclare et qui dépend d'express et de lodash) et 13 à l'image nginx (les paquets Alpine de premier niveau). C'est une reconstruction : pour une image, ces 13 paquets sont ceux que rien d'autre ne requiert, pas forcément ceux qu'a installés le Dockerfile.

Le graphe n'est pas la seule différence. Syft ajoute des CPE devinés à chaque paquet, Trivy n'en met pas. Résultat mesuré après une recopie complète de NVD, OSV (npm, Alpine) et sans GitHub Advisories :

ProjetTrivy 0.75.0Syft 1.54.1
Node.js18 (avis GitHub via OSV)31 (les 18 mêmes + 13 entrées NVD)
Image nginx25 (OSV Alpine)39 (les 25 mêmes + 14 entrées NVD)

Sur le projet Node.js, les 13 entrées NVD de Syft sont toutes des doublons : les mêmes CVE déjà présentes sous leur identifiant GHSA-. Sur l'image, les 14 entrées NVD portent sur libpng, zlib, musl et c-ares, rapprochées par CPE sans tenir compte des correctifs rétroportés par Alpine : elles demandent une vérification au cas par cas. Retenez surtout l'inverse : un SBOM Trivy sur une instance où seule la NVD est active ne produit aucun résultat, faute de CPE. Activez OSV ou GitHub dans ce cas.

Les endpoints ci-dessous alimentent aussi l'interface et se prêtent à un export vers un outil de suivi. Deux changements v5 à connaître : les listes sont paginées à 100 éléments par défaut (pageSize et pageNumber), et les métriques du portefeuille sont un instantané recalculé périodiquement, alors que celles d'un projet sont à jour après chaque analyse.

L'endpoint attend l'UUID du projet, renvoyé à l'envoi du SBOM. Le champ source indique quelle base a produit le résultat, utile quand deux sources divergent sur la sévérité. Les findings exposent aussi isKev et epssScore pour la priorisation.

Fenêtre de terminal
curl -s "http://localhost:8080/api/v1/finding/project/${PROJECT_UUID}?pageSize=500" \
-H "X-Api-Key: $API_KEY" \
| jq -c '.[] | {composant: .component.name, id: .vulnerability.vulnId,
source: .vulnerability.source, severite: .vulnerability.severity,
kev: .vulnerability.isKev, epss: .vulnerability.epssScore}'

Extrait réel sur le projet legacy-app, qui embarque log4j-core 2.14.1 :

{"composant":"log4j-core","id":"GHSA-jfh8-c2jp-5v3q","source":"GITHUB","severite":"CRITICAL","kev":true,"epss":0.99999}
{"composant":"log4j-core","id":"GHSA-7rjr-3q55-vv33","source":"GITHUB","severite":"CRITICAL","kev":true,"epss":0.99977}
{"composant":"log4j-core","id":"GHSA-p6xc-xr62-6r2g","source":"GITHUB","severite":"HIGH","kev":false,"epss":0.99999}

Deux des huit vulnérabilités du composant sont KEV : Log4Shell (CVE-2021-44228) et CVE-2021-45046, reconnues par leurs alias alors que l'avis est d'origine GitHub. Le troisième exemple montre pourquoi on croise les signaux : un EPSS élevé sans entrée KEV reste à surveiller.

Lire les métriques d'un projet et du portefeuille

Section intitulée « Lire les métriques d'un projet et du portefeuille »

Le bloc metrics d'un projet compte désormais aussi les vulnérabilités KEV (champ kev). Pour le portefeuille, un appel à /refresh force le recalcul quand on veut un chiffre immédiat après un envoi.

Fenêtre de terminal
# Un projet
curl -s "http://localhost:8080/api/v1/project/${PROJECT_UUID}" -H "X-Api-Key: $API_KEY" \
| jq '{name, version, metrics: (.metrics | {critical, high, medium, low, kev, components, vulnerableComponents})}'
# Le portefeuille (instantané)
curl -s "http://localhost:8080/api/v1/metrics/portfolio/current" -H "X-Api-Key: $API_KEY" \
| jq '{projects, vulnerableProjects, critical, high, medium, low}'

L'indicateur le plus parlant pour un pilotage est le rapport entre projects et vulnerableProjects, qui se suit dans le temps bien mieux qu'un total brut de CVE.

Les trois pipelines suivants appliquent le même schéma : générer le SBOM pendant le build, puis l'envoyer avec une clé d'API stockée en secret. Ils n'échouent pas sur la présence de vulnérabilités : l'envoi est asynchrone et le job se termine avant l'analyse. Bloquer une release suppose d'attendre le jeton puis d'interroger les violations de politique du projet.

La projectVersion alimentée par CI_COMMIT_REF_NAME crée un projet par branche, ce qui permet de comparer une branche à main mais multiplie les entrées si les branches ne sont jamais nettoyées. DTRACK_URL et DTRACK_API_KEY sont déclarées comme variables CI/CD masquées du projet.

.gitlab-ci.yml
stages:
- build
- security
generate_sbom:
stage: build
image:
name: aquasec/trivy:0.75.0@sha256:af6acf9a6b85dfe389a1941505c0ce9efef52a4719635e1a962f022a3d855daa
entrypoint: [""]
script:
- trivy fs --format cyclonedx --output sbom.json .
artifacts:
paths:
- sbom.json
expire_in: 1 week
upload_sbom:
stage: security
image: curlimages/curl:8.22.0@sha256:58adaa4e8dca9c988bae2aba4ab3434a0bb2da16bbe3f92dec39ec7785166777
needs:
- generate_sbom
script:
- >
curl --fail -s -X POST "${DTRACK_URL}/api/v1/bom"
-H "X-Api-Key: ${DTRACK_API_KEY}"
-F "autoCreate=true"
-F "projectName=${CI_PROJECT_NAME}"
-F "projectVersion=${CI_COMMIT_REF_NAME}"
-F "bom=@sbom.json"

Le workflow ne tourne que sur push vers main : les pull requests de dépôts forkés n'ont pas accès aux secrets, et un workflow qui en dépend échouerait de toute façon. Les secrets passent par env: plutôt que par une expression ${{ }} dans le script, ce qui évite l'injection de modèle signalée par zizmor. L'action anchore/sbom-action télécharge Syft au démarrage (la v0.24.3 prend la 1.54.0 par défaut, syft-version l'aligne sur la 1.54.1 mesurée dans ce guide) : le workflow applique donc la correction jq de la section « Syft ou Trivy » avant l'envoi, faute de quoi le projet n'aurait aucun graphe. jq est préinstallé sur les runners ubuntu-latest.

.github/workflows/sbom.yml
name: SBOM Upload
on:
push:
branches: [main]
permissions: {}
jobs:
sbom:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Generate SBOM
uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3
with:
syft-version: v1.54.1
format: cyclonedx-json
output-file: sbom-syft.json
upload-artifact: false
- name: Rattacher la racine du graphe (SBOM Syft)
run: |
jq '(.metadata.component["bom-ref"]) as $root
| ([.dependencies[]?.dependsOn[]?] | unique) as $children
| ([.components[] | select(.type == "library") | .["bom-ref"]] - $children) as $top
| .dependencies = ([.dependencies[]? | select(.ref != $root)] + [{ref: $root, dependsOn: $top}])' \
sbom-syft.json > sbom.json
- name: Upload to Dependency-Track
env:
DTRACK_URL: ${{ secrets.DTRACK_URL }}
DTRACK_API_KEY: ${{ secrets.DTRACK_API_KEY }}
PROJECT_NAME: ${{ github.repository }}
PROJECT_VERSION: ${{ github.ref_name }}
run: |
curl --fail -s -X POST "${DTRACK_URL}/api/v1/bom" \
-H "X-Api-Key: ${DTRACK_API_KEY}" \
-F "autoCreate=true" \
-F "projectName=${PROJECT_NAME}" \
-F "projectVersion=${PROJECT_VERSION}" \
-F "bom=@sbom.json"

credentials() injecte la clé dans l'environnement du job ; Jenkins masque sa valeur dans les journaux, mais pas dans un fichier que le script écrirait lui-même. Ici la version du projet suit le BUILD_NUMBER, ce qui crée une entrée par build et rend l'historique verbeux : préférez le tag Git pour une release.

// Jenkinsfile
pipeline {
agent any
environment {
DTRACK_URL = 'https://dependency-track.mondomaine.fr'
DTRACK_API_KEY = credentials('dtrack-api-key')
}
stages {
stage('Generate SBOM') {
steps {
sh '''
docker run --rm -v "$PWD:/app" \
aquasec/trivy:0.75.0@sha256:af6acf9a6b85dfe389a1941505c0ce9efef52a4719635e1a962f022a3d855daa \
fs --format cyclonedx --output /app/sbom.json /app
'''
}
}
stage('Upload SBOM') {
steps {
sh '''
curl --fail -s -X POST "${DTRACK_URL}/api/v1/bom" \
-H "X-Api-Key: ${DTRACK_API_KEY}" \
-F "autoCreate=true" \
-F "projectName=${JOB_NAME}" \
-F "projectVersion=${BUILD_NUMBER}" \
-F "bom=@sbom.json"
'''
}
}
}
}

C'est la fonction qui justifie la centralisation : une CVE publiée demain sur un composant présent dans plusieurs projets déclenche une alerte sans nouveau build. Le piège est inverse de celui des scanners : mal filtrées, les notifications deviennent un bruit que plus personne ne lit. La v5 apporte la réponse avec les expressions de filtre en CEL (Common Expression Language), qui permettent par exemple de ne notifier que les vulnérabilités KEV.

Les canaux sont des publishers déjà présents dans l'instance : Slack, Microsoft Teams, Mattermost, Webex, Jira, email, Kafka, webhook et console. Une alerte associe un publisher, une portée (PORTFOLIO ou SYSTEM), des groupes d'événements et, optionnellement, un filtre.

  1. Ouvrir Administration > Notifications > Alerts et créer une alerte de portée Portfolio.

  2. Choisir le publisher (Slack par exemple) et renseigner sa destination : pour Slack, le seul paramètre est l'URL de webhook (destinationUrl), qui est un secret à part entière.

  3. Cocher les groupes voulus, par exemple NEW_VULNERABILITY.

  4. Saisir une expression de filtre pour réduire le bruit, par exemple subject.vulnerability.is_kev pour ne recevoir que les vulnérabilités exploitées.

Le même réglage se fait par l'API, en deux temps (création puis mise à jour du filtre). Dans ce test, le publisher console écrit les notifications dans le journal de l'apiserver :

Fenêtre de terminal
PUB=$(curl -s "http://localhost:8080/api/v1/notification/publisher" -H "Authorization: Bearer $TOKEN" \
| jq -r '.[] | select(.extensionName=="console") | .uuid')
RULE=$(curl -s -X PUT "http://localhost:8080/api/v1/notification/rule" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "{\"name\":\"KEV uniquement\",\"scope\":\"PORTFOLIO\",\"level\":\"INFORMATIONAL\",\"publisher\":{\"uuid\":\"$PUB\"}}" \
| jq -r .uuid)
curl -s -X POST "http://localhost:8080/api/v1/notification/rule" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "{\"uuid\":\"$RULE\",\"name\":\"KEV uniquement\",\"scope\":\"PORTFOLIO\",\"level\":\"INFORMATIONAL\",\"enabled\":true,\"notifyOn\":[\"NEW_VULNERABILITY\"],\"filterExpression\":\"subject.vulnerability.is_kev\"}"

À l'envoi du SBOM contenant log4j-core 2.14.1, le journal a reçu 2 notifications GROUP_NEW_VULNERABILITY, une par vulnérabilité KEV, et aucune pour les six autres.

Quels événements déclenchent une notification ?

Section intitulée « Quels événements déclenchent une notification ? »

Deux groupes sont à distinguer soigneusement. NEW_VULNERABILITY signale une CVE nouvellement rapprochée d'un composant, y compris sur un projet déjà inventorié ; NEW_VULNERABLE_DEPENDENCY signale un composant vulnérable que vous venez d'introduire. Le premier est un signal de veille, le second un défaut de développement. Les groupes …_SUMMARY sont planifiés par une expression cron et envoient un résumé.

GroupeDéclencheur
NEW_VULNERABILITYNouvelle vulnérabilité sur un composant
NEW_VULNERABLE_DEPENDENCYNouveau composant vulnérable ajouté
POLICY_VIOLATIONViolation d'une politique
BOM_PROCESSED / BOM_PROCESSING_FAILEDFin ou échec de traitement d'un SBOM
VULNERABILITY_RETRACTEDVulnérabilité retirée par sa source
PROJECT_AUDIT_CHANGEChangement de statut d'analyse
NEW_VULNERABILITIES_SUMMARYRésumé planifié des nouvelles vulnérabilités
NEW_POLICY_VIOLATIONS_SUMMARYRésumé planifié des violations

Les modèles de message utilisent Pebble et un schéma Protobuf : un modèle écrit pour la v4 doit être réécrit (champs suffixés List, valeurs préfixées comme LEVEL_INFORMATIONAL). Depuis la 5.2, les connexions sortantes (notifications, sources de données, analyseurs) sont filtrées par DT_OUTBOUND_ALLOWED_DESTINATIONS (par défaut external,private) : une destination en boucle locale comme localhost doit y être ajoutée, et les adresses link-local ou de métadonnées cloud restent refusées. Les charges utiles des webhooks peuvent être signées en HMAC-SHA256.

Les politiques évaluent chaque composant et lèvent une violation quand une condition est remplie. En v5, le moteur repose sur CEL : l'éditeur garde ses conditions structurées et ajoute une condition Expression pour les cas qu'elles ne couvrent pas, dont le signal KEV.

La condition License est la seule qui traite de conformité juridique et non de sécurité : c'est le moyen d'attraper l'arrivée d'une dépendance sous licence incompatible. Age et Version distance servent d'indicateurs indirects d'abandon ou de retard, et Expression accepte une règle CEL libre.

ConditionCe qu'elle teste
SeveritySévérité des vulnérabilités (CRITICAL, HIGH...)
EPSSScore EPSS le plus élevé du composant
Vulnerability IDUne vulnérabilité précise
CWECatégorie de faiblesse (CWE-79, CWE-89...)
License / License groupLicences ou groupes de licences
Coordinates / Package URL / CPEUn composant précis
Age / Version distanceAncienneté, retard sur la dernière version
Component hashEmpreinte d'un composant
ExpressionExpression CEL libre

Le niveau Fail n'interrompt aucun build par lui-même : Dependency-Track enregistre la violation et émet un événement POLICY_VIOLATION, à charge pour votre pipeline d'interroger l'API et de décider. Warn et Info servent à la priorisation. Une politique sans projet assigné s'applique à tout le portefeuille.

  1. Ouvrir Policy Management > Policies et cliquer sur Create Policy

  2. Ajouter une condition, par exemple Severity IS CRITICAL

  3. Choisir le niveau de violation : Fail, Warn ou Info

  4. Restreindre la portée si besoin, à des projets ou à des tags

Exemple : signaler tout composant touché par une vulnérabilité KEV

Section intitulée « Exemple : signaler tout composant touché par une vulnérabilité KEV »

Cette politique utilise la condition Expression, de type SECURITY. Elle lève une violation pour tout composant qui porte au moins une vulnérabilité présente dans un catalogue KEV actif.

Fenêtre de terminal
POLICY=$(curl -s -X PUT "http://localhost:8080/api/v1/policy" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name":"Vulnérabilités exploitées (KEV)","operator":"ANY","violationState":"FAIL"}' \
| jq -r .uuid)
curl -s -X PUT "http://localhost:8080/api/v1/policy/${POLICY}/condition" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"subject":"EXPRESSION","violationType":"SECURITY","value":"vulns.exists(vuln, vuln.is_kev)"}'

Pour inclure aussi les vulnérabilités pas encore cataloguées mais à forte probabilité d'exploitation, la documentation officielle propose vulns.exists(vuln, vuln.is_kev || vuln.epss_score > 0.5).

Exemple : interdire les versions de Log4j antérieures à 2.17.1

Section intitulée « Exemple : interdire les versions de Log4j antérieures à 2.17.1 »

La condition COORDINATES vise un composant par son groupe, son nom et un intervalle de version, indépendamment des bases de CVE. C'est le moyen d'interdire durablement une version que vous savez problématique, y compris avant publication d'un identifiant officiel.

Fenêtre de terminal
POLICY=$(curl -s -X PUT "http://localhost:8080/api/v1/policy" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name":"Bloquer Log4j < 2.17.1","operator":"ANY","violationState":"FAIL"}' \
| jq -r .uuid)
curl -s -X PUT "http://localhost:8080/api/v1/policy/${POLICY}/condition" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"subject":"COORDINATES","operator":"MATCHES","violationType":"OPERATIONAL","value":"{\"group\":\"org.apache.logging.log4j\",\"name\":\"log4j-core\",\"version\":\"<2.17.1\"}"}'

Après un nouvel envoi du SBOM, les violations se lisent ainsi :

Fenêtre de terminal
curl -s "http://localhost:8080/api/v1/violation/project/${PROJECT_UUID}" \
-H "Authorization: Bearer $TOKEN" \
| jq -c '.[] | {politique: .policyCondition.policy.name, type, composant: .component.name}'
{"politique":"Bloquer Log4j < 2.17.1","type":"OPERATIONAL","composant":"log4j-core"}
{"politique":"Vulnérabilités exploitées (KEV)","type":"SECURITY","composant":"log4j-core"}

Une application découpée en quinze microservices apparaît comme quinze projets sans lien, et personne ne sait répondre à la question de sa posture globale. Un projet collection agrège les métriques de ses enfants sans dupliquer leurs composants. Les enfants s'y rattachent par la relation parent, l'agrégation ne se déduit pas des noms.

Fenêtre de terminal
curl -s -X PUT "http://localhost:8080/api/v1/project" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Platform E-Commerce", "version": "1.0", "collectionLogic": "AGGREGATE_DIRECT_CHILDREN"}'

La v5 supprime la valeur NONE (un projet ordinaire n'a simplement pas de collectionLogic) et propose trois logiques :

LogiqueAgrège
AGGREGATE_DIRECT_CHILDRENTous les enfants directs
AGGREGATE_DIRECT_CHILDREN_WITH_TAGLes enfants directs portant le tag de collection, obligatoire
AGGREGATE_LATEST_VERSION_CHILDRENLes enfants directs marqués comme dernière version

La panne la plus dangereuse ne se voit pas : un projet qui affiche zéro vulnérabilité. Ce résultat peut signifier que tout va bien, que les bases ne sont pas encore recopiées, ou que les composants n'ont pas l'identifiant attendu par les sources actives. Avant de conclure, vérifiez le nombre de composants, les sources activées et l'état du miroir.

Les défaillances de démarrage viennent le plus souvent d'une configuration v4 recopiée telle quelle, ou d'une image tirée sans tag précis.

SymptômeCause probableSolution
L'API annonce une version 4.xImage tirée avec latest, qui pointe sur la v4Épingler 5.2.0 et son digest
L'apiserver ne se connecte pas à la baseVariables v4 ALPINE_DATABASE_*Utiliser DT_DATASOURCE_URL, _USERNAME, _PASSWORD
Refus de démarrer après mise à jourPropriété allow-local-connections encore présente (5.2)La remplacer par DT_OUTBOUND_ALLOWED_DESTINATIONS
Interface vide ou erreur réseauAPI_BASE_URL du frontend incorrectePointer vers l'URL de l'API vue par le navigateur
/api/v1/health répond 404Endpoint v4Santé sur le port 9000 : /health
Secrets illisibles après restaurationVolume /data (clé KEK) non sauvegardéSauvegarder base et volume ensemble

Un 403 se distingue d'un 401 : la clé est valide mais l'équipe n'a pas la permission demandée. C'est le cas typique d'une équipe créée sans PROJECT_CREATION_UPLOAD, qui réussit vers un projet existant et échoue dès qu'autoCreate doit intervenir.

SymptômeCause probableSolution
401 UnauthorizedClé invalide ou révoquéeVérifier la clé, en générer une nouvelle
403 ForbiddenPermission manquanteAjouter BOM_UPLOAD et PROJECT_CREATION_UPLOAD
413 Payload Too LargeSBOM au-delà de 100 MioAugmenter DT_BOM_UPLOAD_MAX_SIZE_BYTES
Graphe de dépendances videSBOM Syft sans lien depuis la racineRattacher la racine avec jq, ou générer avec Trivy
Zéro vulnérabilité avec un SBOM TrivySeule la NVD est active, et Trivy n'émet pas de CPEActiver OSV ou GitHub Advisories
Liste tronquée à 100 élémentsPagination v5 par défautPasser pageSize et pageNumber
Colonne KEV videMiroir KEV en échecVérifier les journaux MirrorKevDataSourceActivity

L'endpoint /api/version répond sans authentification et confirme que l'apiserver tourne ; la santé détaillée, migrations comprises, est sur le port de gestion.

Fenêtre de terminal
docker compose ps
docker compose logs -f apiserver
curl -s http://localhost:8080/api/version | jq .version
docker compose exec apiserver wget -q -Y off -T 3 -O - http://127.0.0.1:9000/health

Un point domine tous les autres : Dependency-Track ne vous protège que des vulnérabilités portant sur des composants effectivement inventoriés et identifiés comme les sources actives l'attendent. Ce qui manque au SBOM n'existe pas pour l'outil, et un tableau de bord vert reflète la qualité de vos SBOM avant de refléter votre sécurité.

  1. Dependency-Track 5 exige PostgreSQL 14+, se configure par DT_DATASOURCE_* et se migre à la main depuis la 4.14.2 ; le tag latest désigne encore la v4.
  2. Sur une installation neuve, seule la NVD est active et ne rapproche que par CPE : activez OSV pour les paquets open source.
  3. Les catalogues CISA KEV et ENISA EU KEV sont actifs par défaut depuis la 5.1 ; filtrez alertes et politiques sur is_kev pour traiter d'abord ce qui est exploité.
  4. Un SBOM Syft 1.54.1 n'a pas de lien depuis la racine : le graphe reste vide jusqu'à la correction jq ; Trivy 0.75.0 le fournit.
  5. La clé d'API n'est affichée qu'une fois ; préférez les comptes de service (5.2) pour les nouvelles intégrations.
  6. L'envoi est asynchrone : suivez le jeton avant d'interroger résultats et violations.
  7. Sauvegardez PostgreSQL et le volume /data ensemble, sans quoi les secrets chiffrés sont perdus.
  8. Preuve attendue par le SOCLE (SOCLE-VLN-INV-1 et SOCLE-VLN-INV-2) : une plateforme de corrélation multi-sources reliant les vulnérabilités aux artefacts via SBOM, avec des vulnérabilités enrichies par CISA KEV et EPSS au-delà du CVSS. Les projets alimentés par la CI et la colonne KEV de leurs findings constituent cette preuve.

Deux directions prolongent utilement cette mise en place. En amont, améliorer la qualité des SBOM, puisque c'est elle qui borne tout ce que l'outil peut détecter. En aval, documenter l'exploitabilité réelle avec des documents VEX, que Dependency-Track importe, et réduire le flux d'alertes à la source avec Renovate.

La documentation officielle a déménagé avec la v5 et fait référence sur deux sujets que ce guide ne détaille pas : la liste complète des propriétés de configuration (variables DT_*) et les notes de mise à jour, à lire avant chaque montée de version mineure.

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn