CycloneDX and SPDX: SBOM Format Comparison and Tooling

CycloneDX and SPDX: SBOM Format Comparison and Tooling

Two SBOM formats dominate the software supply chain landscape: CycloneDX and SPDX. Both are open standards, both serve overlapping purposes, and both are supported by major tools. Choosing between them — or using both — depends on your use case, tooling, and compliance requirements.

What Is an SBOM Format?

A Software Bill of Materials (SBOM) is an inventory of all software components in an artifact. The format defines how that inventory is structured and serialized. A standardized format means different tools can produce and consume SBOMs interchangeably — a scanner generates it, a vulnerability database reads it, a compliance tool validates it.

The US Executive Order on Improving the Nation's Cybersecurity (EO 14028) accelerated SBOM adoption by requiring them for software sold to federal agencies. NTIA's minimum elements for SBOMs include both SPDX and CycloneDX as accepted formats.

SPDX Overview

SPDX (Software Package Data Exchange) was developed by the Linux Foundation and is maintained by the SPDX workgroup. It has been standardized as ISO/IEC 5962:2021.

Origin: Started in 2010 for license compliance in open source software. Vulnerability and security use cases were added later.

Primary use case: License compliance, legal review, open source governance.

Supported formats: Tag-value (.spdx), JSON (.spdx.json), YAML (.spdx.yaml), RDF/XML (.rdf), spreadsheet.

Specification version: SPDX 2.3 is current; SPDX 3.0 is in development with significant changes.

SPDX Structure

// spdx.json
{
  "SPDXID": "SPDXRef-DOCUMENT",
  "spdxVersion": "SPDX-2.3",
  "creationInfo": {
    "created": "2024-01-15T10:00:00Z",
    "creators": ["Tool: Syft-0.99.0"],
    "licenseListVersion": "3.22"
  },
  "name": "myapp",
  "dataLicense": "CC0-1.0",
  "documentNamespace": "https://example.com/myapp-abc123",
  "packages": [
    {
      "SPDXID": "SPDXRef-Package-requests-2.31.0",
      "name": "requests",
      "version": "2.31.0",
      "supplier": "Organization: Python Packaging Authority",
      "downloadLocation": "https://pypi.org/project/requests/2.31.0/",
      "filesAnalyzed": false,
      "externalRefs": [
        {
          "referenceCategory": "SECURITY",
          "referenceType": "cpe23Type",
          "referenceLocator": "cpe:2.3:a:python-requests:requests:2.31.0:*:*:*:*:*:*:*"
        },
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:pypi/requests@2.31.0"
        }
      ],
      "licenseConcluded": "Apache-2.0",
      "licenseDeclared": "Apache-2.0",
      "copyrightText": "NOASSERTION"
    }
  ],
  "relationships": [
    {
      "spdxElementId": "SPDXRef-DOCUMENT",
      "relationshipType": "DESCRIBES",
      "relatedSpdxElement": "SPDXRef-Package-requests-2.31.0"
    }
  ]
}

SPDX Relationship Types

SPDX has a rich relationship vocabulary:

  • CONTAINS / CONTAINED_BY
  • DEPENDS_ON / DEPENDENCY_OF
  • BUILD_TOOL_OF / DEV_TOOL_OF / TEST_TOOL_OF
  • GENERATED_FROM / GENERATES
  • DESCRIBED_BY / DESCRIBES
  • OPTIONAL_DEPENDENCY_OF / PROVIDED_DEPENDENCY_OF
  • RUNTIME_DEPENDENCY_OF / DEV_DEPENDENCY_OF

This granularity is valuable for license compliance: you need to know if a GPL library is a build tool (less license exposure) or runtime dependency (full exposure).

CycloneDX Overview

CycloneDX was created by OWASP in 2017, initially for software composition analysis (SCA) in application security contexts.

Origin: OWASP, focused from the start on security and vulnerability management.

Primary use case: Vulnerability management, security scanning, DevSecOps.

Supported formats: XML and JSON (both equally first-class). Protobuf in development.

Specification version: CycloneDX 1.6 is current (1.5 widely supported).

CycloneDX Structure

// cyclonedx.json
{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "serialNumber": "urn:uuid:550e8400-e29b-41d4-a716-446655440000",
  "version": 1,
  "metadata": {
    "timestamp": "2024-01-15T10:00:00Z",
    "tools": [
      {
        "vendor": "Anchore",
        "name": "syft",
        "version": "0.99.0"
      }
    ],
    "component": {
      "type": "container",
      "name": "myapp",
      "version": "v1.2.3"
    }
  },
  "components": [
    {
      "type": "library",
      "name": "requests",
      "version": "2.31.0",
      "purl": "pkg:pypi/requests@2.31.0",
      "cpe": "cpe:2.3:a:python-requests:requests:2.31.0:*:*:*:*:*:*:*",
      "licenses": [
        {"license": {"id": "Apache-2.0"}}
      ],
      "supplier": {
        "name": "Python Packaging Authority",
        "contact": [{"email": "distutils-sig@python.org"}]
      }
    }
  ],
  "dependencies": [
    {
      "ref": "myapp@v1.2.3",
      "dependsOn": ["pkg:pypi/requests@2.31.0"]
    }
  ]
}

CycloneDX Extensions

CycloneDX 1.5+ supports additional BOM types beyond software:

  • SaaSBOM: Software-as-a-Service components
  • HBOM: Hardware Bill of Materials
  • MLBOM: Machine Learning Bill of Materials
  • CBOM: Cryptography Bill of Materials (algorithms used)
  • Vulnerability Disclosure Report (VDR)
  • Vulnerability Exploitability eXchange (VEX)

The VEX format is particularly useful for managing false positives: it lets you publish statements like "CVE-2021-44228 does not affect this product because we don't use the JNDI lookup feature."

Side-by-Side Comparison

Feature SPDX CycloneDX
Standards body Linux Foundation / ISO OWASP
ISO standard Yes (ISO/IEC 5962:2021) No
Primary focus License compliance Security / vulnerability mgmt
Serialization formats Tag-value, JSON, YAML, RDF JSON, XML
Relationship vocabulary Rich (20+ types) Simpler
VEX support Via SPDX 3.0 Native (CDX 1.4+)
Tooling maturity Mature Rapidly growing
US Gov acceptance Yes (NTIA) Yes (NTIA)
PURL support External reference First-class field
Metadata richness Good Excellent
Hardware / ML BOM No Yes (extension types)

Generating Both Formats

Most tools support both. Here's how to generate each:

Syft

# SPDX JSON
syft myapp:latest -o spdx-json > sbom.spdx.json

<span class="hljs-comment"># SPDX tag-value
syft myapp:latest -o spdx > sbom.spdx

<span class="hljs-comment"># CycloneDX JSON
syft myapp:latest -o cyclonedx-json > sbom.cdx.json

<span class="hljs-comment"># CycloneDX XML
syft myapp:latest -o cyclonedx-xml > sbom.cdx.xml

<span class="hljs-comment"># Generate multiple formats at once
syft myapp:latest \
  -o spdx-json=sbom.spdx.json \
  -o cyclonedx-json=sbom.cdx.json

Trivy

# SPDX JSON
trivy image --format spdx-json --output sbom.spdx.json myapp:latest

<span class="hljs-comment"># CycloneDX
trivy image --format cyclonedx --output sbom.cdx.json myapp:latest

cdxgen (CycloneDX-native)

cdxgen generates CycloneDX SBOMs with deep language ecosystem support:

npm install -g @cyclonedx/cdxgen

# Node.js project
cdxgen -t nodejs -o sbom.cdx.json .

<span class="hljs-comment"># Python project
cdxgen -t python -o sbom.cdx.json .

<span class="hljs-comment"># Java Maven project
cdxgen -t maven -o sbom.cdx.json .

<span class="hljs-comment"># Docker image
cdxgen -t docker -o sbom.cdx.json myapp:latest

<span class="hljs-comment"># Auto-detect project type
cdxgen -o sbom.cdx.json .

spdx-sbom-generator

For SPDX-native generation from source:

go install github.com/opensbom-generator/spdx-sbom-generator@latest

# Node.js
spdx-sbom-generator -p . -o sbom.spdx.json --format json

<span class="hljs-comment"># Go modules
spdx-sbom-generator -p . -m go -o sbom.spdx.json --format json

Converting Between Formats

Sometimes you receive an SBOM in one format and need another. CycloneDX CLI handles conversions:

# Install
dotnet tool install --global CycloneDX

<span class="hljs-comment"># Convert SPDX to CycloneDX
cyclonedx convert --input-format spdx-json --output-format json \
  --input-file sbom.spdx.json --output-file sbom.cdx.json

<span class="hljs-comment"># Convert CycloneDX XML to JSON
cyclonedx convert --input-format xml --output-format json \
  --input-file sbom.cdx.xml --output-file sbom.cdx.json

Note: conversion between formats loses some data — SPDX has relationship types that don't map to CycloneDX, and CycloneDX VEX data doesn't exist in SPDX 2.x.

Validating SBOMs

A syntactically valid SBOM is not necessarily a useful one. Validation checks completeness:

CycloneDX Validation

# Validate CycloneDX SBOM
cyclonedx validate --input-format json --input-file sbom.cdx.json --fail-on-errors

<span class="hljs-comment"># Check minimum elements (NTIA compliance)
<span class="hljs-comment"># Each component should have: name, version, supplier, unique identifiers, relationships

SPDX Validation

# Install spdx-tools
pip install spdx-tools

<span class="hljs-comment"># Validate SPDX document
pyspdxtools validate sbom.spdx.json

Custom Validation Script

#!/usr/bin/env python3
"""Validate SBOM quality — check for minimum required fields"""

import json
import sys

def validate_cyclonedx(sbom_path):
    with open(sbom_path) as f:
        sbom = json.load(f)
    
    issues = []
    
    for component in sbom.get('components', []):
        name = component.get('name', 'unknown')
        
        if not component.get('version'):
            issues.append(f"Missing version: {name}")
        
        if not component.get('purl'):
            issues.append(f"Missing PURL: {name}")
        
        licenses = component.get('licenses', [])
        if not licenses:
            issues.append(f"Missing license: {name}")
    
    return issues

if __name__ == '__main__':
    issues = validate_cyclonedx(sys.argv[1])
    if issues:
        print(f"SBOM quality issues ({len(issues)}):")
        for issue in issues[:20]:  # Show first 20
            print(f"  - {issue}")
        sys.exit(1)
    else:
        print("SBOM validation passed")
        sys.exit(0)

Vulnerability Scanning with Each Format

Both formats work with major vulnerability scanners:

# Grype with SPDX
grype sbom:sbom.spdx.json

<span class="hljs-comment"># Grype with CycloneDX
grype sbom:sbom.cdx.json

<span class="hljs-comment"># Trivy scanning an SBOM
trivy sbom sbom.cdx.json

<span class="hljs-comment"># OSV-Scanner
osv-scanner --sbom sbom.cdx.json

Choosing Between Formats

Use SPDX when:

  • You need ISO standards compliance (government contracts, regulated industries)
  • License compliance is your primary concern
  • You're dealing with complex dependency relationships that need rich modeling
  • Your toolchain primarily uses SPDX (common in open source ecosystems)

Use CycloneDX when:

  • Vulnerability management is your primary focus
  • You need VEX statements to suppress false positives
  • You're working in application security / DevSecOps contexts
  • You need SaaSBOM, HBOM, or MLBOM types
  • Your toolchain is OWASP-oriented (Dependency-Track, etc.)

Use both when:

  • You have compliance requirements for SPDX and security operations using CycloneDX
  • Generating both costs nothing extra in CI (Syft supports both simultaneously)

Dependency-Track: SBOM Management Platform

Dependency-Track is an open-source platform that ingests SBOMs and provides continuous vulnerability monitoring, policy enforcement, and audit trails. It supports both CycloneDX (natively) and SPDX.

# Upload SBOM to Dependency-Track
curl -X <span class="hljs-string">"PUT" <span class="hljs-string">"https://dependency-track.example.com/api/v1/bom" \
  -H <span class="hljs-string">'X-Api-Key: your-api-key' \
  -H <span class="hljs-string">'Content-Type: multipart/form-data' \
  -F <span class="hljs-string">"project=my-project-uuid" \
  -F <span class="hljs-string">"bom=@sbom.cdx.json"

<span class="hljs-comment"># Or use the CLI
dt bom upload \
  --server https://dependency-track.example.com \
  --api-key your-api-key \
  --project my-project-uuid \
  --bom sbom.cdx.json

VEX: Managing False Positives

CycloneDX VEX (Vulnerability Exploitability eXchange) solves a real problem: vulnerability scanners report hundreds of CVEs, most of which don't apply to your deployment.

// vex.cdx.json
{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "version": 1,
  "vulnerabilities": [
    {
      "id": "CVE-2021-44228",
      "source": {
        "name": "NVD",
        "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-44228"
      },
      "analysis": {
        "state": "not_affected",
        "justification": "code_not_reachable",
        "detail": "We use log4j-core but JNDI lookup is disabled via log4j2.formatMsgNoLookups=true and the code path using it is unreachable in our deployment."
      },
      "affects": [
        {"ref": "urn:cdx:bom-serial/1#pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1"}
      ]
    }
  ]
}

VEX states: not_affected, affected, fixed, under_investigation.

The formats are converging: SPDX 3.0 adds native VEX support, narrowing the gap between the two. For now, if you need VEX, use CycloneDX.

Both SPDX and CycloneDX are mature enough to build on. The best choice is the one your toolchain supports best — and generating both from CI costs almost nothing.

Read more