Skip to main content
Security·14 min read

Software Bill of Materials — What to Say When the Auditor Asks 'What's in Production?'

Complete guide to Software Bill of Materials covering CycloneDX and SPDX formats, generation with Syft and Trivy, signing with cosign, and monitoring with Dependency-Track. Includes compliance context and CI/CD integration.

DT

DevOps Engineer & Technical Writer

What Is an SBOM and Why It Matters

SBOM Lifecycle Build CI Pipeline Generate Syft / Trivy CycloneDX Sign Cosign/Sigstore Store OCI Registry Monitor CVE Feeds Alert New vuln found Continuous monitoring loop — new CVEs trigger re-evaluation Supply Chain Security: Know what is in production at all times

A Software Bill of Materials is an ingredient list for software. It declares every component — libraries, frameworks, modules, and their versions — that makes up your application. Think of it as a nutrition label for code.

Before Log4Shell (December 2021), most organizations could not answer a simple question: "Do we use Log4j, and where?" Teams spent days or weeks auditing every service, container, and deployment. Organizations with SBOMs answered in minutes.

An SBOM lets you:

  • Respond to zero-day disclosures in hours instead of weeks
  • Track license obligations across your software supply chain
  • Meet regulatory requirements (US federal contracts, EU Cyber Resilience Act)
  • Verify that what you built is what you deployed (no tampering)
  • Automate vulnerability monitoring across all components

This guide covers the full lifecycle: generating SBOMs, signing them for authenticity, and monitoring them for new vulnerabilities.

---

SBOM Formats: CycloneDX vs. SPDX

Two formats dominate the SBOM landscape. Choosing between them depends on your primary use case.

CycloneDX (OWASP)

CycloneDX was created by OWASP specifically for security use cases. It excels at vulnerability correlation — linking components to known CVEs.

Key characteristics:

  • JSON and XML output formats
  • Native support for vulnerability data (VEX — Vulnerability Exploitability eXchange)
  • Designed for automation and machine consumption
  • Lighter specification, easier to parse
  • Supports components, services, and dependencies graph
  • Version 1.5 and above includes formulation data (how the software was built)

Example CycloneDX JSON (abbreviated):

{

"bomFormat": "CycloneDX",

"specVersion": "1.5",

"version": 1,

"metadata": {

"timestamp": "2026-07-02T10:00:00Z",

"component": {

"type": "application",

"name": "my-web-app",

"version": "2.1.0"

}

},

"components": [

{

"type": "library",

"name": "express",

"version": "4.18.2",

"purl": "pkg:npm/express@4.18.2",

"licenses": [{"license": {"id": "MIT"}}]

},

{

"type": "library",

"name": "lodash",

"version": "4.17.21",

"purl": "pkg:npm/lodash@4.17.21",

"licenses": [{"license": {"id": "MIT"}}]

}

]

}

SPDX (Linux Foundation)

SPDX (Software Package Data Exchange) is an ISO/IEC 5962:2021 international standard. It was designed for license compliance and is the format required by many legal and procurement teams.

Key characteristics:

  • Tag-value, JSON, XML, and RDF output formats
  • ISO standard (strongest for compliance documentation)
  • Rich license expression syntax (MIT AND Apache-2.0)
  • Package verification codes for integrity checking
  • Relationship types between packages (DEPENDS_ON, CONTAINS, BUILD_TOOL_OF)
  • Stronger in describing file-level licensing

Example SPDX JSON (abbreviated):

{

"spdxVersion": "SPDX-2.3",

"dataLicense": "CC0-1.0",

"SPDXID": "SPDXRef-DOCUMENT",

"name": "my-web-app-sbom",

"documentNamespace": "https://example.com/my-web-app-2.1.0",

"packages": [

{

"SPDXID": "SPDXRef-Package-express",

"name": "express",

"versionInfo": "4.18.2",

"downloadLocation": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",

"licenseConcluded": "MIT",

"externalRefs": [

{

"referenceCategory": "PACKAGE-MANAGER",

"referenceType": "purl",

"referenceLocator": "pkg:npm/express@4.18.2"

}

]

}

],

"relationships": [

{

"spdxElementId": "SPDXRef-DOCUMENT",

"relationshipType": "DESCRIBES",

"relatedSpdxElement": "SPDXRef-Package-express"

}

]

}

When to Use Which

Choose CycloneDX when your primary goal is vulnerability management, you need to integrate with security tools like Dependency-Track or Grype, you want machine-readable automation-friendly output, or you need VEX support.

Choose SPDX when compliance with ISO standards is required, legal teams need license audit documentation, you are working with government contracts that specify SPDX, or you need file-level license attribution.

In practice, generate both. Tools like Syft output either format with a flag change. Many organizations generate CycloneDX for security workflows and SPDX for compliance records.

---

Generation Tools

Syft (by Anchore)

Syft generates SBOMs from container images, filesystems, archives, and source directories. It is the most versatile SBOM generator available.

Install:

curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

Generate from a container image:

syft my-app:latest -o cyclonedx-json > sbom-cyclonedx.json

syft my-app:latest -o spdx-json > sbom-spdx.json

Generate from source directory:

syft dir:./src -o cyclonedx-json > sbom-source.json

Generate from a filesystem (after build):

syft dir:./dist -o cyclonedx-json > sbom-artifact.json

Sample output snippet (CycloneDX):

{

"bomFormat": "CycloneDX",

"specVersion": "1.5",

"components": [

{

"type": "library",

"name": "github.com/gin-gonic/gin",

"version": "v1.9.1",

"purl": "pkg:golang/github.com/gin-gonic/gin@v1.9.1"

}

]

}

Trivy — SBOM as Part of Scanning

Trivy generates SBOMs alongside vulnerability scanning, giving you both in one pass:

curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

Generate SBOM:

trivy image --format cyclonedx --output sbom.json my-app:latest

trivy image --format spdx-json --output sbom-spdx.json my-app:latest

trivy fs --format cyclonedx --output sbom-fs.json .

Scan an existing SBOM for vulnerabilities:

trivy sbom sbom.json

This is powerful — you can generate the SBOM once and scan it repeatedly as new vulnerabilities are disclosed without rebuilding the image.

Microsoft SBOM Tool

For .NET and Windows workloads, Microsoft's SBOM tool integrates with MSBuild:

dotnet tool install --global Microsoft.Sbom.DotNetTool

Generate:

sbom-tool generate \

-b ./bin/Release \

-bc ./src \

-pn my-dotnet-app \

-pv 1.0.0 \

-ps "My Organization" \

-nsb https://example.com/sbom

This produces SPDX 2.2 format by default, suitable for compliance documentation.

cdxgen — CycloneDX Generator

cdxgen supports over 30 languages and package managers:

npm install -g @cyclonedx/cdxgen

Generate for various ecosystems:

cdxgen -o sbom.json

cdxgen -t java -o sbom.json

cdxgen -t python -o sbom.json

cdxgen -t go -o sbom.json

cdxgen -o sbom.json --deep

The --deep flag performs recursive analysis of sub-projects and transitive dependencies.

---

Signing SBOMs

An unsigned SBOM is a claim. A signed SBOM is evidence. Signing proves the SBOM was generated by your CI system and has not been tampered with.

cosign (Sigstore) — Keyless Signing

cosign enables keyless signing using OIDC identity from your CI provider. No key management required — your GitHub Actions identity becomes the signing key.

Install:

go install github.com/sigstore/cosign/v2/cmd/cosign@latest

Or download binary:

curl -sSfL https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 -o /usr/local/bin/cosign

chmod +x /usr/local/bin/cosign

Sign an SBOM:

cosign sign-blob --yes sbom.json --bundle sbom.json.bundle

Verify the signature:

cosign verify-blob sbom.json \

--bundle sbom.json.bundle \

--certificate-identity "https://github.com/my-org/my-repo/.github/workflows/build.yml@refs/heads/main" \

--certificate-oidc-issuer "https://token.actions.githubusercontent.com"

Attestation with in-toto Format

in-toto attestations wrap your SBOM in a standardized envelope that includes metadata about who generated it and when:

cosign attest --predicate sbom.json \

--type cyclonedx \

registry.example.com/my-app:latest

cosign verify-attestation \

--type cyclonedx \

--certificate-identity-regexp ".github.com/my-org/." \

--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \

registry.example.com/my-app:latest

This attaches the SBOM directly to the container image in the registry. Anyone pulling the image can also pull and verify its SBOM.

Verifying SBOM Authenticity Before Consumption

Before trusting an SBOM from a third party, verify its signature:

cosign verify-attestation \

--type cyclonedx \

--certificate-identity-regexp ".github.com/vendor-org/." \

--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \

registry.vendor.com/their-app:latest | \

jq -r '.payload' | base64 -d > verified-sbom.json

trivy sbom verified-sbom.json

This creates a chain of trust: the vendor signed the SBOM with their CI identity, you verify it matches their known identity, then you scan it for vulnerabilities.

---

Monitoring with Dependency-Track

Generating SBOMs is half the story. You need continuous monitoring — when a new CVE is published, which of your projects are affected?

Dependency-Track (by OWASP) is the leading open source platform for SBOM-based vulnerability monitoring.

Deploy Dependency-Track

Using Docker Compose:

version: '3.8'

services:

dtrack-apiserver:

image: dependencytrack/apiserver:latest

ports:

- "8081:8080"

volumes:

- dtrack-data:/data

environment:

- ALPINE_DATABASE_MODE=external

- ALPINE_DATABASE_URL=jdbc:postgresql://postgres:5432/dtrack

- ALPINE_DATABASE_DRIVER=org.postgresql.Driver

- ALPINE_DATABASE_USERNAME=dtrack

- ALPINE_DATABASE_PASSWORD=dtrack

depends_on:

- postgres

restart: unless-stopped

dtrack-frontend:

image: dependencytrack/frontend:latest

ports:

- "8080:8080"

environment:

- API_BASE_URL=http://localhost:8081

depends_on:

- dtrack-apiserver

restart: unless-stopped

postgres:

image: postgres:16-alpine

environment:

- POSTGRES_DB=dtrack

- POSTGRES_USER=dtrack

- POSTGRES_PASSWORD=dtrack

volumes:

- postgres-data:/var/lib/postgresql/data

restart: unless-stopped

volumes:

dtrack-data:

postgres-data:

Start it:

docker compose up -d

Access the dashboard at http://localhost:8080 (default credentials: admin/admin).

Upload SBOMs from CI

Use the Dependency-Track API to upload SBOMs automatically after each build:

curl -X POST "https://dtrack.example.com/api/v1/bom" \

-H "X-Api-Key: $DTRACK_API_KEY" \

-H "Content-Type: multipart/form-data" \

-F "projectName=my-app" \

-F "projectVersion=2.1.0" \

-F "autoCreate=true" \

-F "bom=@sbom.json"

GitHub Actions step:

- name: Upload SBOM to Dependency-Track

run: |

curl -X POST "${{ secrets.DTRACK_URL }}/api/v1/bom" \

-H "X-Api-Key: ${{ secrets.DTRACK_API_KEY }}" \

-H "Content-Type: multipart/form-data" \

-F "projectName=${{ github.event.repository.name }}" \

-F "projectVersion=${{ github.sha }}" \

-F "autoCreate=true" \

-F "bom=@sbom.json"

Dashboard Capabilities

Once SBOMs are flowing in, Dependency-Track provides:

  • Portfolio view showing all projects with vulnerability counts
  • Component inventory listing every library version across all projects
  • Vulnerability timeline tracking new CVEs affecting your components over time
  • License risk identifying components with copyleft or unknown licenses
  • Outdated component detection showing packages behind current versions

Policy Violations and Alerting

Configure policies to enforce organizational rules:

  • No Critical Vulnerabilities: Condition is vulnerability severity equals CRITICAL, violation state is FAIL
  • No GPL in Production: Condition is license family equals GPL, violation state is WARN
  • Component Age: Condition is component outdated more than 365 days, violation state is INFO

Set up notifications to Slack, Microsoft Teams, email, or webhooks for real-time alerting when new vulnerabilities affect your portfolio.

---

Compliance Context

US Executive Order 14028

Issued May 2021, EO 14028 ("Improving the Nation's Cybersecurity") requires SBOM for software sold to the US federal government. Key requirements:

  • Vendors must provide SBOMs for software delivered to federal agencies
  • SBOMs must be machine-readable
  • Must include direct and transitive dependencies
  • Must be updated with each new release
  • Format: SPDX or CycloneDX accepted

If you sell software to the US government or work with contractors who do, SBOM generation is not optional.

EU Cyber Resilience Act (CRA)

The EU CRA (expected enforcement 2027) requires:

  • Products with digital elements must have documented SBOMs
  • Vulnerability handling process must be in place
  • Known vulnerabilities must be disclosed
  • Security updates must be provided for the product's lifetime
  • Applies to both commercial and open source software (with exceptions for non-commercial open source)

NTIA Minimum Elements for SBOM

The National Telecommunications and Information Administration defined minimum SBOM elements:

Required fields per component:

  • Supplier name
  • Component name
  • Component version
  • Unique identifier (purl recommended)
  • Dependency relationship
  • Author of SBOM data
  • Timestamp of SBOM creation

Required practices:

  • Automation support (machine-readable format)
  • Known unknowns (declare what you could not identify)
  • Frequency (new SBOM per release at minimum)
  • Access control (determine distribution method)

---

Complete CI/CD Pipeline: Generate, Sign, Attach, Monitor

Here is a production pipeline implementing the full SBOM lifecycle:

name: SBOM Pipeline

on:

push:

branches: [main]

tags: ['v*']

permissions:

contents: read

packages: write

id-token: write

jobs:

build-and-sbom:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- name: Build Application

run: |

npm ci

npm run build

- name: Build Container Image

run: |

docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .

- name: Install Syft

run: |

curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

- name: Generate SBOM (CycloneDX)

run: |

syft ghcr.io/${{ github.repository }}:${{ github.sha }} \

-o cyclonedx-json > sbom-cyclonedx.json

- name: Generate SBOM (SPDX)

run: |

syft ghcr.io/${{ github.repository }}:${{ github.sha }} \

-o spdx-json > sbom-spdx.json

- name: Install cosign

uses: sigstore/cosign-installer@v3

- name: Login to Container Registry

uses: docker/login-action@v3

with:

registry: ghcr.io

username: ${{ github.actor }}

password: ${{ secrets.GITHUB_TOKEN }}

- name: Push Container Image

run: |

docker push ghcr.io/${{ github.repository }}:${{ github.sha }}

- name: Sign Container Image

run: |

cosign sign --yes ghcr.io/${{ github.repository }}:${{ github.sha }}

- name: Attach SBOM to Image

run: |

cosign attest --yes \

--predicate sbom-cyclonedx.json \

--type cyclonedx \

ghcr.io/${{ github.repository }}:${{ github.sha }}

- name: Upload SBOM to Dependency-Track

run: |

curl -X POST "${{ secrets.DTRACK_URL }}/api/v1/bom" \

-H "X-Api-Key: ${{ secrets.DTRACK_API_KEY }}" \

-H "Content-Type: multipart/form-data" \

-F "projectName=${{ github.event.repository.name }}" \

-F "projectVersion=${{ github.sha }}" \

-F "autoCreate=true" \

-F "bom=@sbom-cyclonedx.json"

- name: Upload SBOMs as Artifacts

uses: actions/upload-artifact@v4

with:

name: sbom-documents

path: |

sbom-cyclonedx.json

sbom-spdx.json

Verification by Consumers

Anyone consuming your container image can verify it:

cosign verify \

--certificate-identity-regexp ".github.com/your-org/." \

--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \

ghcr.io/your-org/your-app:latest

cosign verify-attestation \

--type cyclonedx \

--certificate-identity-regexp ".github.com/your-org/." \

--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \

ghcr.io/your-org/your-app:latest | jq -r '.payload' | base64 -d > verified-sbom.json

trivy sbom verified-sbom.json

---

Ongoing Monitoring Best Practices

SBOMs are not point-in-time documents. New vulnerabilities are disclosed daily.

SBOM Freshness Check

A stale SBOM gives false confidence. Enforce freshness:

sbom_date=$(jq -r '.metadata.timestamp' sbom.json)

age_seconds=$(( $(date +%s) - $(date -d "$sbom_date" +%s) ))

age_days=$(( age_seconds / 86400 ))

if [ "$age_days" -gt 7 ]; then

echo "SBOM is $age_days days old. Regenerate required."

exit 1

fi

Diffing SBOMs Between Releases

Track what changed between versions:

diff <(jq -r '.components[].purl' sbom-v1.json | sort) \

<(jq -r '.components[].purl' sbom-v2.json | sort)

This shows added, removed, and upgraded dependencies — useful for change review and audit trails.

Monitoring Lifecycle

  • Generate SBOM on every build
  • Store SBOMs in Dependency-Track
  • Monitor — Dependency-Track checks NVD and OSV for new CVEs matching your components
  • Alert — Notify your team when new vulnerabilities affect deployed software
  • Remediate — Update the component and generate a new SBOM
  • Verify — Confirm the new build no longer contains the vulnerability
  • ---

    Frequently Asked Questions

    What is an SBOM and why is it important?

    An SBOM (Software Bill of Materials) is a formal inventory of all components, libraries, and dependencies in your software, including their versions and licenses. It's important for security vulnerability tracking, license compliance, and supply chain transparency. When a new CVE is published, an SBOM lets you instantly know if you're affected without scanning every artifact.

    How do I generate an SBOM?

    Use tools like Syft (for container images and filesystems), CycloneDX plugins (for build tools like Maven, npm, pip), or SPDX tools for standard compliance. Integrate SBOM generation into your CI/CD pipeline after the build step. Generate in both CycloneDX (JSON) and SPDX formats for maximum compatibility with downstream tools and compliance requirements.

    What is the difference between CycloneDX and SPDX formats?

    CycloneDX is designed for application security with focus on vulnerability identification, dependency graphs, and DevSecOps integration. SPDX (Software Package Data Exchange) originated from license compliance with richer licensing metadata and is an ISO standard. CycloneDX is more common in DevSecOps pipelines while SPDX is preferred for legal/compliance use cases. Many tools support both.

    How do I monitor SBOMs for new vulnerabilities?

    Ingest SBOMs into a vulnerability management platform like Dependency-Track (open-source) that continuously correlates your component inventory against vulnerability databases (NVD, OSV). Set up alerts for new critical CVEs affecting your components. Automate ticket creation when vulnerabilities are discovered, and track remediation timelines by severity.

    What is software supply chain security?

    Software supply chain security protects the integrity of your software from source code through build to deployment. It includes verifying dependency sources, signing build artifacts, using SBOMs to track components, scanning for vulnerabilities, and ensuring build reproducibility. Frameworks like SLSA define maturity levels for supply chain security practices.

    ---