Building a DevSecOps Evidence Chain: SBOM, Provenance, Deployment, and VEX
A finding just landed: CVE-2024-29025 against pkg:maven/io.netty/netty-codec-http@4.1.107.Final in your payments-api SBOMSoftware Bill of MaterialsA machine-readable inventory of the components and dependencies included in a software product.. Before you page anyone or file a ticket, you need one answer: is the affected artifact running in production right now? In my experience, most teams cannot answer that in under an hour. The evidence is scattered across a scanner output, a deployment manifest, and a Slack thread from last Tuesday.
This walkthrough closes that gap. It is the implementation companion to From SBOM to Actionable Signal. Where that article defines the evidence-to-decision model, this one walks through how I run it for a single service and finding. One manual pass, start to finish, keeping enough raw evidence at each step for another person to reconstruct the reasoning.
The evidence-to-decision model
Section titled “The evidence-to-decision model”The operating model below is derived from the sources cited in this guide. It is practical guidance, not a standard, scoring engine, or proof of exploitability or safety.
- Each row answers a different question. No single input is a verdict.
unknownis a valid outcome when a required join or assessment is missing.- The arrows are evidence links for review, not an automatic decision.
Inventory is not runtime evidence
Section titled “Inventory is not runtime evidence”The NTIA SBOM minimum-elements report defines an SBOM as a formal record of software components and their supply-chain relationships. CISA’s 2026 SBOM guidance also treats coverage and known unknowns as part of an SBOM practice.
Use that information as component-inventory evidence. Do not infer that an artifact is deployed, that its inventory is complete, or that vulnerable code is reachable. An absent component in an SBOM is not proof that it is absent at runtime — a gap explored in Why Your SBOM Does Not Protect You.
Before collecting more data, make the outcome explicit. You will remediate, deprioritize with evidence, or accept risk for a bounded period. unknown is the required intermediate outcome whenever a join or assessment is missing.
Here’s what I do: one manual walkthrough
Section titled “Here’s what I do: one manual walkthrough”The goal is narrow: determine whether the exact affected artifact is observed in production, preserve uncertainty, and record a reviewable next action.
1. Preserve the finding
Section titled “1. Preserve the finding”Input: the advisory and the payments-api SBOM entry. Retain: advisory identifier, component identity, SBOM reference, issuer, generation time, coverage, and known unknowns. Answered: what component did the finding name, and in which declared inventory? Not yet answered: whether the inventory is complete, the component is deployed, or it is exposed, reachable, or exploitable.
This boundary matters. An SBOM is component-inventory evidence, not runtime evidence. Its coverage and known unknowns make an absent or missing match interpretable rather than silently clean. The NTIA SBOM minimum-elements report and CISA 2026 SBOM minimum-elements guidance support that inventory and disclosure boundary.
2. Bind the artifact
Section titled “2. Bind the artifact”Input: the affected component’s releasable artifact. Retain: an immutable OCIOpen Container InitiativeA governance structure for open standards around container formats, runtime specifications, and image distribution. digest, the object type it identifies, and registry context. Answered: which specific content is joined to the finding? Not yet answered: who owns it, which environment it targets, whether it is authorized, running, or secure.
For OCI content, a digest is a content identifier based on a collision-resistant hash of content bytes. I use that digest, not a mutable tag, as the join key between the artifact, its provenance, and deployment observations. A digest identifies content. It does not establish operational state.
3. Verify the build claim
Section titled “3. Verify the build claim”Input: the provenance attestation and the recorded digest. Retain: attestation reference, verification result, signature check, subject digest, predicate type, builder identity, and trusted level. Answered: did the retained attestation verify for this artifact under your trust boundary? Not yet answered: whether the artifact is deployed, safely configured, reachable, or exploitable.
SLSA provenance describes where, when, and how an artifact was produced. SLSA’s verification guidance covers checking the signature, subject, predicate, builder, and trusted level. SLSA Build L3 adds hardened-build-platform controls within its threat model; it is not a runtime-security or exploitability certificate. Available but unverified provenance provides none of that claimed build assurance.
4. Observe deployment
Section titled “4. Observe deployment”Input: the intended deployment reference and an independently collected runtime observation. Retain: both identities, workload, environment, collection method, and observation time. Answered: what was intended to run, and what was observed at a stated time? Not yet answered: whether every instance is current, healthy, exposed, or represented by this observation.
Desired and observed state answer different questions, and an observation can be missing or stale. On Kubernetes, an image can be specified by digest, while ContainerStatus.imageID is a runtime-resolved identifier that may differ from the Pod specification’s image identifier. That Kubernetes detail is platform-specific, not a universal deployment API.
5. Add decision context
Section titled “5. Add decision context”Input: a VEXVulnerability Exploitability eXchangeA security advisory that communicates whether vulnerabilities affect a specific product and why. (if one exists), plus locally collected exposure and reachability evidence. Retain: VEX issuer, product and version match, date, revision history, status, and justification; for exposure and reachability, retain method, coverage, and uncertainty. Answered: what attributable product-status assessment and bounded local context are available? Not yet answered: whether the evidence independently proves safety or exploitability.
The CSAF 2.0 VEX profile defines statuses including fixed, known_affected, known_not_affected, and under_investigation, and requires lifecycle tracking and revision history. Treat a VEX as attributable, revisable product-status evidence: match it to your product and version, and keep its justification. VEX: Documenting “Not Exploitable” examines each justification flag and what it commits a supplier to.
I always record the reachability method and its blind spots alongside the result. A study on static vs. dynamic analysis of Java dependencies found that static analysis misses dynamic behavior like reflection while dynamic analysis only covers paths your tests actually exercise. That study applies to one language ecosystem. When I fill in this step for a Go or Python service, I note that those findings do not transfer directly and state what my local analysis method can and cannot see.
6. Decide and reopen
Section titled “6. Decide and reopen”Input: the linked records and their stated limitations. Retain: local outcome, accountable owner, timestamp, raw-evidence links, and expiry or reassessment trigger. Answered: what decision can you defend right now? Not yet answered: whether later evidence or changed operating context invalidates it.
This is a local, reviewable judgment. Assign evidence owners: the build owner for the digest and provenance record, the platform or service owner for the deployment observation, and an application or security owner for exposure and reachability context. A VEX revision, artifact or deployment change, exposure or configuration change, or improved analysis must reopen the record.
Production decision record
Section titled “Production decision record”finding: advisory_id: CVE-2024-29025 component: pkg:maven/io.netty/netty-codec-http@4.1.107.Final sbom_ref: evidence://payments-api/sbom/2026-08-02 sbom_boundary: "coverage and known unknowns retained"artifact: digest: sha256:9b2e4a7f3c1d object_type: oci-image-manifest registry_context: ghcr.io/acme/payments-apiprovenance: attestation_ref: evidence://payments-api/provenance/4.1.107.Final verification: passed trust_boundary: approved-builder-policydeployment: intended_ref: ghcr.io/acme/payments-api@sha256:9b2e4a7f3c1d observed_runtime_id: runtime://payments-api/pod-7/sha256:c4d8e2f1a9b3 workload: payments-api environment: production observed_at: 2026-08-02T09:30:00Z observation_method: independently-collected-runtime-observationcontext: vex_status: under_investigation exposure_evidence: evidence://payments-api/exposure/2026-08-02 reachability: method: documented-local-analysis coverage: bounded uncertainty: "not a proof of exploitability or safety"decision: outcome: unknown owner: payments-api-service-owner reassess_on: - VEX revision - artifact or deployment change - exposure or configuration change - improved analysisevidence_links: - evidence://payments-api/finding/CVE-2024-29025 - evidence://payments-api/raw-recordsHere, passed reports the provenance-verification result only. It does not decide the vulnerability. unknown remains the outcome until the required local evidence has been assessed.
Choose an outcome branch only when its evidence exists
Section titled “Choose an outcome branch only when its evidence exists”Both branches are decision paths, not severity rules or universal policy — Responding to a Critical CVE provides a routing checklist for each. In either case, retain the original finding and links to the raw evidence.
Remediate
Section titled “Remediate”Use this branch when the observed production identity maps to the affected digest, no applicable evidence changes the product assessment, and your local exposure and risk review supports urgent action. Record a remediation owner and target artifact, then re-observe the new deployment identity after the change.
An SBOM match, provenance result, exposure record, or reachability result does not independently prove exploitability. The branch records a local decision based on the joined evidence and your review.
Deprioritize with justification
Section titled “Deprioritize with justification”Use this branch only when an attributable, dated, version-matched VEX or equivalent local assessment is retained with its justification and local context. Record the evidence, accountable owner, bounded expiry, and concrete reopening events.
known_not_affected is not an unaudited suppression switch. A missing product match, missing justification, stale context, or unresolved assessment returns the record to unknown, not to deprioritization. This is consistent with the CSAF VEX profile, whose statements remain attributable and revisable rather than permanent truth values.
Automate after the manual run
Section titled “Automate after the manual run”Gate: complete the walkthrough once. If a reviewer cannot reconstruct the finding-to-outcome path from raw evidence, timestamps, owners, uncertainty, and reassessment triggers, you are not ready to automate.
Good automation candidates are deterministic work:
- Retain references and timestamps at each step
- Check whether an attestation subject matches a recorded digest
- Collect intended and observed deployment identities
- Detect missing mandatory fields
- Link updated VEX versions and reopen records when a listed trigger fires
Keep human review for exposure and reachability interpretation, the adequacy of a VEX for your product and context, risk acceptance, final disposition, and remediation authorization. I’ve seen teams jump to automated suppression before they had one clean manual path. The result is fast-looking answers that no one can reconstruct six months later.
Common failure modes
Section titled “Common failure modes”- Using a tag as the join key. A tag is mutable. A digest is not. They are not equivalent identifiers.
- Keeping an attestation without verifying it. Unverified provenance provides none of the claimed build assurance.
- Equating desired state with observed state. A deployment declaration and a runtime observation answer different questions.
- Suppressing findings from VEX status alone. VEX must be matched, attributed, dated, and reviewed in your local context.
- Recording a rationale without an expiry. A decision without a reassessment trigger outlives its supporting assumptions.
Your first service
Section titled “Your first service”Before you automate anything, confirm these five things for one service:
- Your SBOM includes a stated boundary, coverage, and known unknowns.
- Each finding is linked to a component identity and immutable artifact digest.
- Provenance verification is recorded separately from provenance availability.
- Intended and observed deployment identities include timestamps and workload context.
- Every final decision has an owner, raw-evidence links, and an expiry or reassessment trigger.
Pick the next finding that comes in. Run the manual record for one service, one finding, all six steps. If a second reviewer cannot reconstruct your decision from the raw evidence alone, it is not done.
Article series
From SBOM Findings to Defensible Decisions
Explore every resource in this series, whatever its format.
- articleWhy Your SBOM Does Not Protect You (and What Is Missing)
- articleFrom SBOM to Actionable Signal: Which Vulnerabilities Should You Actually Fix?
- guideBuilding a DevSecOps Evidence Chain: SBOM, Provenance, Deployment, and VEX
- articleResponding to a Critical CVE Without Triggering a Flood of False Positives
- articleVEX: Documenting "Not Exploitable" Without Turning Security into a Checkbox
- articleWhat the Cyber Resilience Act Changes for Software Vulnerability Management