An SBOM that is not current is an SBOM that lies to you. If your SBOM was generated last quarter but you have shipped 47 releases since then, it tells you nothing about what is actually running in production. The only way to keep SBOMs accurate is to automate their generation as part of every build.
This guide walks through practical SBOM automation for the CI/CD platforms teams actually use, with real configuration examples you can adapt.
The SBOM Automation Pipeline
A complete SBOM automation workflow has four stages:
- Generate — Create the SBOM during the build process
- Validate — Verify the SBOM is well-formed and meets minimum requirements
- Analyze — Scan the SBOM for vulnerabilities and policy violations
- Store and Distribute — Attach the SBOM to the build artifact and make it available to consumers
Stage 1: Generation
The best time to generate an SBOM is immediately after building your artifact, when the build environment has full visibility into resolved dependencies.
Source-Based Generation
Generate an SBOM from your source code and dependency manifests:
# Using Syft for source-based SBOM
syft dir:. -o cyclonedx-json > sbom.cdx.json
# Using cdxgen for more language-specific analysis
cdxgen -o sbom.cdx.json
Container Image-Based Generation
Generate an SBOM from the built container image (captures OS packages and runtime dependencies):
# Using Syft for container images
syft myregistry/myapp:${BUILD_TAG} -o cyclonedx-json > sbom.cdx.json
# Using Trivy
trivy image --format cyclonedx myregistry/myapp:${BUILD_TAG} > sbom.cdx.json
Which Approach?
Use both. Source-based SBOMs capture your application dependencies with precision. Image-based SBOMs capture OS-level packages that source analysis misses. The combination gives you complete coverage.
Stage 2: Validation
A malformed SBOM is worse than no SBOM because it gives false confidence. Validate every generated SBOM before using it.
# Validate CycloneDX format
cyclonedx-cli validate --input-file sbom.cdx.json --input-format json
# Check minimum fields are present
# (component names, versions, and package URLs)
Validation should check:
- Format compliance (valid JSON/XML against the schema)
- Minimum data quality (all components have names, versions, and package URLs)
- Completeness (the number of components is reasonable for the application)
Stage 3: Analysis
Once you have a validated SBOM, scan it for known vulnerabilities and policy violations:
# Vulnerability scanning with Grype
grype sbom:./sbom.cdx.json --fail-on high
# License compliance checking
# (tool-specific, integrate with your policy engine)
This is where policy gates come in. Define what constitutes a blocking issue:
- Critical or high CVEs with known exploits → block deployment
- Components with prohibited licenses → block deployment
- Components with no identified license → warn but allow
- Low/medium CVEs → warn, track for remediation
Stage 4: Store and Distribute
SBOMs need to be stored alongside the artifacts they describe and made available to downstream consumers.
Attach to Container Images
Using Cosign attestations, you can attach SBOMs directly to container images in your registry:
cosign attest --predicate sbom.cdx.json \
--type cyclonedx \
myregistry/myapp:${BUILD_TAG}
Store in Artifact Repository
Upload SBOMs to your artifact repository alongside build artifacts:
# Upload to S3
aws s3 cp sbom.cdx.json s3://sbom-bucket/${APP_NAME}/${BUILD_TAG}/sbom.cdx.json
# Upload to Artifactory
curl -u ${ARTIFACTORY_USER}:${ARTIFACTORY_TOKEN} \
-T sbom.cdx.json \
"https://artifactory.example.com/sbom-repo/${APP_NAME}/${BUILD_TAG}/sbom.cdx.json"
Platform-Specific Examples
GitHub Actions
name: Build with SBOM
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build application
run: npm ci && npm run build
- name: Build container image
run: docker build -t myregistry/myapp:${{ github.sha }} .
- name: Generate source SBOM
uses: anchore/sbom-action@v0
with:
format: cyclonedx-json
output-file: sbom-source.cdx.json
- name: Generate image SBOM
run: |
syft myregistry/myapp:${{ github.sha }} \
-o cyclonedx-json > sbom-image.cdx.json
- name: Scan for vulnerabilities
run: |
grype sbom:./sbom-image.cdx.json --fail-on high
- name: Push image and attach SBOM
run: |
docker push myregistry/myapp:${{ github.sha }}
cosign attest --predicate sbom-image.cdx.json \
--type cyclonedx \
myregistry/myapp:${{ github.sha }}
- name: Upload SBOM artifact
uses: actions/upload-artifact@v3
with:
name: sbom
path: sbom-*.cdx.json
GitLab CI
stages:
- build
- sbom
- scan
- deploy
build:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
generate-sbom:
stage: sbom
image: anchore/syft:latest
script:
- syft $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -o cyclonedx-json > sbom.cdx.json
artifacts:
paths:
- sbom.cdx.json
expire_in: 1 year
vulnerability-scan:
stage: scan
image: anchore/grype:latest
script:
- grype sbom:./sbom.cdx.json --fail-on high
dependencies:
- generate-sbom
Jenkins
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'docker build -t myregistry/myapp:${BUILD_TAG} .'
}
}
stage('Generate SBOM') {
steps {
sh 'syft myregistry/myapp:${BUILD_TAG} -o cyclonedx-json > sbom.cdx.json'
archiveArtifacts artifacts: 'sbom.cdx.json'
}
}
stage('Security Scan') {
steps {
sh 'grype sbom:./sbom.cdx.json --fail-on high'
}
}
}
}
Common Pitfalls
Generating SBOMs too early. If you generate the SBOM before dependency resolution, you capture what is declared, not what is installed. Always generate after npm ci, pip install, or equivalent.
Not including OS packages. Source-based SBOMs miss the base image layer. If your container uses ubuntu:22.04, those 150+ OS packages need to be in your SBOM too.
Treating SBOM generation as a one-time project. SBOMs must be regenerated on every build. If it is not automated, it will fall behind.
Not versioning SBOMs. Each release should have its own SBOM. Store them with the same version identifier as the artifact they describe.
How Safeguard.sh Helps
Safeguard.sh integrates directly into your CI/CD pipeline to automate SBOM generation, validation, vulnerability scanning, and policy enforcement in a single step. Our platform stores historical SBOMs for every build, enabling you to track how your dependency landscape evolves over time and quickly diff between releases to identify newly-introduced risks. With native support for GitHub Actions, GitLab CI, and Jenkins, getting started takes minutes, not days.