SBOM Explained (2026): SPDX 3.0 vs CycloneDX 1.7, VEX and the Rules
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:
- The format is not named. SPDX and CycloneDX both qualify as commonly used and machine-readable.
- The floor is top-level dependencies, lower than CISA's 2026 baseline. Meeting CISA's elements more than covers the CRA wording.
- 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.
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
Renovate vs Dependabot in 2026: 119 Managers vs 34, Both Wait 3 Days
Renovate vs Dependabot in 2026, compared on real defaults: 119 Renovate managers vs 34 Dependabot ecosystems, grouping, native auto-merge vs an Actions workflow, Dependabot's new 3-day cooldown, minimumReleaseAge, Mend-hosted vs self-hosted, and which to pick.
12 minOct 31, 2026SAST vs DAST vs SCA vs IAST in 2026: What Each Finds, 34% False Positives
SAST, DAST, SCA and IAST explained for 2026: what each scanner actually finds, real false positive rates (CodeQL at 34% in an ISSTA 2025 study), Semgrep vs CodeQL vs SonarQube vs Snyk vs ZAP, AI triage, and where each belongs in a CI/CD pipeline.
13 minOct 20, 2026Hardened Container Images in 2026: DHI Free, Bitnami Gone, 515 CVEs to 0
Container base images in 2026 compared: Docker Hardened Images went free under Apache 2.0, Bitnami moved its catalog to bitnamilegacy, Chainguard cut its free tier to five images, and a grype scan of 12 base images found 515 CVEs in python:3.13 and zero in four minimal images.
12 min