Generate an SBOM for CRA: Tools, Formats, CI/CD

A hands-on guide to generating Software Bills of Materials for CRA compliance. Covers open-source tools, format selection, and automated pipeline integration.

CRA Evidence Team Published February 5, 2026 Updated July 12, 2026
A built artifact scanned into an SBOM document listing components, then carried into continuous monitoring
In this article

The CRA requires a Software Bill of Materials. Every competitor article tells you this. None show you how to generate one.

This guide covers open-source tools, format selection, and CI/CD integration with no vendor lock-in required.

Summary

  • CRA requires machine-readable SBOMs covering "at least top-level dependencies"
  • Recommended formats: CycloneDX 1.4+ or SPDX 2.3+ (per BSI TR-03183)
  • Open-source tools: Syft (images/filesystems), Trivy (containers), cdxgen (source code)
  • Integrate SBOM generation into CI/CD for automatic updates
  • Quality matters: minimum fields include package name, version, supplier, hash, license

CI/CD SBOM Pipeline

Automate all six stages so every release produces a fresh, signed SBOM.

1
Code

Commit triggers the pipeline on merge to main or on a version tag push.

InputSource code + lock files

2
Build

Container image or binary assembled from locked dependencies.

InputBuilt artifact

3
Generate

Syft, Trivy, or cdxgen scans the artifact and produces a CycloneDX or SPDX file.

Outputsbom.cdx.json

4
Sign

Cosign signs the SBOM file and produces a sigstore bundle with the signature and certificate.

Outputsbom.cdx.json.sigstore.json

5
Store

SBOM archived in CI artifacts or uploaded to CRA Evidence alongside the release tag.

OutputRetained 10 years

6
Monitor

Stored SBOM rescanned against new CVEs; alerts fire when new vulnerabilities match components.

OngoingVulnerability alerts

What the CRA Actually Requires

Start with what the regulation says. Annex I, Part II of the CRA requires manufacturers to:

"identify and document vulnerabilities and components contained in products with digital elements, including by drawing up a software bill of materials in a commonly used and machine-readable format covering at the very least the top-level dependencies of the products"

Key points:

  • Machine-readable format: Not a PDF, not a spreadsheet, but structured data
  • At least top-level dependencies: The minimum scope, though more is better
  • Not required to be public: Provided to authorities on request
  • Must be updated: With each release, patch, or component change

The CRA doesn't mandate a specific format, but standardization efforts point clearly to CycloneDX and SPDX.

The SBOM is part of the CRA technical documentation under Annex VII. Manufacturers must draw up that documentation before placing a product on the market and keep it available to market surveillance authorities for at least 10 years, or for the support period, whichever is longer (Article 13(13)). An SBOM stored locally with no link to a specific product release does not satisfy this. It must be retrievable, versioned, and tied to the product version it describes.

Format Selection: CycloneDX vs SPDX

Two formats dominate the SBOM landscape. Both are acceptable for CRA compliance.

CycloneDX

Origin: OWASP project, security-focused Current version: 1.6 (1.4+ recommended for CRA) Best for: Security and vulnerability management

Strengths:

JSON example:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.5",
  "version": 1,
  "components": [
    {
      "type": "library",
      "name": "lodash",
      "version": "4.17.21",
      "purl": "pkg:npm/lodash@4.17.21",
      "hashes": [
        {
          "alg": "SHA-256",
          "content": "cc6d..."
        }
      ],
      "licenses": [
        {
          "license": {
            "id": "MIT"
          }
        }
      ]
    }
  ]
}

SPDX

Origin: Linux Foundation, license compliance-focused Current version: 2.3; ISO/IEC 5962:2021 standardised SPDX 2.2.1 Best for: License compliance and legal review

Strengths:

  • ISO international standard
  • Detailed license expression syntax
  • Strong in open source compliance contexts
  • Longer track record
  • Better for complex licensing scenarios

JSON example:

{
  "spdxVersion": "SPDX-2.3",
  "dataLicense": "CC0-1.0",
  "SPDXID": "SPDXRef-DOCUMENT",
  "name": "my-application",
  "packages": [
    {
      "SPDXID": "SPDXRef-Package-lodash",
      "name": "lodash",
      "versionInfo": "4.17.21",
      "downloadLocation": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
      "licenseConcluded": "MIT",
      "checksums": [
        {
          "algorithm": "SHA256",
          "checksumValue": "cc6d..."
        }
      ]
    }
  ]
}

Which to Choose?

Use Case Recommendation
Primary focus is security/vulnerabilities CycloneDX
Primary focus is license compliance SPDX
Need VEX integration CycloneDX
Enterprise with existing SPDX tooling SPDX
German market (BSI TR-03183) Either (both recommended)
Starting fresh, no preference CycloneDX (simpler, security-focused)

For CRA compliance, either format works. Pick one and be consistent.

Open-Source SBOM Generation Tools

No vendor lock-in required. These tools are free, open-source, and production-ready.

Syft (Anchore)

Best for: Container images, filesystems, archives License: Apache 2.0 Output formats: CycloneDX, SPDX, Syft JSON

Installation:

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

# Homebrew
brew install syft

# Docker
docker pull anchore/syft

Usage examples:

# Scan a container image
syft alpine:latest -o cyclonedx-json > sbom.cdx.json

# Scan a directory
syft dir:/path/to/project -o cyclonedx-json > sbom.cdx.json

# Scan an archive
syft /path/to/archive.tar.gz -o spdx-json > sbom.spdx.json

# Scan with specific catalogers (e.g., only Python)
syft dir:. -o cyclonedx-json --select-catalogers python

Supported ecosystems: Python, Node.js, Ruby, Java, Go, Rust, PHP, .NET, and more.

Trivy (Aqua Security)

Best for: Container images with built-in vulnerability context License: Apache 2.0 Output formats: CycloneDX, SPDX, plus vulnerability reports

Installation:

# Linux (Debian/Ubuntu)
sudo apt-get install trivy

# macOS
brew install trivy

# Docker
docker pull aquasec/trivy

Usage examples:

# Generate SBOM from container image
trivy image --format cyclonedx --output sbom.cdx.json alpine:latest

# Generate SBOM from filesystem
trivy fs --format cyclonedx --output sbom.cdx.json /path/to/project

# Generate SBOM with vulnerability info
trivy image --format cyclonedx --output sbom.cdx.json \
  --scanners vuln nginx:latest

Advantage: Trivy can generate SBOMs and scan for vulnerabilities in one pass.

cdxgen (CycloneDX)

Best for: Source code analysis across many languages License: Apache 2.0 Output format: CycloneDX

Installation:

# npm (requires Node.js)
npm install -g @cyclonedx/cdxgen

# Docker
docker pull ghcr.io/cyclonedx/cdxgen

Usage examples:

# Scan current directory
cdxgen -o sbom.json

# Scan specific project type
cdxgen -t python -o sbom.json

# Scan with deep dependency resolution
cdxgen --deep -o sbom.json

# Scan a specific directory
cdxgen -o sbom.json /path/to/project

Supported languages: JavaScript, Python, Java, Go, Rust, PHP, Ruby, .NET, C/C++, and more.

Tool Comparison

Feature Syft Trivy cdxgen
Container images Excellent Excellent Good
Source code Good Good Excellent
Filesystem scan Excellent Good Good
Vulnerability scanning No (use Grype) Yes No
CycloneDX output Yes Yes Yes
SPDX output Yes Yes No
Speed Fast Medium Medium
Language coverage Very broad Broad Very broad

Recommendation: Start with Syft for most use cases. Add Trivy if you need integrated vulnerability scanning. Use cdxgen for complex source code projects.

Firmware and Embedded Devices

The tools above work well for containers and package managers. For firmware and embedded devices (the primary CRA target), the approach is different.

Firmware images are binary blobs, not package registries. The standard first step is extracting the filesystem:

# Extract filesystem from firmware image
binwalk -Me firmware.bin

# Run Syft on the extracted filesystem
syft dir:_firmware.bin.extracted/squashfs-root -o cyclonedx-json > sbom.cdx.json

Use binwalk -Me (recursive extraction) rather than -e for nested or compressed images. Coverage will be lower than for container images. Stripped binaries lose symbol information, and Syft may not identify all components.

For binary-level analysis of compiled components, BLint, cve-bin-tool, or EMBA provide deeper inspection. Proprietary SDKs and closed-source libraries embedded in firmware typically require manual SBOM entries. Document the vendor, version, and source URL.

CI/CD Integration

Manual SBOM generation doesn't scale. Integrate it into your build pipeline.

GitHub Actions

name: SBOM Generation

on:
  push:
    branches: [main]
  release:
    types: [published]

jobs:
  sbom:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write   # required for Cosign keyless signing
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Build container image
        run: |
          docker build -t myapp:${{ 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 from built image
        run: |
          syft myapp:${{ github.sha }} -o cyclonedx-json > sbom.cdx.json

      - name: Install Cosign
        uses: sigstore/cosign-installer@v3

      - name: Sign SBOM (keyless)
        run: |
          cosign sign-blob sbom.cdx.json --bundle sbom.cdx.json.sigstore.json --yes

      - name: Upload SBOM and signature as artifacts
        uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: |
            sbom.cdx.json
            sbom.cdx.json.sigstore.json
          retention-days: 90

      # Optional: Upload to CRA Evidence for long-term compliance storage
      - name: Upload to CRA Evidence
        if: github.event_name == 'release'
        env:
          CRA_EVIDENCE_TOKEN: ${{ secrets.CRA_EVIDENCE_TOKEN }}
        run: |
          curl -X POST https://api.craevidence.com/api/v1/ci/sbom \
            -H "Authorization: Bearer $CRA_EVIDENCE_TOKEN" \
            -F "file=@sbom.cdx.json" \
            -F "product_id=${{ vars.PRODUCT_ID }}" \
            -F "version=${{ github.ref_name }}"

Note: The CI artifact (retention-days: 90) is for developer convenience. For CRA-compliant 10-year retention, upload to a dedicated long-term store on every release. See the Retention section below.

GitLab CI

generate-sbom:
  stage: build
  image: anchore/syft:latest
  script:
    - syft dir:. -o cyclonedx-json > sbom.cdx.json
  artifacts:
    paths:
      - sbom.cdx.json
    expire_in: 90 days
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
    - if: $CI_COMMIT_TAG

Jenkins

pipeline {
    agent any

    stages {
        stage('Generate SBOM') {
            steps {
                sh '''
                    curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b .
                    ./syft dir:. -o cyclonedx-json > sbom.cdx.json
                '''
            }
        }

        stage('Archive SBOM') {
            steps {
                archiveArtifacts artifacts: 'sbom.cdx.json', fingerprint: true
            }
        }
    }
}

Docker Build Integration

Generate SBOM during Docker build:

# Multi-stage build with SBOM generation
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Generate SBOM in build stage
FROM anchore/syft:latest AS sbom
COPY --from=builder /app /app
RUN syft dir:/app -o cyclonedx-json > /sbom.cdx.json

# Final image
FROM node:20-slim
COPY --from=builder /app/dist /app
COPY --from=sbom /sbom.cdx.json /app/sbom.cdx.json
CMD ["node", "/app/index.js"]

SBOM Signing

The CRA does not mandate SBOM signing, but signing establishes that the SBOM has not been modified since it was generated. Enterprise procurement teams and notified bodies increasingly expect a verifiable signature alongside the SBOM file.

Cosign (part of the Sigstore project) is the standard tool:

# Install Cosign
brew install cosign

# Sign an SBOM file. Produces a bundle with the signature and certificate
cosign sign-blob sbom.cdx.json --bundle sbom.cdx.json.sigstore.json

# Verify later
cosign verify-blob sbom.cdx.json --bundle sbom.cdx.json.sigstore.json \
  --certificate-identity <your-identity> \
  --certificate-oidc-issuer <your-issuer>

Store sbom.cdx.json and sbom.cdx.json.sigstore.json together. The bundle contains the signature, the signing certificate, and the Rekor transparency log entry. Anyone with the bundle can verify the SBOM without contacting you.

For CI/CD, Cosign supports keyless signing using the pipeline's OIDC identity (GitHub Actions, GitLab CI). No keys to manage. The GitHub Actions example above already includes this: the job needs id-token: write permission, and sign-blob takes --yes to run without an interactive prompt. Cosign 2.0 and later make keyless signing the default, so the old COSIGN_EXPERIMENTAL flag is no longer needed.

Continuous Monitoring

A signed, stored SBOM is not the finish line. New vulnerabilities are disclosed against components that were clean at release time. The stored SBOM has to be rescanned on a schedule so those CVEs surface.

Grype reads an existing SBOM directly, so the rescan does not need the original build environment:

name: SBOM Rescan

on:
  schedule:
    - cron: "0 6 * * 1"  # Weekly, Monday 06:00 UTC

jobs:
  rescan:
    runs-on: ubuntu-latest
    steps:
      - name: Fetch the stored SBOM
        env:
          SBOM_URL: ${{ vars.SBOM_URL }}
        run: |
          curl -sSfL -o sbom.cdx.json "$SBOM_URL"

      - name: Install Grype
        run: |
          curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

      - name: Scan the SBOM for new CVEs
        run: |
          grype sbom:./sbom.cdx.json

CRA Evidence runs this rescan automatically for every uploaded SBOM and alerts you when a new CVE matches a component, which covers the same need without a scheduled job.

SBOM Quality: Meeting TR-03183

The German BSI Technical Guideline TR-03183 extends the NTIA minimum elements. While not legally required across the EU, following TR-03183 ensures high-quality SBOMs.

Required Fields

Field Required By Notes
Component name NTIA + TR-03183 Package identifier
Version NTIA + TR-03183 Exact version string
Supplier NTIA + TR-03183 Vendor or maintainer
Unique identifier NTIA + TR-03183 PURL recommended
Dependency relationship NTIA + TR-03183 Direct vs transitive
Author of SBOM NTIA + TR-03183 Who created the SBOM
Timestamp NTIA + TR-03183 When SBOM was created
Hash values TR-03183 SHA-256 minimum
License TR-03183 SPDX license ID
Source repository TR-03183 VCS URL if available

Validating SBOM Quality

Use these tools to check your SBOM:

# Validate CycloneDX format
npm install -g @cyclonedx/cyclonedx-cli
cyclonedx validate --input-file sbom.cdx.json

# Check for minimum fields with jq
jq '.components[] | select(.version == null or .purl == null)' sbom.cdx.json

# Count components with hashes
jq '[.components[] | select(.hashes != null)] | length' sbom.cdx.json

Improving SBOM Quality

If your SBOM is missing data:

  1. Use lock files: package-lock.json, Pipfile.lock, go.sum contain more metadata
  2. Scan built artifacts: More complete than source-only scans
  3. Combine tools: Different tools find different components
  4. Add manual entries: For commercial or internal components

Keeping SBOMs Current

An SBOM is a snapshot. It must be updated to remain useful.

When to Regenerate

  • Every release (major, minor, patch)
  • After dependency updates
  • After security patches
  • When build configuration changes

Versioning Strategy

product-v1.0.0-sbom.cdx.json
product-v1.0.1-sbom.cdx.json
product-v1.1.0-sbom.cdx.json

Or use timestamps:

product-sbom-2026-01-15T10-30-00Z.cdx.json

Retention

10-year retention is a legal obligation

Article 13(13) requires manufacturers to keep the technical documentation at the disposal of market surveillance authorities for at least 10 years after the product is placed on the market, or for the support period, whichever is longer. The SBOM is part of that documentation, so the same clock applies to it.

CI artifact retention (90 days in the examples above) is for developer convenience, not compliance. The 10-year requirement needs a dedicated long-term store. CRA Evidence is purpose-built for it: versioned SBOM storage with vulnerability rescanning and technical file export, tied to each product release.

If you prefer to host it yourself, any major object store works once you enable the right controls:

Object store Controls to enable
Amazon S3 Versioning, lifecycle policies, Object Lock, access controls
Azure Blob Storage Immutability policies, access tier management
Google Cloud Storage Retention policies, object versioning

A lifecycle policy alone is not enough. The store must also support versioning, access controls, and ideally immutability to satisfy audit requirements.

Common Pitfalls

One-time SBOM generation

Problem: Creating an SBOM once and never updating it.

Solution: Automate SBOM generation in CI/CD. Every build should produce a fresh SBOM.

Missing transitive dependencies

Problem: SBOM only lists direct dependencies, missing nested packages.

Solution: Use lock files, scan built artifacts, enable deep scanning options.

Wrong format version

Problem: Using CycloneDX 1.3 when TR-03183 recommends 1.4+.

Solution: Check your tool's output version. Update tools regularly.

No hash values

Problem: Components without cryptographic hashes can't be verified.

Solution: Ensure your tool includes hashes. Syft and Trivy add SHA-256 hashes when they can read the component files.

Manual creation

Problem: Hand-crafting SBOMs is error-prone and unsustainable.

Solution: Always automate. Manual entries only for components tools can't detect.

Ignoring internal components

Problem: Only documenting open-source dependencies, not proprietary code.

Solution: Internal components need documentation too. Add them manually or configure tools appropriately.

SBOM Implementation Checklist

Format selection
  • CycloneDX 1.4+ or SPDX 2.3+ chosen.
  • JSON format selected (machine-readable, not PDF or spreadsheet).
  • Format decision documented for the team.

Either format satisfies the CRA. BSI TR-03183 recommends both. Switching formats mid-product is costly, so decide before the first release.

Tooling
  • Primary tool selected: Syft, Trivy, or cdxgen.
  • Tool installed and tested locally against a real artifact.
  • Output validated against the CycloneDX or SPDX schema.

Match the tool to the artifact. The tool comparison table above shows the best fit for containers, source trees, and firmware images.

CI/CD integration
  • SBOM generation added to the build pipeline.
  • Artifacts stored with appropriate retention policy.
  • Generation triggered on every release tag, not only on main-branch pushes.

A release without a matching SBOM is a compliance gap from day one. Wire generation into the pipeline so the step cannot be skipped.

Quality assurance
  • All components carry name, version, and supplier.
  • Hash values present for every component (SHA-256 minimum).
  • License information included using SPDX license identifiers.
  • Transitive dependencies captured, not only direct ones.
  • PURL identifiers used for each package.

Run cyclonedx validate and check for missing hashes with jq '[.components[] | select(.hashes == null)] | length'.

Operations
  • SBOM versioned and named alongside the product release it describes.
  • Historical SBOMs archived with a 10-year retention policy.
  • Process documented so any team member can regenerate on demand.

Retention is a legal obligation, not housekeeping. Keep each historical SBOM tied to the release it describes for the full period.

CRA Evidence (optional)
  • API token configured in CI secrets.
  • SBOM upload automated on release via CLI or API.
  • TR-03183 quality scoring reviewed after first upload.

CRA Evidence automates vulnerability rescanning, quality scoring, and technical file export with integrated SBOMs.

Frequently Asked Questions

Does the CRA require a specific SBOM format?

No. The CRA requires a machine-readable format but does not mandate CycloneDX or SPDX by name. Both formats satisfy the regulation. BSI TR-03183 explicitly recommends CycloneDX 1.4+ or SPDX 2.3+, and either gives you a defensible choice with auditors. Pick one and use it consistently across all releases.

Do I need to include transitive dependencies?

The CRA text says "at least top-level dependencies." That is the legal minimum. BSI TR-03183 recommends full transitive coverage, and it is what authorities and customers increasingly expect in practice. Scanning built artifacts rather than source manifests is the easiest way to capture the full dependency tree automatically.

Does the SBOM need to be made public?

No. The CRA does not require public disclosure. You must be able to provide the SBOM to market surveillance authorities on request. Sharing it with customers or publishing it openly is optional and a commercial decision. Many manufacturers provide SBOMs to enterprise customers as part of procurement.

How often must I update the SBOM?

The CRA does not set a calendar cadence. Each product version must have a current SBOM. Practically that means regenerating on every release, after dependency updates, after security patches, and when build configuration changes. Automating SBOM generation in CI/CD makes this automatic rather than a manual step you can forget.

Is a package.json or requirements.txt enough?

No. Source manifests are not SBOMs. A CRA-compliant SBOM must be in a machine-readable structured format (CycloneDX or SPDX), include cryptographic hashes for each component, and reflect the built artifact rather than just the source tree. A package.json lists intended dependencies; a CycloneDX SBOM from a scanned container image documents what actually shipped.

How long must I retain SBOMs?

Article 13(13) requires manufacturers to keep technical documentation for at least 10 years after the product is placed on the market, or for the support period, whichever is longer. The SBOM is part of that documentation, so the retention clock applies to it too.

What if a component has no PURL or hash?

Add it manually. Tools miss commercial SDKs, proprietary libraries, and internal components that do not appear in public package registries. A manual SBOM entry is valid under the CRA. An undocumented component is not. For components where you cannot compute a hash directly, document the source location and version string at minimum, and note the gap in your technical file.

Next Steps

What to do next

  1. Choose a format now. Pick CycloneDX 1.5 for new projects. Install Syft and run it against your main repository or container image. A working SBOM in 15 minutes is more useful than a perfect format decision that takes a week.
  2. Add SBOM generation to your CI/CD pipeline. Use the GitHub Actions, GitLab CI, or Jenkins examples above. Commit the output as a release artifact so every version has a corresponding SBOM from the moment it ships.
  3. Validate quality. Run cyclonedx validate and check that every component has a hash and a PURL. Components missing hashes fail the TR-03183 bar and will not satisfy auditors.
  4. Connect to vulnerability scanning. Upload your SBOM to CRA Evidence or run Grype against it. An SBOM that is never scanned gives you compliance paperwork, not security posture.
  5. Plan for 10-year retention now. Configure artifact lifecycle policies before you have three years of releases without them. Retroactively applying retention rules is painful and sometimes impossible.

Requirements: Understand what the CRA requires for SBOMs in our SBOM requirements guide.

Quality: Validate your SBOM against the BSI TR-03183 standard.

VEX: Add vulnerability context to your SBOM with VEX documents.


This article is for informational purposes only and does not constitute legal advice. For specific compliance guidance, consult with qualified legal counsel familiar with EU product regulations.

CRA SBOM
Share

Does the CRA apply to your product?

Answer 6 simple questions to find out if your product falls under the EU Cyber Resilience Act scope. Get your result in under 2 minutes.

Ready to achieve CRA compliance?

Start managing your SBOMs and compliance documentation with CRA Evidence.

Deep dive into CRA topics

Evergreen guides covering the specific requirements, processes, and roles defined by the Cyber Resilience Act.