How to Read an SBOM: CycloneDX vs SPDX Explained with Real Examples
Published April 21, 2026
What Is an SBOM and Why You Need One
A Software Bill of Materials (SBOM) is a machine-readable inventory of every component inside a software artifact. Think of it as a nutrition label for software — it lists all ingredients (packages), their versions, where they came from (supplier), and how they relate to each other (dependency tree).
SBOMs became a regulatory requirement in the United States with Executive Order 14028 (May 2021), which mandates that all software sold to the federal government must include an SBOM. The EU Cyber Resilience Act (CRA), taking effect in 2027, extends this requirement to all products with digital elements sold in the European market — affecting virtually every software vendor globally.
Beyond compliance, SBOMs are operationally critical. When a new CVE like Log4Shell (CVE-2021-44228) is disclosed, organizations with SBOMs can immediately identify which products and deployments contain the affected component. Without SBOMs, this identification requires re-scanning every artifact — a process that can take days or weeks across large environments.
Two formats dominate the SBOM landscape: CycloneDX (maintained by OWASP) and SPDX (maintained by the Linux Foundation). Both are machine-readable, widely supported, and suitable for compliance. This guide explains how to read both formats with annotated real-world examples.
CycloneDX Format: Annotated Example
CycloneDX is a JSON-first SBOM format designed for security use cases. It was created by OWASP specifically for vulnerability identification, license compliance, and component analysis. The format is simpler to parse than SPDX and includes native support for vulnerability data (VEX) within the same document.
{
"bomFormat": "CycloneDX", // Format identifier
"specVersion": "1.5", // Schema version
"version": 1, // BOM version (increment on updates)
"metadata": {
"timestamp": "2026-04-21T10:30:00Z",
"tools": [{
"vendor": "ScanRook",
"name": "scanner",
"version": "1.14.2"
}],
"component": { // The subject being described
"type": "container",
"name": "myapp",
"version": "v2.1.0"
}
},
"components": [ // All discovered components
{
"type": "library",
"name": "express",
"version": "4.18.2",
"purl": "pkg:npm/express@4.18.2", // Package URL (universal ID)
"licenses": [{ "license": { "id": "MIT" } }],
"supplier": { "name": "TJ Holowaychuk" }
},
{
"type": "library",
"name": "lodash",
"version": "4.17.21",
"purl": "pkg:npm/lodash@4.17.21",
"licenses": [{ "license": { "id": "MIT" } }]
},
{
"type": "library",
"name": "openssl",
"version": "3.1.4-r2",
"purl": "pkg:apk/alpine/openssl@3.1.4-r2",
"licenses": [{ "license": { "id": "Apache-2.0" } }]
}
],
"dependencies": [ // Dependency relationships
{
"ref": "pkg:npm/express@4.18.2",
"dependsOn": [
"pkg:npm/body-parser@1.20.1",
"pkg:npm/cookie@0.5.0"
]
}
]
}Key fields to understand:
- purl (Package URL) — The universal identifier for a component. Format:
pkg:ecosystem/name@version. This is what vulnerability databases use for matching. - type — Component classification: library, framework, application, container, device, firmware, file, or operating-system.
- licenses — SPDX license identifiers (MIT, Apache-2.0, GPL-3.0-only, etc.).
- dependencies — The dependency graph showing which components depend on which others.
- metadata.tools — Which tool generated the SBOM and at what version (provenance).
SPDX Format: Annotated Example
SPDX (Software Package Data Exchange) is an ISO standard (ISO/IEC 5962:2021) maintained by the Linux Foundation. It predates CycloneDX and was originally designed for license compliance. SPDX is more expressive for complex supply chain relationships and supports multiple serialization formats (JSON, RDF, tag-value, YAML).
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0", // License for the SBOM itself
"SPDXID": "SPDXRef-DOCUMENT",
"name": "myapp-v2.1.0",
"documentNamespace": "https://scanrook.io/sbom/myapp-v2.1.0-abc123",
"creationInfo": {
"created": "2026-04-21T10:30:00Z",
"creators": [
"Tool: ScanRook-1.14.2",
"Organization: Acme Corp"
]
},
"packages": [
{
"SPDXID": "SPDXRef-Package-express",
"name": "express",
"versionInfo": "4.18.2",
"supplier": "Person: TJ Holowaychuk",
"downloadLocation": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"filesAnalyzed": false,
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"copyrightText": "Copyright (c) 2009-2014 TJ Holowaychuk",
"externalRefs": [{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:npm/express@4.18.2"
}]
},
{
"SPDXID": "SPDXRef-Package-openssl",
"name": "openssl",
"versionInfo": "3.1.4-r2",
"supplier": "Organization: Alpine Linux",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": false,
"licenseConcluded": "Apache-2.0",
"licenseDeclared": "Apache-2.0",
"copyrightText": "NOASSERTION",
"externalRefs": [{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:apk/alpine/openssl@3.1.4-r2"
}]
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-DOCUMENT",
"relationshipType": "DESCRIBES",
"relatedSpdxElement": "SPDXRef-Package-express"
},
{
"spdxElementId": "SPDXRef-Package-express",
"relationshipType": "DEPENDS_ON",
"relatedSpdxElement": "SPDXRef-Package-body-parser"
}
]
}Key differences from CycloneDX:
- SPDXID — Internal reference identifier. Used in the relationships section to express how components connect.
- licenseConcluded vs licenseDeclared — SPDX distinguishes between what the package declares its license to be and what analysis concluded it actually is (they can differ).
- downloadLocation — Where the package can be obtained. Required field (use NOASSERTION if unknown).
- relationships — More expressive than CycloneDX dependencies. Types include DEPENDS_ON, BUILD_TOOL_OF, CONTAINS, GENERATED_FROM, and more.
- dataLicense — The license of the SBOM document itself (always CC0-1.0 for SPDX).
CycloneDX vs SPDX: Side-by-Side Comparison
| Feature | CycloneDX | SPDX |
|---|---|---|
| Maintained by | OWASP | Linux Foundation |
| ISO standard | No (ECMA pending) | Yes (ISO 5962) |
| Primary focus | Security/vulnerability | Licensing/provenance |
| Serialization | JSON, XML | JSON, RDF, tag-value, YAML |
| VEX support | Native (inline) | Separate document |
| Complexity | Simpler, flatter | More expressive, verbose |
| Dependency graph | Basic (dependsOn) | Rich (20+ relationship types) |
| Tool support | Syft, Trivy, ScanRook, Grype | Syft, Trivy, MS SBOM Tool, FOSSology |
| Package URL (purl) | First-class field | External reference |
| Best for | DevSecOps, vulnerability mgmt | Legal compliance, supply chain |
How to Generate an SBOM
Multiple tools can generate SBOMs from container images, source repositories, and binary artifacts. Here are the most common approaches:
ScanRook
Every ScanRook scan automatically generates a complete package inventory (equivalent to an SBOM). The scan report includes all packages, versions, ecosystems, and licenses — available via the dashboard or API.
# Scan and get full package inventory
scanrook scan container ./my-image.tar --format json --out report.json
# The report.json contains packages[], files[], and findings[]
# Upload via dashboard for web-based explorationSyft (Anchore)
Syft is a dedicated SBOM generation tool that outputs in both CycloneDX and SPDX formats. It reads container images, file systems, and archives.
# CycloneDX output
syft my-image:latest -o cyclonedx-json > sbom-cdx.json
# SPDX output
syft my-image:latest -o spdx-json > sbom-spdx.json
# From a directory
syft dir:/path/to/project -o cyclonedx-jsonTrivy (Aqua Security)
Trivy includes SBOM generation alongside its vulnerability scanning capabilities. It can output CycloneDX and SPDX.
# CycloneDX SBOM
trivy image --format cyclonedx my-image:latest > sbom.json
# SPDX SBOM
trivy image --format spdx-json my-image:latest > sbom-spdx.jsonHow to Read the Dependency Tree
An SBOM's dependency tree reveals which components are direct dependencies versus transitive (indirect) dependencies. This distinction matters for remediation — you can directly update a direct dependency, but a transitive dependency requires updating the intermediate package that pulls it in.
# Example dependency tree from CycloneDX:
#
# myapp (your application)
# ├── express@4.18.2 (direct dependency)
# │ ├── body-parser@1.20.1 (transitive)
# │ │ └── raw-body@2.5.1 (transitive, depth 2)
# │ ├── cookie@0.5.0 (transitive)
# │ └── qs@6.11.0 (transitive)
# ├── lodash@4.17.21 (direct dependency)
# └── pg@8.11.3 (direct dependency)
# └── pg-protocol@1.6.0 (transitive)
# If a CVE affects raw-body@2.5.1:
# - You cannot update raw-body directly (it is transitive)
# - You need body-parser to release a version using a fixed raw-body
# - Or express to bump body-parser
# - Or you override/force the resolution in your lockfileIn CycloneDX, dependencies are expressed in the dependencies array where each entry lists what it depends on. In SPDX, relationships use DEPENDS_ON relationship types between SPDXIDs. Both produce the same logical tree but with different structural representations.
Understanding the dependency depth is crucial for risk assessment. A vulnerable component deep in the tree (depth 3+) is often harder to fix but may also be harder to reach from an attacker's perspective — the vulnerable function may not be reachable through the actual call paths in your application.
SBOM Diff: Tracking Changes Between Versions
Comparing SBOMs between versions reveals exactly what changed in your software supply chain. A diff shows added components, removed components, and version changes — making it immediately clear whether a release introduced new dependencies or updated existing ones.
# Comparing SBOM v2.0 to v2.1:
ADDED:
+ pkg:npm/zod@3.22.4 (new validation library)
+ pkg:npm/@types/zod@3.22.4 (type definitions)
REMOVED:
- pkg:npm/joi@17.9.2 (replaced by zod)
- pkg:npm/@hapi/hoek@9.3.0 (joi dependency, no longer needed)
CHANGED:
~ pkg:npm/express@4.18.1 → 4.18.2 (patch update)
~ pkg:npm/lodash@4.17.20 → 4.17.21 (security fix)
~ pkg:apk/alpine/openssl@3.1.3 → 3.1.4-r2 (CVE fix)
SUMMARY: +2 added, -2 removed, 3 updated
NET CHANGE: 0 (same component count)SBOM diffs are particularly valuable in regulated environments where change control processes require documenting what changed between releases. They also help security teams assess whether an update introduced new risk (new dependencies from unknown suppliers) or reduced it (security patches applied).
Tools like cyclonedx-cli diff and spdx-tools compare can automate this comparison. ScanRook provides diff capability between scan reports in the dashboard — upload the same artifact at different versions and compare the package inventories.
Regulatory Requirements
US Executive Order 14028 (May 2021)
Requires all software sold to the US federal government to include an SBOM. NIST defined minimum elements: supplier name, component name, version, unique identifier, dependency relationships, author of the SBOM data, and timestamp. Both CycloneDX and SPDX satisfy these minimum elements.
Applies to: any organization selling software or cloud services to US federal agencies. The requirement cascades — if your customer is a federal contractor, they may require SBOMs from their vendors (you) to meet their own compliance obligations.
EU Cyber Resilience Act (CRA) — Effective 2027
Requires all products with digital elements sold in the EU market to include an SBOM. Unlike EO 14028 which applies only to government procurement, the CRA applies to the entire commercial market. Penalties for non-compliance include fines up to 15 million euros or 2.5% of global annual turnover.
The CRA also mandates vulnerability handling processes: coordinated disclosure, security updates for the product lifetime, and notification of actively exploited vulnerabilities to ENISA within 24 hours. SBOMs are the foundation that makes automated vulnerability tracking possible. See our compliance scanning guide for framework-specific requirements.
FDA Cybersecurity Requirements (Medical Devices)
The FDA requires SBOMs for all medical device submissions as of March 2023. Both commercial and open source components must be listed. The requirement applies to both premarket submissions (510(k), PMA) and postmarket monitoring processes.
Practical Tips for Working with SBOMs
- Store SBOMs alongside artifacts. Attach SBOMs to container images using OCI annotations or Cosign attestations. This ensures the SBOM always travels with the artifact it describes.
- Automate generation in CI/CD. Generate SBOMs as part of every build, not manually or on-demand. This ensures every deployed version has a current inventory.
- Validate SBOM completeness. An SBOM that only lists top-level dependencies misses the transitive tree where most vulnerabilities hide. Use tools that enumerate the full dependency graph.
- Use purl for cross-tool interoperability. Package URLs (purl) are the universal identifier that all tools understand. Ensure your SBOMs include purl references for every component to enable cross-referencing with vulnerability databases.
- Consider VEX for false positive reduction. If your SBOM lists a component with a known CVE but your product is not actually affected (the vulnerable function is not called), publish a VEX statement. This prevents downstream consumers from raising false alarms.
- Version your SBOMs. CycloneDX includes a
versionfield; increment it when you update the SBOM for the same artifact (e.g., after adding VEX data or correcting a component entry).
Frequently Asked Questions
What is an SBOM?
A Software Bill of Materials is a machine-readable inventory of all components, libraries, and dependencies in a software artifact — like an ingredient list for software.
What is the difference between CycloneDX and SPDX?
CycloneDX (OWASP) is security-focused with simpler structure and built-in VEX support. SPDX (Linux Foundation, ISO 5962) is broader, covering licensing and provenance with richer relationship types. Both satisfy regulatory SBOM requirements.
Is an SBOM legally required?
For US federal government suppliers, yes (EO 14028). For all software in the EU market, yes starting 2027 (Cyber Resilience Act). For FDA-regulated medical devices, yes since March 2023.
How do I generate an SBOM?
Tools like ScanRook, Syft, and Trivy generate SBOMs from container images and source code. ScanRook produces a package inventory with every scan. Syft and Trivy output in CycloneDX or SPDX format.
Which SBOM format should I use?
For security/vulnerability use cases: CycloneDX. For legal/licensing compliance: SPDX. For maximum interoperability: generate both from the same source data using tools like Syft.
How often should I regenerate my SBOM?
Every time you build a new version. Integrate SBOM generation into your CI/CD pipeline so every artifact has a current, accurate inventory.
Can an SBOM tell me if my software is vulnerable?
An SBOM alone lists components but does not indicate vulnerability status. Cross-reference SBOM data against vulnerability databases (NVD, OSV) to identify affected components. CycloneDX supports embedding vulnerability status via VEX.
What is VEX and how does it relate to SBOM?
VEX (Vulnerability Exploitability eXchange) is a companion document stating whether a known vulnerability actually affects your product. It lets producers mark CVEs as "not affected" or "fixed", reducing false positives for SBOM consumers.
Generate Your First SBOM
Upload a container image, source archive, or binary to ScanRook and get a complete package inventory with vulnerability and license data in seconds. No SBOM tool configuration required.