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 |
|---|---|
| Always "CycloneDX" |
| Schema version (1.4 is current) |
| Unique ID (UUID) for this SBOM |
| Version of this SBOM (increment when SBOM changes) |
| When the SBOM was generated |
| Tool that created this SBOM (syft, cyclonedx, etc.) |
| Array of every component (library, package, etc.) |
| Unique reference for this component |
| Type: library, application, framework, etc. |
| Package URL (standardized package identifier) |
| SPDX license IDs |
| 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.jsonNode.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.jsonParsing 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.jsonIntegrating 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.jsonGrype: 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 MEDIUMDependency-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/bomReal-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# ... moreStep 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 MEDIUMStep 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 obligationStep 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+jsonCycloneDX 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.
