SBOM Explained (2026): SPDX 3.0 vs CycloneDX 1.7, VEX and the Rules

Nov 10, 2026 · 11 min · Ajay Kumar

Producing an SBOM takes about four minutes: install a scanner, point it at an image, get a JSON file. Producing one that lists what the image really ships, in a format the person asking can parse, and that still means something six months later, is the actual job. That gap between "we have an SBOM" and "we have an SBOM anyone can trust" is what this post is about. I will explain what a software bill of materials actually contains, how SPDX and CycloneDX differ in 2026, what CISA's new minimum elements and the EU Cyber Resilience Act ask for, where VEX fits, why SBOM quality is still poor, and which tools I would use to generate and consume them.

What is an SBOM, in plain terms?

An SBOM is an inventory of the components inside a piece of software: names, versions, who produced them, how they relate, and identifiers a machine can match against vulnerability databases. CISA's own framing is an "ingredients list" for software, which is fair as long as you remember that ingredients lists can be wrong.

The useful part is the identifiers. A component called openssl with version 3.0.13 is a hint. A component with a package URL such as pkg:deb/debian/openssl@3.0.13-1~deb12u1 and a SHA-256 hash is evidence. Scanners like Grype and Trivy match those identifiers against advisories, and Dependency-Track keeps matching them every day after you ship, which is the real reason to bother. A scan at build time tells you about today; a stored SBOM lets you answer "are we affected?" next March, when the next xz-style advisory lands and nobody remembers what went into last quarter's image.

What an SBOM is not: a vulnerability report, a licence audit, or proof the software is safe. It is a claim about contents, produced by a tool, at a particular moment. Hold on to that last sentence, because the quality section below depends on it.

SPDX vs CycloneDX in 2026: what actually differs?

There are two formats that matter. CISA's 2026 guidance, per FOSSA's summary, no longer mentions SWID tags, leaving SPDX and CycloneDX as the named options.

SPDX is a Linux Foundation project. Version 2.3 (November 2022) is what most tools still emit by default for interoperability. SPDX 3.0 shipped in April 2024 and 3.0.1 in December 2024; it is a rewrite into a graph model serialised as JSON-LD, split into profiles (Software, Security, Build, AI, Dataset, Licensing, Lite and others). SPDX 3.1 has been a release candidate since 3.1-RC1 on 24 January 2026, with no final release as of today. The structural change matters more than the version number: an SPDX 3 document has no top-level spdxVersion field, so a lot of tooling written for 2.x simply does not recognise it.

CycloneDX is an OWASP project. CycloneDX 1.7 was released on 21 October 2025 and adopted as ECMA-424 2nd edition in December 2025; patch releases 1.7.1 and 1.7.2 followed in 2026. It started as a security-first format and grew sideways: services, cryptographic assets (CBOM), machine-learning models, attestations and VEX all live in the same schema.

SPDX CycloneDX Source
Steward Linux Foundation OWASP, standardised by Ecma spdx-spec, Ecma
Current stable 3.0.1 (Dec 2024); 2.3 still the common default 1.7 (Oct 2025), patch 1.7.2 (Sep 2026) SPDX releases, CycloneDX releases
Next 3.1, release candidate since Jan 2026 none announced 3.1-RC1
Formal standard ISO/IEC 5962:2021 (for 2.2.1) ECMA-424 2nd ed. (Dec 2025) IEC, Ecma
Strengths licensing detail, legal review, 3.0 profiles for AI and datasets security tooling, embedded VEX, services, crypto, ML models CycloneDX 1.7 notes
Syft output versions spdx-json 2.2, 2.3, 3.0 cyclonedx-json 1.2 to 1.7 Syft formats
Dependency-Track upload not accepted the only upload format, all versions DT file formats

My honest take: the format war is mostly over because the tools write both. Syft will emit SPDX and CycloneDX from the same scan in one command. Pick based on who reads the file. If the consumer is a vulnerability platform, CycloneDX is the path of least resistance, since Dependency-Track's file-format reference says CycloneDX is the only format it accepts for upload. If the consumer is a legal team or a customer contract that says "SPDX", give them SPDX 2.3, because it is the version their tools will parse. I would not hand SPDX 3.0 to anyone yet unless they asked for it by name.

What goes in an SBOM: CISA's 2026 minimum elements

On 29 July 2026 CISA published the 2026 Minimum Elements for an SBOM, jointly with NSA, FBI and international partners. It replaces the 2021 NTIA list after a 2025 public comment period. The data set roughly doubled. Per FOSSA's field-by-field breakdown, the fields now split into two groups:

  • SBOM metadata: author, timestamp, author signature, data format name and version, generation context, tool name and version, and SBOM version.
  • Component data: producer, name, version, identifiers (at least one machine-processable one, such as a purl or CPE), dependency relationship, hash value and algorithm, and licence.

Two changes matter in practice. First, coverage: the 2021 baseline accepted top-level dependencies only, while the 2026 elements cover all components including transitive ones. Second, generation context. A source-scan SBOM (from a lockfile) and a build-time SBOM (from the built image) of the same project will disagree, and until now nothing in the file told the reader which kind they were holding. Tool name and version matter for the same reason: two versions of the same scanner can produce different inventories.

None of this is a US federal mandate on private companies. It is guidance that procurement teams copy into contracts, which in practice is how most SBOM requirements arrive.

The EU Cyber Resilience Act and SBOMs

The CRA is the one with legal teeth. Regulation (EU) 2024/2847, Annex I Part II point 1, requires manufacturers to draw up "a software bill of materials in a commonly used and machine-readable format" covering "at the very least the top-level dependencies". Three things follow from the text:

  1. The format is not named. SPDX and CycloneDX both qualify as commonly used and machine-readable.
  2. The floor is top-level dependencies, lower than CISA's 2026 baseline. Meeting CISA's elements more than covers the CRA wording.
  3. It does not have to be public. Annex VII point 8 puts the SBOM in the technical documentation, provided "further to a reasoned request from a market surveillance authority".

Timing, from Article 71: vulnerability reporting under Article 14 has applied since 11 September 2026, and the essential requirements, including the SBOM, apply from 11 December 2027. I covered the reporting side, harmonised standards and open-source stewards in the CRA post. For SBOMs the practical reading is simple: if you sell software with digital elements into the EU, you need a reproducible way to produce one per release and keep it for the support period. That is a pipeline job, not a document someone writes.

Where VEX fits

An SBOM tells you what is inside. A vulnerability scanner then tells you which of those components have known CVEs. The gap between those two outputs is where most teams drown, and VEX (Vulnerability Exploitability eXchange) is the format for closing it. A VEX statement says, for a specific product and CVE, one of: not affected, affected, fixed, or under investigation, with a justification such as "vulnerable code not in execute path".

There are three common carriers: CycloneDX's built-in VEX, OpenVEX (a small standalone JSON format) and CSAF VEX profiles. Tool support is what decides it. Grype's README lists OpenVEX support for filtering and augmenting results, and its CLI takes --vex documents directly; Dependency-Track ingests CycloneDX VEX.

The discipline matters more than the format. A VEX "not affected" is a security claim with your name on it. Write it only after someone has looked, store it in Git next to the code, and review it like any other change.

Why SBOM quality is still a problem

Here is the uncomfortable research. A study of 55,444 SBOMs produced by six tools across 3,287 repositories, submitted in January 2026, found inter-tool package-detection consistency as low as 7.84 to 12.77 percent depending on language, licence accuracy below 20 percent, and poor consistency between a tool and its own earlier versions. A separate 2,414-repository study found SBOM-driven vulnerability scanning produced a 92.0 percent false-positive rate in its case study, and that function-call analysis could prune 61.9 percent of those alarms.

SBOM formats, tools and rules: the milestones that shape 2026 Apr 2024 SPDX 3.0 Dec 2024 SPDX 3.0.1 21 Oct 2025 CycloneDX 1.7 Dec 2025 ECMA-424 2nd ed. 24 Jan 2026 SPDX 3.1-RC1 9 Jun 2026 Dependency-Track 5.0 29 Jul 2026 CISA 2026 elements 11 Sep 2026 CRA reporting live 11 Dec 2027 CRA SBOM duty Quality gap: tools agree on as few as 7.84 to 12.77% of packages; scanners fed SBOMs hit 92.0% false positives Sources: spdx-spec and CycloneDX releases, Ecma, OWASP, CISA, EUR-Lex, arXiv 2601.05622 and 2511.20313
Format releases (grey), tool and standard milestones (green) and regulatory dates (terracotta), with the two 2025 to 2026 quality studies in the footer. Dates from the linked release pages, CISA, Ecma and Regulation (EU) 2024/2847.

The causes are mundane. Source scans read lockfiles and miss what the build added; image scans see the OS packages and may miss statically linked Go or Rust dependencies; some ecosystems lack hashes in their metadata; and licence fields are guesses scraped from package manifests. None of that is fixed by picking the "right" format.

What does help, in my experience: generate the SBOM from the artifact you ship (the built image, by digest), record the tool and version, pin that tool version in CI so the inventory does not drift between releases, attach the SBOM to the artifact with a signature, and feed it to something that keeps re-checking it. That is the boring pipeline work that has always separated teams who can answer an auditor in an hour from teams who need a week.

Which SBOM tools to use: Syft, Trivy, cdxgen and Dependency-Track

The generator choice matters less than running one consistently. The current releases, checked on GitHub today:

Tool Role Latest release Formats Source
Syft generate from images, dirs, archives v1.57.1 (9 Nov 2026) SPDX 2.2 to 3.0, CycloneDX 1.2 to 1.7 releases
Grype scan images or SBOMs for CVEs v0.121.3 (9 Nov 2026) reads Syft, SPDX, CycloneDX; OpenVEX releases
Trivy scanner that also generates SBOMs v0.76.2 (5 Nov 2026) SPDX, CycloneDX releases
cdxgen source-level CycloneDX generator v13.7.0 (7 Nov 2026) CycloneDX releases
Dependency-Track store SBOMs, monitor continuously 5.2.1 and 4.14.6 (25 Oct 2026) CycloneDX ingest releases

Dependency-Track deserves a note. Version 5.0 went GA on 9 June 2026 with stateless API servers coordinating through PostgreSQL only (H2, MySQL and SQL Server are dropped), no in-place upgrade from v4 (an offline migrator instead), and at least six months of security fixes on the 4.14.x line. The 5.2 line added GitHub OIDC workload identity for uploads, according to the gh-upload-sbom README, so CI no longer needs a long-lived API key.

A minimal, pinned way to produce both formats from a built image:

# Install a pinned Syft into ./bin (no sudo, no system-wide install)
curl -sSfL https://get.anchore.io/syft | sh -s -- -b ./bin v1.57.1
./bin/syft version

# Scan the image you ship, by digest, and pin the output spec versions
# so consumers get a format they can parse (SPDX 2.3, CycloneDX 1.6)
IMAGE="ghcr.io/acme/api@sha256:<digest>"
./bin/syft scan "$IMAGE" \
  -o spdx-json@2.3=sbom.spdx.json \
  -o cyclonedx-json@1.6=sbom.cdx.json

# Scan the SBOM, not the image again, and fail on high or critical
grype sbom:./sbom.cdx.json --fail-on high --only-fixed

Why pin @2.3 and @1.6? Syft's format docs say it uses the latest supported version when you do not specify one, and spdx-json now lists 3.0 as supported. (In the v1.57.1 source the unpinned SPDX default is still 2.3, while CycloneDX already defaults to 1.7, so docs and code do not quite agree; pinning removes the guesswork.) GitHub's actions/attest detects SPDX by looking for the 2.x spdxVersion and SPDXID fields, which an SPDX 3 document does not have. Move to newer versions when every consumer in your chain says it is ready, not when the generator is.

The full CI version of this, with attestations, Grype in SARIF and an upload to Dependency-Track, is in the companion guide: how to generate an SBOM with Syft in GitHub Actions. If you want the scanning side in more depth, the Trivy and Grype guide covers it.

FAQ

What is the difference between SPDX and CycloneDX?

Both are machine-readable SBOM formats that list components, versions, identifiers and relationships. SPDX (Linux Foundation, current stable 3.0.1, with 2.3 still the common default) has deeper licensing detail; CycloneDX (OWASP, 1.7, standardised as ECMA-424) is security-oriented and carries VEX, services and cryptographic assets in the same schema. Most generators, including Syft and Trivy, can write either.

Is an SBOM legally required?

In the EU, the Cyber Resilience Act requires manufacturers of products with digital elements to draw up an SBOM covering at least top-level dependencies, applying from 11 December 2027, and to provide it to market surveillance authorities on a reasoned request. In the US, CISA's 2026 minimum elements are guidance, but they increasingly appear in procurement contracts.

Does an SBOM need to be public?

No. The CRA places it in technical documentation that authorities can request; it does not require publication. Many vendors share SBOMs with customers under contract, or attach them to images as signed attestations so only parties who can pull the image can read them.

What is VEX and do I need it?

VEX is a statement of whether a product is actually affected by a specific vulnerability, with a justification. If you publish SBOMs to customers, you will need it, because they will scan your SBOM and send you every CVE match. OpenVEX and CycloneDX VEX are the most commonly supported carriers.

What I would do this quarter

If I were starting from nothing this quarter, I would not write an SBOM policy first. I would add Syft to the release workflow, pinned, scanning the pushed image by digest, emitting SPDX 2.3 and CycloneDX 1.6, and attach both as signed attestations. I would stand up Dependency-Track 5.2 on its own Postgres, upload the CycloneDX file from CI using OIDC rather than a stored key, and start a vex/ directory in the repo for the "not affected" decisions we actually reviewed. Then I would pick one released image and compare its SBOM against what is really in it, by hand, once. That single afternoon will tell you more about your SBOM quality than any format debate.

Related: CRA Reporting Went Live on 11 September, Sigstore Explained (2026) and Vulnerability Management in 2026.

Spot an error or something out of date? Email me and I will fix it and log it on the corrections page.

I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.

Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.


Related