OWASP Dependency-Track est un serveur qui reçoit des SBOM CycloneDX, en tient l’inventaire par projet et par version, et les réévalue en continu contre des bases de vulnérabilités. Il répond à une question qu’aucun pipeline de CI ne pose : parmi les composants déjà livrés, lesquels sont devenus vulnérables depuis leur construction ? Cet article décrit son fonctionnement, ses politiques, ses alertes, son traitement du VEX, et la façon dont saas-core l’utilise sans en faire une dépendance de la livraison.

Pourquoi un SBOM archivé ne suffit-il pas ?

Un SBOM archivé décrit un état figé, alors que les vulnérabilités se publient après coup. Une CVE divulguée le lendemain d’une construction ne fait rougir aucun run de CI, puisque rien n’a changé dans le dépôt. Les gates de CI jugent un commit au moment où il passe ; il leur manque une vue continue de ce qui tourne réellement.

Dependency-Track comble ce manque. Il conserve chaque SBOM reçu comme une version de projet, et chaque nouvelle donnée de vulnérabilité est corrélée à l’ensemble du portefeuille, sans nouveau build.

Comment Dependency-Track consomme-t-il un SBOM CycloneDX ?

Dependency-Track ingère un SBOM CycloneDX par son API REST, rattaché à un projet nommé et versionné. L’appel le plus courant est un POST /api/v1/bom en multipart, authentifié par une clé d’API dans l’en-tête X-Api-Key. Avec autoCreate, le projet est créé s’il n’existe pas encore.

Une fois le SBOM reçu, chaque composant est identifié, principalement par son purl (pkg:npm/..., pkg:apk/...), puis comparé aux sources configurées : NVD, GitHub Advisories, OSV, entre autres. Les correspondances deviennent des constats (findings) par projet, consultables dans l’onglet d’audit.

Voici l’équivalent en curl de l’envoi que fait le script de saas-core :

curl -X POST "http://localhost:8085/api/v1/bom" \
  -H "X-Api-Key: $DTRACK_API_KEY" \
  -F "autoCreate=true" \
  -F "projectName=saas-core/app" \
  -F "projectVersion=v1.0.0" \
  -F "projectTags=app,source" \
  -F "parentName=saas-core" \
  -F "isLatest=true" \
  -F "bom=@sbom-app.cdx.json"

Le projet parent permet d’agréger plusieurs projets enfants (code, images applicatives, images d’infrastructure) sous une seule vue de portefeuille.

Que fait une politique Dependency-Track ?

Une politique transforme un constat en violation, avec un état INFO, WARN ou FAIL. Elle combine des conditions sur la sévérité d’une vulnérabilité, la licence, la version ou l’identité d’un composant, avec un opérateur ANY ou ALL. Une violation FAIL ne bloque rien par elle-même : Dependency-Track n’est pas dans le chemin de livraison, il signale.

L’intérêt est d’aligner la politique sur la règle déjà appliquée en CI. Si la CI rougit sur une critique, la politique doit dire la même chose, sinon deux sources de vérité divergent.

Comment éviter que les alertes deviennent du bruit ?

Il faut choisir les événements notifiés selon qu’ils demandent un geste, pas selon qu’ils sont nouveaux. Dependency-Track propose des notifications par portée : PORTFOLIO (nouvelle vulnérabilité, violation de politique, échec de traitement d’un SBOM…) et SYSTEM (erreurs d’analyseur, de synchronisation des sources).

Le piège classique est NEW_VULNERABILITY. Chaque livraison crée une nouvelle version de projet, dont toutes les vulnérabilités sont « nouvelles » au sens de l’outil : une rafale par étiquette. Une alerte sur violation de politique, réglée sur les critiques, ne se déclenche que lorsqu’il faut agir.

Les alertes système sont souvent négligées. Une source dont le jeton a expiré ne produit aucune erreur visible : les listes de vulnérabilités cessent simplement de grandir.

Comment Dependency-Track traite-t-il le VEX ?

Dependency-Track stocke une analyse par constat, avec un état (NOT_AFFECTED, EXPLOITABLE, IN_TRIAGE, RESOLVED, FALSE_POSITIVE), une justification, un texte, et une option de suppression. Le VEX (Vulnerability Exploitability eXchange) est la forme échangeable de ces analyses : il dit, vulnérabilité par vulnérabilité, si un produit est affecté.

Deux limites pratiques existent. Dependency-Track importe le VEX au format CycloneDX, pas OpenVEX. Et le VEX CycloneDX rattache chaque déclaration à un composant par bom-ref, un identifiant qui peut changer d’un SBOM à l’autre. Pour un projet qui écrit son VEX en OpenVEX, la voie déterministe est l’API d’analyse : PUT /api/v1/analysis, en faisant correspondre la vulnérabilité et le paquet.

Statut OpenVEXÉtat d’analyse Dependency-Track
not_affectedNOT_AFFECTED
affectedEXPLOITABLE
fixedRESOLVED
under_investigationIN_TRIAGE

Que coûte une instance Dependency-Track ?

Dependency-Track est un service Java gourmand en mémoire et lent au premier démarrage. L’image bundled réunit l’API et l’interface dans un seul conteneur, ce qui simplifie un usage local. Il refuse de démarrer sous 4 Go de tas Java, et la recommandation retenue dans saas-core est d’environ 4,5 Go de RAM pour le conteneur. Le premier démarrage télécharge les miroirs de vulnérabilités, ce qui prend du temps.

La base de données mérite attention. La base H2 embarquée est réservée aux essais : dans saas-core, elle s’est corrompue lors d’un redémarrage pendant un import OSV, emportant la configuration faite à la main dans l’interface. D’où une base PostgreSQL dédiée et une configuration décrite dans le code.

Comment saas-core l’intègre

saas-core traite Dependency-Track comme un outil d’atelier : il tourne sur le poste du développeur, il n’est appelé ni par le runtime, ni par la CI, ni par la livraison, et sa panne ne coûte que la vue.

  • Déploiement local. docker-compose.dtrack.yml définit un projet Compose isolé (saas-core-dtrack) avec deux services : dtrack (image dependencytrack/bundled épinglée à une version, -Xmx4g, port publié sur 127.0.0.1:8085 uniquement, arrêt laissé à 120 s pour qu’un import se termine) et dtrack-postgres, sans port publié. La sonde de santé utilise wget, l’image ne contenant pas curl.
  • Pourquoi local. L’instance est injoignable depuis GitHub Actions, et l’exposer exigerait un accès entrant et une clé en secret de CI. Les SBOM y sont donc poussés par un geste local, pnpm sbom:sync (scripts/sbom/dtrack-sync.mjs), qui appelle POST /api/v1/bom.
  • Ce qui est poussé. Le code (saas-core/app, SBOM produit par cdxgen, version issue de git describe), les trois images applicatives de l’étiquette déployée (leur SBOM est l’artefact sbom-images-<sha> de la CI), et les images d’infrastructure de l’environnement d’essai, analysées par syft à leur empreinte, en variante linux/arm64. Tous sont rattachés au projet parent saas-core et marqués comme dernière version.
  • Configuration en code. scripts/sbom/dtrack-config.mjs décrit les sources (NVD par API, OSV pour Alpine, npm, Debian et Go, alias désactivés), la politique « Vulnerabilite critique » (toute critique met le projet en violation FAIL, la même règle que le scan d’images de la CI), deux alertes (violations de politique et SBOM refusés au niveau portefeuille ; erreurs d’analyseur et de synchronisation au niveau système, sans NEW_VULNERABILITY), le projet parent et les permissions de l’équipe Automation. pnpm dtrack:setup l’applique de façon idempotente. Aucun secret n’y figure : les clés se posent dans un .env ignoré par git ou dans l’interface.
  • VEX. pnpm dtrack:vex (scripts/sbom/dtrack-vex.mjs) lit security/vex/*.openvex.json, traduit statuts et justifications, et pose l’analyse via PUT /api/v1/analysis sur les constats dont la vulnérabilité et le purl sans version correspondent. Un statut ou une justification inconnus arrêtent le script au lieu d’être devinés.
// scripts/sbom/dtrack-config.mjs (extrait)
export const POLICIES = [
  {
    name: "Vulnerabilite critique",
    operator: "ANY",
    violationState: "FAIL",
    conditions: [{ subject: "SEVERITY", operator: "IS", value: "CRITICAL" }],
  },
];

Les limites sont assumées et documentées. La vue continue dépend d’un geste humain : sans pnpm sbom:sync après une livraison, l’outil réévalue la version précédente. Le SBOM d’une image applicative est celui de l’image amd64 construite par la CI au même commit, pas l’image arm64 publiée octet pour octet. Les contrôles qui bloquent restent en CI ; ils sont décrits dans l’article sur la génération des SBOM avec cdxgen et syft et dans celui sur OSV-Scanner, pnpm audit et le VEX.

En résumé

Dependency-Track apporte ce que la CI ne peut pas fournir : une réévaluation continue de composants déjà livrés. Sa valeur dépend de trois choix : une politique alignée sur les gates de CI, des alertes réservées à ce qui demande un geste, et une configuration versionnée plutôt que saisie dans une interface.

Le détail de ce qu’inclut le kit saas-core, chaîne d’approvisionnement comprise, figure sur la page des tarifs.