Architecture Supply Chain

An SBOM without runtime exposure is a parts list

August 6, 2026·10 min read·Chris Boker, Founder, CVEasy AI
A large left-side paper SBOM grid of small square components in ink, three vertical filter bars in the middle labeled by shape for deployed, loaded, and reachable narrowing the flow with teal arrows and one gold highlighted output tile on the right, with a rust arc closing back to feed a small evidence stamp as VEX out

A vulnerability team spends Tuesday patching an Apache Commons finding because the scanner surfaced it across twenty images, and by Friday nobody has looked at the Log4j entry in the queue below it. Neither ticket carries any signal about whether the running workload loads the affected class, whether the process ever reaches the vulnerable function, or whether the image is even in production. The build pipeline shipped a Software Bill of Materials for every artifact on every deploy, and the risk picture is no clearer than the day the SBOM program started.

An SBOM says what is inside the artifact. Risk lives in what is deployed, loaded, and reachable. That distance is where the join architecture belongs, and where the enterprise vulnerability stack is still mostly missing.

What SBOMs are genuinely good at

When Log4Shell dropped in December 2021, the organizations that had already generated CycloneDX or SPDX SBOMs across their fleet asked one question and had an answer inside an hour. Everyone else spent the weekend guessing. Response time to the next widely embedded flaw is the strongest case an SBOM makes for itself, with license attribution and supplier provenance right behind it. Regulators have caught up, and SBOM production at the build boundary now sits between table stakes and outright mandate depending on the sector.

The gap is not with SBOMs. A Bill of Materials describes an artifact and stops there, because that is what the word "bill" means. Risk is a property of the running system, which is a level of abstraction the SBOM was never designed to reach.

Three gaps between the artifact and the risk

The SBOM sits on one side and the running workload sits on the other. Three distinct joins have to carry a vulnerability across:

  1. Deployed vs present. The package is in the artifact and the artifact was built, but no live workload runs that image today. Registries carry an inventory of ghosts, and the risk lives in what is actually scheduled.
  2. Loaded vs shipped. The package is on disk inside the container and the process starts without ever calling into it. The library is present, and the code path is dead.
  3. Reachable vs loaded. The vulnerable function lives inside a package the process loads, and no path through this application ever calls it under production inputs. The load is real, and the reachability is not.

Each gap explains a different flavor of wasted week. Skip the first and the queue patches retired workloads; skip the second and it patches cold code; skip the third and every reachable-by-any-caller CVE looks the same as one your app actually invokes.

Prior art in the join space, named honestly

Several groups have already built pieces of this join, and calling them out before we claim ground is the difference between synthesis and reinvention.

Sysdig Risk Spotlight uses eBPF probes on the host to observe which packages a running container actually loads, then narrows scanner output to the in-use set; Sysdig documents the combined view as a Runtime Bill of Materials, or RBOM. The mechanism works, with one honest limit: the join is intra-platform, so the in-use flag does not travel laterally into another scanner, another ticket queue, or another vendor's asset inventory. RapidFort uses the same RBOM naming around a similar idea, and the convergence is a signal the concept has industry consensus even where the plumbing has not.

EdgeBit married eBPF with the Linux kernel's Integrity Measurement Architecture, so the SBOM becomes an enforcement contract at runtime; the SCORED '23 workshop paper documents the design. Bomfather approaches from the build side, using kernel-level eBPF tracing to record which files a compiler touched and hashing them into a Merkle tree, so the SBOM stops missing dynamically loaded or dead-code-eliminated dependencies. Together, Sysdig, EdgeBit, and Bomfather cover build truth and runtime loadedness for the workloads their sensors can see.

GUAC, the OpenSSF incubating project, does a different job: it aggregates SBOM, SLSA, VEX, and OSV documents into one graph so a query can walk from an artifact to its provenance, its vulnerabilities, and its declared exploitability. It joins documents beautifully, and it does not observe running workloads or intend to.

On reachability, Endor Labs computes function-level call graphs across direct and transitive dependencies, while Semgrep Supply Chain runs dataflow reachability on direct dependencies only. They are two honest, differently scoped approaches to a hard problem, and any credible pattern names them separately rather than folding both into "reachability."

VEX is the communication layer, and its adoption is honest

The OpenVEX, CycloneDX VEX, and CSAF VEX profiles all encode the same idea: a supplier or an owner asserts affected, not_affected, under_investigation, or fixed against a product plus a vulnerability, and a downstream tool filters scanner noise using that assertion. Trivy consumes VEX today; most enterprise vulnerability management platforms still do not, and that gap is where the promise sits.

The OpenSSF Signal in the Noise report, published January 8, 2026 with contributions from Amazon, Aqua Security, Chainguard, Cisco, Debian, Ericsson, Freexian, Google, Microsoft, Red Hat, and openSUSE, calls the state of VEX exactly what it is: adoption remains inconsistent, three technical directions have not converged into one format, and tooling is the bottleneck between the specification and the desk. VEX is more promise than practice as of 2026, and that framing is the one to carry into any pattern that leans on it.

The limit VEX cannot solve on its own is that someone still has to produce the judgment. A supplier writes not_affected because a vulnerable function is not called; an operator writes not_affected because a compensating control blocks the path. Both take a human today, and no authoritative pattern has published a way to generate VEX from runtime evidence instead.

The join architecture, made portable

The pattern below is tool-agnostic on purpose. It runs on durable identifiers rather than vendor names, and sits underneath any scanner, runtime sensor, or ticket queue.

The identifiers do the work. A purl names the package, an image digest names the artifact, a workload UID (Kubernetes UID, systemd unit, or platform equivalent) names the running process, a service address names the exposed surface, and a business function names why the workload exists. All five land on the asset identity graph we described in July.

Five joins then carry a CVE from the parts list to the incident:

  1. purl to image digest, from the SBOM emitted at build.
  2. image digest to running workload, from cluster inventory, VM inventory, or serverless platform state.
  3. running workload to loaded package set, from an eBPF observer in the Sysdig or EdgeBit lineage, feeding a runtime-loaded flag onto the exposure entity.
  4. loaded package to reachable code, from a reachability analyzer, with Endor-style call graphs for transitive coverage and Semgrep-style dataflow for direct-dependency precision.
  5. reachable code to business function, from the asset graph's owner and service context.

The output is a set of runtime attributes the exposure entity carries into the scoring plane, not a Boolean and not a filter. Given those attributes, the same CVE routes three fates: Act when it is deployed, loaded, reachable, and on a customer-facing service; Track when it sits in the image and the runtime never loads it; Attend when it is loaded but no reachable path under production inputs touches the vulnerable function. A queue with those three lanes stays honest, and prioritization can be shown rather than asserted.

Where this architecture emits VEX rather than only consuming it

Once runtime evidence is on the exposure entity, two VEX documents fall out. When Join 3 shows a package is present in the image but not loaded across a sufficient observation window, the platform auto-emits a not_affected assertion with the justification vulnerable_code_not_in_execute_path, one of the four standard OpenVEX status justifications. When Join 4 shows a loaded package where no reachable path leads to the vulnerable function, the same assertion applies with a stronger evidence chain behind it. Neither replaces supplier judgment; both give the operator's side of the conversation a machine-readable output the ticket queue and the auditor can consume.

Where the ground is still open

Three items are still not vendor-neutral practice. First, a cross-vendor pattern for the join, so a customer running Sysdig, Semgrep, and a third-party VM platform can share the runtime-loaded and reachable flags rather than duplicating them. Second, the runtime-evidence VEX flow above, which no authoritative source has documented as a repeatable practice. Third, VEX consumption inside the enterprise VM and ticketing layer, where guidance is thin enough that most teams still handle it by spreadsheet.

CVEasy runs this join inside the customer's trust boundary, with runtime loadedness and reachability landing on the exposure entity as TRIS scoring layers rather than as after-the-fact filters. We insist on the local-first constraint because of Join 3: the map of what your fleet actually loads at runtime is the most sensitive artifact your program produces, and it belongs on hardware you control.

Related Reading