Git, CI/CD & GitOpsLXVIII · SBOMFormats
SPDX and CycloneDX — the two formats, the trade-offs, the tooling
What you'll learn
- Describe the structural difference between SPDX and CycloneDX
- Choose SPDX or CycloneDX based on downstream consumer and ecosystem fit
- List the production-grade tools that emit and ingest each format
- Recognise why converting between formats loses information
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
SPDX and CycloneDX are the two SBOM formats with production-grade tooling. They are not interchangeable: they model components differently, optimise for different consumers, and produce different artifacts. The choice between them is a downstream decision — the question is “what consumes the SBOM?” — and the default is to pick one, generate it at build time, and treat the conversion to the other as an exception rather than a routine.
SPDX and CycloneDX
SPDX began at the Linux Foundation as a license-focused format whose primary unit is the package. Strengths: SPDX identifiers are the standard for license expression; SPDX is named in U.S. federal SBOM framing and the EU Cyber Resilience Act discussion; SPDX documents attach cleanly to the in-toto attestation envelope. Weaknesses: heavier than CycloneDX; permissive schema.
CycloneDX began at OWASP as a component-graph format whose
primary unit is the component organised into a dependency
graph with explicit dependsOn edges. Strengths: first-class
dependency edges; native VEX, services, and properties fields;
Dependency-Track, Mend, Snyk, trivy, and grype ingest
CycloneDX natively. Weaknesses: license expression tooling is
less mature.
flowchart LR
A["Build artifact"] --> B["Generator"]
B --> C["SPDX"]
B --> D["CycloneDX"]
C --> E["License / gov tooling"]
D --> F["Vuln management tooling"]
C -. "convert (lossy)" .-> D
D -. "convert (lossy)" .-> C
The conversion trap
A SBOM in SPDX and a SBOM in CycloneDX for the same artifact
will not match field-for-field: SPDX’s license-concluded
field has no CycloneDX analogue; CycloneDX’s dependsOn graph
has no SPDX field of the same name. A conversion tool can map
the common fields (name, version, hash, supplier) but the rest
is lost.
Production discipline
- Pick the format the downstream consumer ingests natively: SPDX for license and gov tooling; CycloneDX for vuln management.
- Generate with the format-native tool (syft with the matching output flag, or cyclonedx-bom for CycloneDX) and validate against the official JSON Schema.
- Do not convert between formats at consumer time as a default. Run two generators if two consumers need two formats.
Cross-course references
- Git, CI/CD & GitOps — Part LXVIII-05 (Vulnerability Matching) is the downstream consumer that motivates CycloneDX’s component-graph model.
Quiz
Knowledge check · 4 questions
Q1. A team's downstream vulnerability platform ingests CycloneDX natively and they need a dependency graph with explicit `dependsOn` edges. Which format should they pick?
Q2. Converting an SBOM from SPDX to CycloneDX (or the reverse) at consumer time preserves every field from the source format.
Q3. Name the structural difference between SPDX's primary model and CycloneDX's primary model.
Q4. Diagnose why the vulnerability matcher misses half the components and recommend the fix.
Team T picks SPDX because the legal team's license checker is SPDX-only. The CI pipeline runs syft and emits sbom.spdx.json. The downstream matcher (Dependency-Track) requires CycloneDX. A pipeline step converts the SPDX document to CycloneDX at consumer time. The matcher ingests the converted document but the dependency-graph traversal finds no edges because SPDX's package relationship model does not map cleanly to CycloneDX's `dependsOn` graph.
Passing score: 75%. Answers are checked in this browser.