Skip to content
Supply Chain Security

Generating a CycloneDX SBOM in Your CI/CD Pipeline

Automate SBOM generation at build time with CycloneDX, add AI-attribution metadata, and sign with Sigstore — practical CI/CD integration.

Intermediate 13 min read Updated May 2026

An SBOM (Software Bill of Materials) that lives in your docs folder and is manually updated before every release is not an SBOM — it's a liability. A real SBOM is generated automatically at build time, embedded in your artifact's metadata, and updated continuously.

This article covers the tactical implementation: integrating CycloneDX SBOM generation into GitHub Actions, GitLab CI, or your CI/CD platform, tagging AI-generated components, signing with Sigstore, and storing alongside your artifacts.

Why CycloneDX and Not SPDX?

Both are valid standards. Choose based on your use case:

Standard Best For Notes
CycloneDX Security-focused SBOMs Vulnerability tracking, VEX, cryptographic signatures
SPDX License compliance focus Richer license metadata, open-source audits

For regulatory compliance (EU CRA, FedRAMP, SOC 2): Use CycloneDX.

Why: CycloneDX is designed for security practitioners. It handles vulnerability metadata, VEX (Vulnerability Exploitability Exchange), AI-attribution tags, and cryptographic signatures natively. SPDX is stronger on licensing but weaker on security operations.

Most enterprises generate both: CycloneDX for internal security operations, SPDX for open-source license tracking.

CycloneDX Core Structures Reference

A CycloneDX SBOM is a JSON document with metadata, component list, and dependency graph:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.7",
  "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
  "version": 1,
  "metadata": {
    "timestamp": "2026-04-29T14:00:00Z",
    "lifecycles": [{"phase": "build"}],
    "tools": {
      "components": [{
        "type": "application",
        "bom-ref": "tool-cdxgen-10.2.0",
        "name": "cdxgen",
        "version": "10.2.0"
      }]
    },
    "component": {
      "type": "application",
      "name": "api-gateway",
      "version": "2.14.1",
      "supplier": {
        "name": "Crash Override Labs",
        "url": ["https://crashoverride.com"]
      }
    }
  },
  "components": [
    {
      "type": "library",
      "bom-ref": "pkg:npm/[email protected]",
      "name": "express",
      "version": "4.18.2",
      "purl": "pkg:npm/[email protected]",
      "licenses": [{"license": {"name": "MIT"}}]
    }
  ],
  "dependencies": [
    {
      "ref": "pkg:npm/[email protected]",
      "dependsOn": ["pkg:npm/[email protected]"]
    }
  ]
}

Key fields:

Field Purpose
bomFormat Always "CycloneDX"
specVersion Version of spec (1.7 is current — released 2025-10-21, standardized as ECMA-424 on 2025-12-10; 1.6 still validates)
serialNumber UUID for this SBOM (unique per build)
version SBOM document version (increment if you patch the SBOM)
metadata.timestamp When SBOM was generated
metadata.component The artifact being documented
components List of all dependencies
dependencies Directed graph: who depends on whom

Step 1: Generate SBOM from Build Artifact

You have three options:

Option 1: Source Analysis (Fast, Incomplete)

Use cyclonedx-py, cyclonedx-npm, or cyclonedx-gomod to analyze lockfiles:

# Node.js
npm install --save-dev @cyclonedx/cyclonedx-npm
npx cyclonedx-npm --output-file sbom.cdx.json

# Go
cyclonedx-gomod mod -output sbom.cdx.json

# Python
cyclonedx-py requirements requirements.txt -o sbom.cdx.json

Pros: Fast, works in CI easily

Cons: Misses vendored dependencies, embedded assets, build-time injections, transitive chains

Verdict: Use for quick testing, but not for production releases.


Option 2: Binary/Artifact Inspection (Accurate, Slower)

Use cdxgen or syft to analyze actual build artifacts (containers, binaries, bundles):

# For container images
cdxgen -t docker docker.io/myorg/api-gateway:v2.14.1 -o sbom.cdx.json

# For filesystem/artifacts
syft /path/to/artifact -o cyclonedx-json > sbom.cdx.json

# For multi-ecosystem projects
cdxgen --recursive /path/to/project -o sbom.cdx.json

Pros: Finds everything (vendored, embedded, transitive)

Cons: Slower, requires artifact availability

Verdict: Use for production SBOMs. This is accurate.


Option 3: Hybrid (Recommended)

Combine source analysis (fast, for early validation) with artifact inspection (accurate, for release):

# In CI: Quick sanity check
npx cyclonedx-npm --output-file sbom-lint.cdx.json
node tools/validate-sbom.js sbom-lint.cdx.json || exit 1

# Before release: Accurate SBOM from artifact
docker build -t api-gateway:v2.14.1 .
cdxgen -t docker docker.io/myorg/api-gateway:v2.14.1 -o sbom-release.cdx.json

Step 2: CI/CD Integration (GitHub Actions Example)

Here's a production-ready GitHub Actions workflow:

name: Build and Generate SBOM

on:
  push:
    branches: [main]
    tags: ["v*"]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: myorg/api-gateway

jobs:
  build-and-sbom:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      attestations: write
      id-token: write
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      # Build and push container image
      - name: Build and push
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
      
      # Install SBOM tools
      - name: Install cdxgen and Sigstore
        run: |
          npm install -g @cyclonedx/cdxgen
          curl -sSLO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
          chmod +x cosign-linux-amd64
          sudo mv cosign-linux-amd64 /usr/local/bin/cosign
      
      # Generate SBOM from container image
      - name: Generate SBOM
        run: |
          cdxgen \
            -t docker \
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
            -o sbom.cdx.json
      
      # Add AI-attribution metadata (if applicable)
      - name: Add AI metadata to SBOM
        if: contains(github.event.head_commit.message, '[ai-generated]')
        run: |
          node tools/add-ai-metadata.js \
            sbom.cdx.json \
            --agent "copilot/coding-agent" \
            --model "gpt-4-code" \
            --commit "${{ github.sha }}"
      
      # Validate SBOM
      - name: Validate SBOM
        run: |
          npm install -g @cyclonedx/cli
          cyclonedx-cli validate \
            --input-file sbom.cdx.json \
            --fail-on-errors
      
      # Sign SBOM with Sigstore
      - name: Sign SBOM attestation
        if: github.event_name != 'pull_request'
        run: |
          cosign attest \
            --predicate sbom.cdx.json \
            --type cyclonedx \
            --yes \
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
      
      # Store SBOM in artifact
      - name: Upload SBOM artifact
        uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.cdx.json
      
      # Publish SBOM to well-known location (for compliance scanning)
      - name: Publish SBOM
        if: github.event_name != 'pull_request'
        run: |
          # Copy SBOM to .well-known/sbom/ for public access
          mkdir -p $GITHUB_WORKSPACE/.well-known/sbom
          cp sbom.cdx.json $GITHUB_WORKSPACE/.well-known/sbom/api-gateway-${{ github.sha }}.cdx.json
          cp sbom.cdx.json $GITHUB_WORKSPACE/.well-known/sbom/api-gateway-latest.cdx.json

Key points:

  1. Generate SBOM from artifact (cdxgen -t docker) — Use the actual container image
  2. Validate SBOM (cyclonedx-cli validate) — Catch malformed SBOMs before release
  3. Add AI metadata — Tag components generated by agents
  4. Sign with Cosign (cosign attest) — Cryptographic proof of origin
  5. Store with artifact — SBOM travels with the container image

Step 3: AI-Attribution Metadata

When an AI agent generates code or patches, add metadata to the SBOM:

{
  "type": "library",
  "name": "auth-service",
  "version": "2.14.1",
  "purl": "pkg:npm/[email protected]",
  "x-ai-generated": {
    "agent": "copilot/coding-agent",
    "model": "gpt-4-turbo",
    "model_version": "2026-q1-v4",
    "prompt": "Generate login handler with OAuth2 support",
    "generated_at": "2026-04-29T10:00:00Z",
    "reviewed_by": "[email protected]",
    "reviewed_at": "2026-04-29T11:30:00Z"
  }
}

This lets auditors and compliance tools understand:

  • Which code came from an agent
  • Which agent and version
  • Who reviewed it
  • When it was reviewed

Step 4: Signing and Publishing

Sigstore (Recommended)

Keyless cryptographic signing using OIDC:

# Sign SBOM with Sigstore (keyless)
cosign attest \
  --predicate sbom.cdx.json \
  --type cyclonedx \
  --yes \
  docker.io/myorg/api-gateway:v2.14.1

# Verify signature — Sigstore public-good infra requires identity gating
# (tighten the regexp to your repo/org slug in production)
cosign verify-attestation \
  --type cyclonedx \
  --certificate-identity-regexp '.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  docker.io/myorg/api-gateway:v2.14.1

Benefit: No key management. GitHub Actions uses GitHub's OIDC token as proof of identity.

Traditional PKI

If you have your own signing keys:

# Sign with your RSA key (modern bundle pattern — single .sigstore.json
# file carries signature, certificate, and Rekor inclusion proof)
cosign sign-blob \
  --key ~/.cosign/cosign.key \
  --bundle sbom.cdx.json.sigstore.json \
  sbom.cdx.json

# Verify with public key
cosign verify-blob \
  --key cosign.pub \
  --bundle sbom.cdx.json.sigstore.json \
  sbom.cdx.json

Publishing SBOMs

Store alongside your artifacts:

https://yourcompany.com/.well-known/sbom/
  ├── api-gateway-v2.14.1.cdx.json
  ├── api-gateway-v2.14.1.cdx.json.sig
  ├── api-gateway-v2.14.0.cdx.json
  └── api-gateway-v2.14.0.cdx.json.sig

This allows:

  • Regulators to audit your SBOMs (EU CRA, FedRAMP)
  • Customers to scan your dependencies (Snyk, Dependabot)
  • Dependency tracking tools to correlate your code with vulnerabilities

Step 5: Vulnerability Scanning Integration

Once SBOM is generated, scan for known vulnerabilities:

# Using Grype
grype sbom:sbom.cdx.json

# Using Trivy
trivy sbom sbom.cdx.json

# Using Dependency-Track (enterprise)
curl -X POST https://dtrack.company.com/api/v1/bom \
  -H "X-API-Key: $DTRACK_API_KEY" \
  -F "projectName=api-gateway" \
  -F "projectVersion=2.14.1" \
  -F "[email protected]"

Output includes:

Component Version CVE Severity Fixed In
lodash 4.17.20 CVE-2021-23337 High 4.17.21
express 4.18.0 CVE-2024-9999 Critical 4.18.2

GitLab CI Example

If you use GitLab:

stages:
  - build
  - sbom
  - sign
  - publish

variables:
  REGISTRY: registry.gitlab.com
  IMAGE_NAME: myorg/api-gateway

build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker build -t $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA .
    - docker push $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA

generate-sbom:
  stage: sbom
  image: node:18
  script:
    - npm install -g @cyclonedx/cdxgen
    - cdxgen -t docker $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA -o sbom.cdx.json
    - cyclonedx-cli validate --input-file sbom.cdx.json --fail-on-errors
  artifacts:
    paths:
      - sbom.cdx.json

sign-sbom:
  stage: sign
  image: gcr.io/projectsigstore/cosign:latest
  script:
    - cosign attest --yes --predicate sbom.cdx.json --type cyclonedx $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA
  only:
    - tags

publish-sbom:
  stage: publish
  image: alpine
  script:
    - mkdir -p /tmp/sbom
    - cp sbom.cdx.json /tmp/sbom/api-gateway-$CI_COMMIT_TAG.cdx.json
    - # Copy to web server or S3
  artifacts:
    paths:
      - /tmp/sbom/

Common SBOM Commands Reference

# Validate SBOM schema
cyclonedx-cli validate --input-file sbom.cdx.json

# Convert JSON to XML
cyclonedx-cli convert --input-file sbom.cdx.json --output-file sbom.cdx.xml

# Merge two SBOMs
cyclonedx-cli merge --input-files sbom1.cdx.json sbom2.cdx.json --output-file merged.cdx.json

# Query components with jq
jq '.components[] | {name, version}' sbom.cdx.json

# Find all transitive dependencies of a package
jq '.dependencies[] | select(.ref == "pkg:npm/[email protected]") | .dependsOn[]' sbom.cdx.json

# Generate VEX (Vulnerability Exploitability Exchange) with vexctl (OpenVEX)
vexctl create --product pkg:oci/[email protected] --vuln CVE-2024-9999 --status not_affected --justification code_not_present --file vex.openvex.json

Checklist: Production SBOM Pipeline

  • SBOM generated from artifact (not just lockfile)
  • SBOM validation in CI/CD pipeline (fails on invalid SBOM)
  • SBOM signed with Sigstore or PKI
  • SBOM published to .well-known/sbom/ before release
  • AI-attribution metadata added for agent-generated components
  • SCA/vulnerability scanning runs on SBOM
  • SBOM retention policy (minimum 3 years for compliance)
  • Metrics tracked: vulnerability count per component, remediation time

References

This article is part of the Supply Chain Security knowledge series (5 articles) Browse all Supply Chain Security articles →
Related Use Case

Software Compliance — Your last compliance vendor

Don't fake the evidence. Trust it.

Explore Use Case →