SBOMs in Practice: VEX, Generation Methods, and the Compliance Clock

Share
SBOMs in Practice: VEX, Generation Methods, and the Compliance Clock
An SBOM being generated at build time and queried against a live vulnerability database.

Knowing what an SBOM is gets you to the starting line. What separates a working SBOM program from a compliance checkbox is the operational detail: which fields actually have to be there, how the identifiers work, why VEX exists, where in the pipeline you generate, and which deadlines are already on the calendar. I covered the fundamentals in what an SBOM is and what goes in one; this is the layer underneath that.

The pressure is real: the World Economic Forum's Global Cybersecurity Outlook 2026 found 65% of large companies name third-party and supply chain vulnerabilities as their biggest barrier to cyber resilience. You can't defend what you can't enumerate, and modern applications are almost entirely built from components someone else wrote.

The NTIA Minimum Elements: What Actually Has to Be There

In July 2021 the US National Telecommunications and Information Administration published the guidance that became the de facto baseline for "is this a real SBOM." It defines three categories: data fields, automation support, and practices.

The seven required data fields:

  1. Supplier name, the entity that created the component.
  2. Component name, the label for the unit of software.
  3. Component version, so you can tell releases apart.
  4. Unique identifiers, such as a PURL or CPE.
  5. Dependency relationship, showing how components relate.
  6. SBOM author, whoever assembled the SBOM.
  7. Timestamp, when it was created.

Automation support means machine-readable format (SPDX, CycloneDX, or SWID). Practices covers how often you regenerate, how comprehensive it is, and how you distribute it.

⚠️ That third category is where most programs quietly fail. An SBOM can hit all seven fields perfectly and still fail the practices test if it was generated once and never updated. A document describing software you no longer run isn't an inventory, it's an artifact. The fields are the easy part; the discipline of regenerating on every build is the part that decides whether the thing works.

PURL vs CPE: The Identifiers That Make Automation Possible

Field 4 is doing more work than it looks. Without a machine-parseable identifier, matching a component to a CVE is string-matching guesswork. Two schemes show up in practice:

PURL (Package URL) encodes ecosystem, namespace, name, and version in a single string:

pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1

That's unambiguous and directly resolvable. PURL is what most modern tooling emits and what OSV-style databases key on.

CPE (Common Platform Enumeration) is what the National Vulnerability Database uses to map products to CVEs. It's older, more verbose, and notoriously fussy about vendor/product naming.

⚠️ The practical friction: PURL is cleaner for package ecosystems (npm, Maven, PyPI, Go), CPE is what NVD speaks, and the mapping between them is imperfect. A component with a PURL but no CPE match can miss NVD-sourced CVE data, and CPE naming inconsistencies produce both false negatives and false positives. Good tooling carries both and queries multiple vulnerability sources (NVD and OSV) rather than trusting one. If your SBOM only has one identifier type, you have a matching blind spot you probably haven't measured.

VEX: The Fix for the False-Positive Flood

This is the piece that makes vulnerability data from an SBOM actually usable, and it's underused.

The core workflow maps every component in the SBOM to known vulnerabilities in NVD and OSV, then ranks what to fix. The problem is noise. A library can contain a vulnerable function your code never calls. The CVE matches, the alert fires, and your team burns a day proving it doesn't apply. Do that enough times and people stop reading the alerts, which is worse than not having them.

Vulnerability Exploitability eXchange (VEX) is a companion document that states, per vulnerability, whether the product is actually affected. It turns "this CVE matches a package we ship" into "this CVE matches, and here is whether it's reachable in our product."

⚠️ VEX is the difference between an SBOM program people trust and one they route to a folder. Without it, you're generating alert fatigue at industrial scale, the same failure mode that makes teams dismiss Dependabot PRs and ignore scanner output. This is exactly the alert-fatigue trap I flagged in the six free GitHub security settings: a control that produces noise trains people to ignore it. Pair the SBOM with VEX, and patch what's reachable rather than what merely matches.

CycloneDX has native VEX support, which is the main practical reason security-led teams reach for it over SPDX.

Format Choice: SPDX, CycloneDX, SWID

Format Steward Standard Focus Reach for it when
SPDX Linux Foundation ISO/IEC 5962:2021 License compliance, provenance Legal/OSS governance is the driver; broad distribution and audits
CycloneDX OWASP ECMA-424 (2024) Security, risk, VEX Vulnerability management leads; AppSec pipelines
SWID ISO/IEC ISO/IEC 19770-2:2015 Software identification Asset management, installed-software tracking

SPDX came from license compliance, so it carries rich copyright, license-expression, and provenance metadata, and SPDX 3.0 broadened it toward security and AI use cases. CycloneDX was built for security from the start and extends past classic app SBOMs into SaaSBOM for services and ML-BOM for models and datasets. SWID mostly appears in compliance contexts rather than day-to-day AppSec, though NIST references SWID tags when mapping products to vulnerabilities.

The good news: both major formats capture the NTIA elements, both are open standards, and most tooling reads and writes either, so the choice rarely locks you in. Pick based on what drives your program, and convert if a consumer needs the other.

Where You Generate Decides How True It Is

Three capture points, trading completeness against convenience:

Source analysis reads manifests and lockfiles (package.json, pom.xml, go.sum) straight from the repo. Fast, early, shift-left friendly. ⚠️ But it records what you declared, not what the build resolved. It routinely misses build-injected components and system-level packages, which is exactly where the ugly transitive dependencies hide.

Build-time generation runs inside CI as the artifact is assembled. This is the accurate one, because it records what actually ships, including anything the build pulled in that no manifest mentions. If you control the build, generate here. Full stop.

Binary/runtime analysis inspects a compiled artifact or container image after the fact. It's the fallback when you don't control the build, third-party software, vendor appliances, legacy binaries. For container images it's often the practical choice anyway, since it captures the complete runtime layer including OS packages.

⚠️ The rule worth internalizing: a source-level SBOM describes intentions, an image-layer SBOM describes reality. When a Log4Shell-class CVE lands, the difference is whether your query returns the truth or a comfortable fiction. The vulnerable library is usually a transitive dependency three levels deep that no lockfile in your repo names directly.

Tooling

The open-source ecosystem is healthy. Syft and Trivy both emit standard formats and drop into CI cleanly, and the CycloneDX and SPDX projects ship their own CLI generators. For container builds, BuildKit can attach an SPDX SBOM as an attestation during the build itself.

⚠️ Sign your SBOMs. Sigstore/cosign lets consumers verify provenance, and an unsigned SBOM is an assertion, not evidence. If you're already signing images (and you should be), signing the SBOM is the same motion and it closes the "how do I know this inventory is the real one" gap for anyone downstream. This is the same digest-and-signature discipline that makes GitOps delivery trustworthy: pin what you ship, sign what you claim.

Generators are step one. They produce a file; they don't correlate it to your live environment or keep ten thousand of them current. That's the scaling problem below.

The Compliance Clock

This is the part with dates on it, and several are closer than teams realize:

  • US Executive Order 14028 (2021) directed federal agencies to require SBOMs from software suppliers, with NTIA minimum elements as baseline.
  • EU Cyber Resilience Act: manufacturers of products with digital elements must produce and maintain SBOMs. Reporting obligations apply from 11 September 2026. Main obligations from 11 December 2027.
  • FDA: SBOMs required in premarket submissions for cyber devices under section 524B of the FD&C Act.
  • FedRAMP and US federal procurement increasingly expect SBOMs from cloud providers.

And the frameworks that assume the inventory without always naming it: NIST SSDF (SP 800-218), DORA in EU financial services, and PCI DSS requirement 6.3.2, all expect you to track software components, which is precisely what an SBOM records.

⚠️ If you sell into the EU, the September 2026 reporting date is the near-term one, not December 2027. And if your customers are enterprises or government agencies, the SBOM request will show up in the security review well before any regulation forces it, procurement moves faster than legislation. Treat SBOM generation as the foundation that satisfies several of these at once rather than a per-regulation project. It also connects to the broader framework mapping I covered in the top cloud security standards for compliance.

Managing Them at Scale

One SBOM is a file. Ten thousand SBOMs are a data problem.

Managing at scale means storing every version, regenerating on every build, and being able to query the entire set at once when a CVE lands. The query-the-whole-estate capability is the entire point, a single SBOM row tells you nothing you didn't know; searching every row across every service in seconds is what turns a week of uncertainty into a same-day containment plan.

The other half is distribution. An SBOM only helps when it reaches whoever needs it, an internal IR team mid-incident, or a customer's security reviewer during procurement. Automated export and scheduled sharing beat emailing a stale file on request.

Practices and Pitfalls

The habits that make it work:

  1. Automate generation in CI/CD so every build emits one with no human in the loop.
  2. Generate at build time for accuracy; regenerate on every release.
  3. Standardize on SPDX or CycloneDX, not a home-grown format.
  4. Capture transitive dependencies, not just declared ones.
  5. Sign and verify with Sigstore so consumers can trust provenance.
  6. Correlate to live vulnerabilities and exposure, not just a static list.

⚠️ The pitfalls mirror them exactly. A stale SBOM describes software you no longer run. An incomplete one that skips transitive dependencies hides the precise components attackers target. And an SBOM that's stored but never queried against real risk is documentation, not security. All three failures look identical from the outside: you have SBOMs, you're "compliant," and you still can't answer "are we affected" when it counts.

Bottom Line

The SBOM that earns its keep is generated at build time on every build, carries both PURL and CPE so vulnerability matching actually resolves, ships with VEX so your team patches reachable bugs instead of theoretical ones, is signed so downstream consumers can trust it, and lives somewhere you can query the whole estate in one shot. Miss any of those and you've built a compliance artifact rather than an incident-response asset. The regulations are arriving on a schedule now, EU CRA reporting in September 2026, main obligations in December 2027, so the choice is doing this properly once or doing it badly twice.


References