Solutions

Know which of your projects are exposed, and whether it matters.

An advisory lands. Nobody can say which services ship the affected version, and the scanner lists every occurrence as if each one were exploitable. The team spends the day on the list instead of on the fix.

Why it keeps happening

Scanners report presence, not exposure.

A software composition scan lists every vulnerable package it can find in the dependency graph. Many are dev-only, pulled in transitively and never loaded, or declared and forgotten. Each one is reported with the same severity as a package your request handler actually calls.

Exposure is a question about releases, not repositories. Which version of which service is running with the affected package, and was it the release before the fix or the one after? Without a bill of materials per release, the answer is reconstructed from deployment history.

Across a portfolio the numbers stop meaning anything. A count of findings across every team says nothing about which service to patch first.

How we solve it

A bill of materials per release, and an exposure answer per project.

Projects in Dependably Packages holds a software bill of materials for every release of every project, tracks which packages each one ships, and orders each advisory by whether first-party code can reach the affected package. The reachability comes from a companion tool that runs in your CI.

Analyze the build in CI

sbom-reach runs against the repository, generates a CycloneDX SBOM, looks up vulnerabilities against the public OSV database or a private vulnerability service you point it at, and annotates each one with package-level reachability: whether first-party code imports and calls the package, imports it without calling it, or never references it. Reachability covers npm and NuGet; Python projects get the SBOM and the vulnerabilities without a verdict.

bom.cdx.json · results.sarif

Upload to the project version

The SBOM is uploaded to the project version, and the SARIF log writes the reachability facts onto it. A version can be promoted to latest, so the current release is always the one the dashboard answers for.

PUT /api/v1/sbom · /api/v1/sarif

Priority by reachability

Each advisory is placed in a priority bucket, track, attend, or act, from its CVSS score, KEV listing, and EPSS probability, then moved by what the analysis observed: reachable promotes it one bucket, not-observed demotes it one where the analysis covered that ecosystem and the package has no install script, and imported-not-called or unknown leave it in place.

reachable · not-observed

Roll up across projects

Projects nest in collections, and severity, policy status, and last-upload time roll up the tree, so a team's exposure and a portfolio's exposure are the same query at different depths. Any version exports its SBOM, and its vulnerability decisions as VEX.

rollup · export
What you end up with

The list of what to patch first, per service.

When an advisory lands, which releases ship it and whether your code can reach it is an answer in the dashboard rather than a working day.

  • Every project version carries the SBOM it was built from, so the answer is per release, not per repository.
  • Advisories your code can reach sit at the top; ones nothing references sit below, still visible.
  • Policy status per version says whether a release passes your rules before it ships.
  • Rollups by collection show which team or portfolio carries the exposure.
Advisory row
  • advisory example-parser@3.1.0 · high
  • reachable src/api/upload.ts imports and calls
  • priority attend → act
  • ships in billing-api 2.8.0 · portal 5.1.2
Where it runs

Self-hosted, or hosted by us on request.

Projects is part of Dependably Packages, which ships as a single container image under Apache-2.0. sbom-reach is Apache-2.0 licensed and is built from source with Node and pnpm; NuGet reachability also needs the .NET SDK. Dependably Cloud runs the same engine on a paid plan we operate; get in touch to talk about a hosted deployment.