Skip to main content
CleanStart

Knowledge Hub

CycloneDX SBOM: Supply Chain Security

Generating CycloneDX SBOMs for Container Images

An SBOM (Software Bill of Materials) is a complete list of every component, library, and dependency in your software application. It's like a product label on food. A nutrition label lists calories, proteins, fats, and ingredients. An SBOM lists every library, version, vulnerability status, and license. Just as you read a nutrition label to understand what you're eating, security teams read SBOMs to understand what code they're running.

Why SBOMs Matter for Security

Vulnerability detection becomes immediate when a new vulnerability is discovered. When a vulnerability like CVE-2022-3786 is discovered in OpenSSL 3.0.1, your SBOM immediately tells you whether you're running that version. If the SBOM lists OpenSSL 3.0.1, you must urgently update. If it lists a different version, you are not affected.

License compliance requires careful tracking of all dependencies. The legal team needs to ensure all licenses are compatible. The SBOM lists all dependencies with their licenses—for example, Flask and NumPy with BSD licenses are compatible, but GPL-licensed-library might be incompatible with commercial software and requires careful evaluation.

Supply chain verification depends on knowing what's in your images. To determine whether an image is safe to run in production, check the SBOM for key indicators: are all packages from official registries? Are there any suspicious packages? Are there known vulnerabilities? Are all components signed?

Compliance and audit requires demonstrating knowledge of your software composition. Regulations require that you "demonstrate you know what's in your software." Show the SBOM to auditors to prove exactly which components are running in production. This satisfies audit requirements.

CycloneDX vs SPDX: Both Are Standards

Both are standardized SBOM formats that serve complementary purposes. CycloneDX (OWASP standard) focuses on risk and security with component vulnerabilities and licensing information in JSON, XML, or Protocol Buffers format. Tool support is modern with growing adoption. SPDX (Linux Foundation standard) emphasizes license compliance and legal tracking with comprehensive file-level data in JSON, RDF, TagValue, or YAML format. Tool support is excellent and widely used.

When to use each: Use CycloneDX for container security, vulnerability management, and risk assessment. Use SPDX for open-source compliance and license auditing. The best practice is to generate both and use them together.

CycloneDX Structure: The JSON Schema

A CycloneDX SBOM is a JSON file with this structure:

{  "bomFormat": "CycloneDX",  "specVersion": "1.4",  "serialNumber": "urn:uuid:12345678-1234-5678-9999-000000000000",  "version": 1,  "metadata": {    "timestamp": "2024-03-20T15:30:00Z",    "tools": [      {        "vendor": "anchore",        "name": "syft",        "version": "0.68.1"      }    ],    "component": {      "bom-ref": "my-app",      "type": "application",      "name": "my-app",      "version": "1.0.0"    }  },  "components": [    {      "bom-ref": "python-3.11.0",      "type": "application",      "name": "python",      "version": "3.11.0",      "purl": "pkg:deb/debian/python3.11@3.11.0-1+0~20230427.6~debian~bullseye?arch=amd64",      "licenses": [        {          "license": {            "id": "PSF-2.0"          }        }      ]    },    {      "bom-ref": "flask-2.3.0",      "type": "library",      "name": "flask",      "version": "2.3.0",      "purl": "pkg:pypi/flask@2.3.0",      "licenses": [        {          "license": {            "id": "BSD-3-Clause"          }        }      ],      "externalReferences": [        {          "type": "security-advisory",          "url": "https://nvd.nist.gov/vuln/detail/CVE-XXXX-XXXXX"        }      ]    },    {      "bom-ref": "jinja2-3.1.2",      "type": "library",      "name": "jinja2",      "version": "3.1.2",      "purl": "pkg:pypi/jinja2@3.1.2",      "licenses": [        {          "license": {            "id": "BSD-3-Clause"          }        }      ]    }  ],  "vulnerabilities": []}

Key fields explained:

Field

Purpose

bomFormat

Always "CycloneDX"

specVersion

Schema version (1.4 is current)

serialNumber

Unique ID (UUID) for this SBOM

version

Version of this SBOM (increment when SBOM changes)

metadata.timestamp

When the SBOM was generated

metadata.tools

Tool that created this SBOM (syft, cyclonedx, etc.)

components[]

Array of every component (library, package, etc.)

components[].bom-ref

Unique reference for this component

components[].type

Type: library, application, framework, etc.

components[].purl

Package URL (standardized package identifier)

components[].licenses[]

SPDX license IDs

vulnerabilities[]

Known CVEs affecting these components

Generating CycloneDX SBOM: Practical Examples

Using Syft (Most Common)

Syft is an excellent SBOM generator that works with container images.

# Install syftcurl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin # Generate SBOM from Docker imagesyft myapp:1.0.0 --output cyclonedx-json > sbom.json # Generate from local directorysyft /path/to/code --output cyclonedx-json > sbom.json # Generate from Dockerfilesyft ghcr.io/python:3.11-slim --output cyclonedx-json > sbom.json # Output formatssyft myapp:1.0.0 --output cyclonedx-json   # JSONsyft myapp:1.0.0 --output cyclonedx-xml    # XMLsyft myapp:1.0.0 --output table             # Human-readable tablesyft myapp:1.0.0 --output json              # Full Syft JSON (more detailed)

Example output:

$ syft python:3.11-slim --output cyclonedx-json > sbom.json$ head -50 sbom.json {  "bomFormat": "CycloneDX",  "specVersion": "1.4",  "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",  "version": 1,  "metadata": {    "timestamp": "2024-03-20T15:32:00Z",    "tools": [      {        "vendor": "anchore",        "name": "syft",        "version": "0.68.1"      }    ],    "component": {      "bom-ref": "python-3.11-slim",      "type": "container",      "name": "python",      "version": "3.11-slim"    }  },  "components": [    {      "bom-ref": "python-3.11.0",      "type": "application",      "name": "python",      "version": "3.11.0",      "purl": "pkg:deb/debian/python3.11@3.11.0-1+0~20230427.6~debian~bullseye"    },    ...  ]}

Using CycloneDX Tool (Language-Specific)

Different package managers have native CycloneDX generators that integrate with your build system.

Python:

# Using cyclonedx-python-libpip install cyclonedx-python # Generate from requirements.txtcyclonedx-python -o cyclonedx-json -o requirements.txt > sbom.json # Generate from environmentcyclonedx-python -o cyclonedx-json > sbom.json

Node.js:

# Using cyclonedx-npmnpm install -g @cyclonedx/npm # Generate from package.jsoncyclonedx-npm --output-file sbom.json # View the sbomcat sbom.json | jq '.'

Java:

# Using cyclonedx-maven-plugin in pom.xml<plugin>    <groupId>org.cyclonedx</groupId>    <artifactId>cyclonedx-maven-plugin</artifactId>    <version>2.7.10</version></plugin> # Generate SBOMmvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom # Output in target/bom.json

Parsing and Using CycloneDX with jq

jq is a JSON query tool perfect for analyzing SBOMs.

# Extract all component names and versionsjq '.components[] | "\(.name):\(.version)"' sbom.json # Output:# "python:3.11.0"# "flask:2.3.0"# "jinja2:3.1.2" # Find all components with a specific namejq '.components[] | select(.name == "flask")' sbom.json # Count total componentsjq '.components | length' sbom.json # Output: 847 # List all licenses usedjq '.components[].licenses[].license.id' sbom.json | sort | uniq# Output:# "BSD-3-Clause"# "PSF-2.0"# "Apache-2.0" # Find GPL-licensed packages (potential compliance issue)jq '.components[] | select(.licenses[].license.id | contains("GPL"))' sbom.json # Find components with vulnerabilitiesjq '.vulnerabilities[] | "\(.ref): \(.id) (\(.ratings[0].severity))"' sbom.json

Integrating SBOM with Vulnerability Tools

SBOM + vulnerability database = automated risk detection.

Trivy: Scan image and generate SBOM with vulnerabilities

# Generate SBOM and scan for vulnerabilitiestrivy image --format=cyclonedx --output=sbom.json myapp:1.0.0 # View vulnerabilities in SBOMjq '.vulnerabilities[] | {ref: .ref, id: .id, severity: .ratings[0].severity}' sbom.json

Grype: Scan SBOM directly

# Generate SBOMsyft myapp:1.0.0 --output cyclonedx-json > sbom.json # Scan the SBOM for vulnerabilitiesgrype sbom:sbom.json # Output:# NAME            INSTALLED   FIXED        VULNERABILITY   SEVERITY# python          3.11.0      3.11.5       CVE-2023-XXXXX   HIGH# flask           2.3.0       2.3.3        CVE-2023-YYYYY   MEDIUM

Dependency-Track: Visual vulnerability management

Dependency-Track is a web application that tracks vulnerabilities from SBOMs.

# Upload SBOM to Dependency-Trackcurl -X POST \  -H "X-API-Key: your-api-key" \  -F "bom=@sbom.json" \  https://dependency-track.example.com/api/v1/bom

Real-World Example: Complete SBOM Workflow

Scenario: You have a Python Flask application. Generate SBOM, check for vulnerabilities, and validate licenses.

Step 1: Build Docker image

docker build -t myflaskapp:1.0.0 .

Step 2: Generate SBOM from image

syft myflaskapp:1.0.0 --output cyclonedx-json > sbom.json # View summaryjq '.components | length' sbom.json# 347 components # Show Python dependenciesjq '.components[] | select(.purl | contains("pypi")) | "\(.name):\(.version)"' sbom.json# flask:2.3.0# jinja2:3.1.2# werkzeug:2.3.0# ... more

Step 3: Check for vulnerabilities

# Scan SBOM with Grypegrype sbom:sbom.json # Output:NAME            INSTALLED   FIXED        VULNERABILITY   SEVERITYwerkzeug        2.2.0       2.2.2        CVE-2023-25761  HIGHjinja2          3.1.0       3.1.1        CVE-2023-28719  MEDIUM

Step 4: Check licenses

# Extract all licensesjq '.components[].licenses[].license.id' sbom.json | sort | uniq -c | sort -rn # Output:# 120 "BSD-3-Clause"#  45 "Apache-2.0"#  30 "MIT"#   2 "GPL-3.0-only"  ← Potential compliance issue! # Identify GPL packagesjq '.components[] | select(.licenses[].license.id | contains("GPL")) | .name' sbom.json# gpl-library # Decide: Replace with non-GPL alternative, or accept GPL obligation

Step 5: Create attestation

{  "sbom_version": "1.0.0",  "created_at": "2024-03-20T15:30:00Z",  "image": "myflaskapp:1.0.0",  "total_components": 347,  "critical_vulnerabilities": 0,  "high_vulnerabilities": 1,  "medium_vulnerabilities": 3,  "licenses": ["BSD-3-Clause", "Apache-2.0", "MIT"],  "compliance": "PASSED - No critical vulnerabilities, all licenses approved",  "signed_by": "CI/CD system"}

Step 6: Store SBOM with image

# Option 1: Store as artifactcp sbom.json sbom-myflaskapp-1.0.0.jsongit add sbom-myflaskapp-1.0.0.json  # Or store in artifact registry # Option 2: Attach to image (OCI Image Format 1.1)syft myflaskapp:1.0.0 --output cyclonedx-json | \  oras attach myregistry/myflaskapp:1.0.0 \  --artifact-type application/vnd.cyclonedx+json

CycloneDX in CI/CD Pipeline

name: Generate SBOM and Check Vulnerabilities on:  push:    branches: [main] jobs:  sbom:    runs-on: ubuntu-latest    steps:      # Build image      - name: Build Docker image        run: docker build -t myapp:${{ github.sha }} .       # Generate SBOM      - name: Generate SBOM with Syft        run: |          curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin          syft myapp:${{ github.sha }} --output cyclonedx-json > sbom.json       # Check for vulnerabilities      - name: Scan SBOM with Grype        run: |          curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin          grype sbom:sbom.json --fail-on high       # Check licenses      - name: Validate licenses        run: |          # Fail if GPL found (example policy)          if jq '.components[] | select(.licenses[].license.id | contains("GPL"))' sbom.json | grep -q .; then            echo "ERROR: GPL-licensed packages found, not allowed"            exit 1          fi          echo "All licenses approved"       # Upload SBOM as artifact      - name: Upload SBOM        uses: actions/upload-artifact@v3        with:          name: sbom          path: sbom.json       # Deploy only if all checks pass      - name: Deploy to production        if: success()        run: echo "Deploying ${{ github.sha }} with clean SBOM"

Recap

An SBOM provides a complete list of all components in your software. CycloneDX is the OWASP standard for security-focused SBOM that works well with container security tools. Syft is the easiest tool to generate SBOM from Docker images. Grype scans SBOM for vulnerabilities automatically. jq is a powerful tool for querying and analyzing SBOM JSON. Store SBOM with your image to prove what's inside each image. Automation checks licenses and vulnerabilities in your CI/CD pipeline.

Next Steps

Read End-to-End Secure Deployment to practice SBOM generation. Read What is a CVE? to understand vulnerabilities. Try: syft alpine:latest --output cyclonedx-json to generate your first SBOM.

Common mistakes to avoid:

Avoid skipping SBOM regeneration when dependencies change, as this leaves your SBOM out of date and inaccurate. Never store SBOM in version control without updating it, as the version in git will not match your deployed image. Don't ignore GPL licenses as this creates legal compliance issues with your organization. Avoid running vulnerable components without patching, as this leaves security vulnerabilities unresolved. Never skip validating SBOM format, as a malformed file won't parse correctly.