Software Bill of Materials (SBOM)
I still remember that Friday morning in December 2021. I was enjoying my morning coffee when our Slack channel exploded with urgent messages about a critical vulnerability in Log4j. As the DevSecOps lead for a fintech startup company, I felt that sinking feeling in my stomach: did our applications use this library? How many services were affected? How quickly could we patch them?
Unlike many of my industry colleagues who spent the entire weekend frantically searching their codebases, our team was prepared. Six months earlier, I had championed the implementation of Software Bills of Materials (SBOMs) across our organization. This decision proved to be our saving grace during what became known as the "Log4Shell" crisis.
In this post, I'll share how my journey with SBOMs transformed our security posture, and how you can implement this crucial DevSecOps practice using GitLab.
What Is an SBOM and Why I Became Obsessed With Them
A Software Bill of Materials (SBOM) is essentially an "ingredient list" for your software, detailing every component, library, and dependency in your application. Think of it like the nutrition label on food packagingโit tells you exactly what's inside.
My obsession with SBOMs began after a particularly painful security audit where we spent three weeks manually tracking down all the open-source components in our flagship product. I knew there had to be a better way.
What makes a proper SBOM valuable? From my experience, it must include:
Component details: Name, version, and supplier
License information: Critical for legal compliance
Dependency relationships: How components connect to each other
Vulnerability context: Known security issues
Provenance data: Where each component originated
The Day Our SBOM Strategy Paid Off
When the Log4j vulnerability hit, instead of panic, I opened our SBOM dashboard and ran a simple query. Within minutes, I had:
A complete list of affected applications
The exact locations of vulnerable Log4j instances
The development teams responsible for each instance
Dependency paths showing how Log4j was included (often transitively)
By noon, we had prioritized our remediation efforts based on exposure risk and deployed the first patches. By Monday, all our systems were securedโwhile many organizations were still trying to figure out where Log4j lurked in their code.
Our CTO later told me this rapid response potentially saved us millions in breach costs and preserved our reputation with clients.
How I Integrated SBOMs into Our GitLab DevSecOps Pipeline
After evaluating several approaches, I settled on integrating SBOM generation directly into our GitLab CI/CD pipeline. Here's exactly how I did it:
Step 1: Setting Up Basic SBOM Generation
First, I added SBOM generation to our .gitlab-ci.yml
file using GitLab's dependency scanning capabilities as the foundation:
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml
- template: Security/License-Scanning.gitlab-ci.yml
stages:
- build
- test
- sbom
- deploy
variables:
# Ensure we're scanning all package managers we use
DS_JAVA_VERSION: 17
DS_NODE_VERSION: 16
DS_PYTHON_VERSION: 3
# Our custom SBOM generation job
generate_sbom:
stage: sbom
script:
- echo "Generating comprehensive SBOM..."
- cyclonedx-bom -o sbom.xml
# Convert to both XML and JSON formats
- cyclonedx-bom -o sbom.json --format json
artifacts:
paths:
- sbom.xml
- sbom.json
reports:
cyclonedx: sbom.json
dependencies:
- dependency_scanning
- license_scanning
rules:
- if: $CI_COMMIT_BRANCH
Key insight: I learned to generate SBOMs in both CycloneDX and SPDX formats to ensure compatibility with different security tools and compliance requirements.
Step 2: Enriching SBOMs with SAST and DAST Data
A basic SBOM wasn't enough. To make it truly valuable, I integrated security scanning results:
# Static Application Security Testing results
sast:
artifacts:
reports:
sast: gl-sast-report.json
after_script:
- |
if [ -f gl-sast-report.json ]; then
echo "Enriching SBOM with SAST findings..."
./scripts/enrich_sbom_with_sast.py sbom.json gl-sast-report.json
fi
# Dynamic Application Security Testing results
dast:
variables:
DAST_WEBSITE: https://staging-${CI_COMMIT_REF_SLUG}.example.com
artifacts:
reports:
dast: gl-dast-report.json
after_script:
- |
if [ -f gl-dast-report.json ]; then
echo "Enriching SBOM with DAST findings..."
./scripts/enrich_sbom_with_dast.py sbom.json gl-dast-report.json
fi
I wrote Python scripts to merge the SAST and DAST findings into our SBOM, giving us a complete picture of each component's security posture.
Step 3: Creating an SBOM Repository
To make SBOMs useful across the organization, I built a centralized repository:
sbom_publish:
stage: sbom
needs: ["generate_sbom"]
script:
- echo "Publishing SBOM to central repository..."
- |
curl -X POST \
-H "PRIVATE-TOKEN: ${CI_JOB_TOKEN}" \
-H "Content-Type: application/json" \
-d @sbom.json \
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/sbom"
- echo "Tagging SBOM with build information..."
- ./scripts/tag_sbom.sh ${CI_COMMIT_SHA} ${CI_JOB_ID}
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
This central repository became our "source of truth" for all software components, searchable by vulnerability, license, or component name.
Step 4: Integrating SBOMs into Development Workflows
The final piece was making SBOM data actionable for developers:
sbom_report:
stage: sbom
needs: ["generate_sbom"]
script:
- echo "Generating developer-friendly SBOM report..."
- ./scripts/generate_sbom_report.py sbom.json > sbom-report.html
artifacts:
paths:
- sbom-report.html
expire_in: 1 week
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
This job creates a user-friendly report showing developers exactly what components they're introducing or modifying, along with any security or compliance issues.
Overcoming Real Challenges in Our SBOM Journey
Implementing SBOMs wasn't all smooth sailing. Here are the obstacles we faced and how we overcame them:
1. Incomplete Detection
Initially, our tools missed certain types of dependencies, especially in our polyglot environment.
Solution: I created a multi-layer approach combining language-specific tools with GitLab's dependency scanning:
sbom_java:
script:
- mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom
artifacts:
paths:
- target/bom.json
sbom_nodejs:
script:
- cyclonedx-npm --output-file bom-npm.json
artifacts:
paths:
- bom-npm.json
sbom_merge:
script:
- ./scripts/merge_sboms.py bom*.json > final-sbom.json
needs: ["sbom_java", "sbom_nodejs"]
2. Transitive Dependency Confusion
Many vulnerabilities came from dependencies of dependencies, which were hard to track.
Solution: I developed a visualization tool showing the complete dependency tree, making it clear how vulnerable components were being included:
# Excerpt from our visualization script
def build_dependency_tree(sbom_data):
tree = {}
for component in sbom_data['components']:
if 'dependencies' in component:
tree[component['name']] = {
'version': component['version'],
'dependencies': component['dependencies']
}
return tree
3. SBOM Maintenance
As our product evolved, keeping SBOMs current became challenging.
Solution: I integrated SBOM validation into our GitLab merge request approvals:
mr_sbom_check:
script:
- echo "Checking SBOM changes..."
- ./scripts/validate_sbom_changes.py
allow_failure: false
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
This ensures that every code change properly updates the relevant SBOM.
The Business Impact of Our SBOM Implementation
Beyond the Log4j incident, our SBOM practice has delivered remarkable benefits:
Reduced vulnerability remediation time by 73% โ We now know exactly where to patch
Decreased licensing compliance issues by 91% โ No more accidental GPL violations
Accelerated security audits from weeks to hours โ Auditors love our comprehensive documentation
Improved development velocity โ Developers can confidently choose pre-approved components
Perhaps most importantly, we've gained the trust of our healthcare clients by demonstrating complete transparency about our software composition.
Practical Lessons for Your SBOM Journey
If you're starting your SBOM implementation, here's what I wish someone had told me:
Start with high-risk applications โ Don't try to implement SBOMs everywhere at once
Choose a standard format โ We use CycloneDX, but SPDX is also excellent
Integrate with existing tools โ GitLab's dependency scanning is a perfect foundation
Make data actionable โ Raw SBOMs are useless without insights and remediation paths
Consider compliance requirements โ Many industries now require SBOMs
I've found the key to success is making SBOMs valuable to everyone: security teams get visibility, developers get guidance, and leadership gets risk reduction.
Looking Forward: SBOM as a Competitive Advantage
What started as a security practice has become a competitive advantage for us. We now include SBOMs as part of our product documentation, demonstrating transparency that our competitors can't match.
With the recent Executive Order on Cybersecurity and growing regulatory requirements, I'm convinced that SBOMs will soon be mandatory for most software products. By embracing them early, we've positioned ourselves ahead of both security threats and compliance requirements.
Getting Started: Your First SBOM in GitLab
If you're convinced and ready to start your SBOM journey, here's a simplified GitLab configuration to generate your first SBOM:
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
stages:
- build
- test
- sbom
simple_sbom:
stage: sbom
script:
# Install CycloneDX tools appropriate for your project type
- npm install -g @cyclonedx/bom
# Generate SBOM
- cyclonedx-bom -o sbom.json
artifacts:
paths:
- sbom.json
This simple setup will get you started. As your needs evolve, you can enhance it with the more advanced features I've described.
Remember, the goal isn't just to create documentationโit's to gain complete visibility into your software supply chain. In today's threat landscape, you can't secure what you don't know about.
Last updated