The Log4Shell vulnerability in December 2021 was a supply chain security wake-up call. A critical vulnerability buried in a transitive dependency (a library that none of you explicitly included, included by a library you use) was present in thousands of products. Organizations scrambled to answer a simple question: “Are we affected?”
Many couldn’t answer that question because they didn’t know what software they were actually running. A Software Bill of Materials (SBOM) is the tool that makes this question answerable.
What an SBOM Is
An SBOM is a machine-readable inventory of the components in a software artifact — the packages, libraries, and dependencies that make up a container image, application binary, or firmware. It’s the software equivalent of a food nutrition label or an ingredient list.
// Simplified SPDX SBOM fragment
{
"SPDXID": "SPDXRef-DOCUMENT",
"spdxVersion": "SPDX-2.3",
"name": "myapp-container:v1.2.3",
"packages": [
{
"SPDXID": "SPDXRef-Package-1",
"name": "log4j-core",
"version": "2.14.1", // This would have been the vulnerable version
"supplier": "Organization: Apache",
"licenseConcluded": "Apache-2.0",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:a:apache:log4j:2.14.1:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:maven/org.apache.logging.log4j/[email protected]"
}
]
}
]
}
When Log4Shell was disclosed, organizations with SBOMs could query: “Do any of my container images contain log4j-core versions 2.0 through 2.14.1?” and get an answer in seconds. Organizations without SBOMs spent days manually checking.
Regulatory Context
Executive Order 14028 (Biden, 2021): Required federal agencies to obtain SBOMs for software used in critical systems. This pushed federal contractors to generate SBOMs.
NIST SSDF (Secure Software Development Framework): Incorporated SBOM generation as a software development practice expectation.
EU Cyber Resilience Act (2024): Requires SBOMs for products with digital elements sold in the EU. Taking effect in 2027.
FDA Medical Device Regulations: SBOMs required for medical devices containing software since 2023.
The regulatory pressure is expanding SBOMs beyond federal contractors into any company selling software to regulated industries.
Generating SBOMs with Syft
Syft (from Anchore) is the most widely-used open-source SBOM generator:
# Install Syft
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# Generate an SBOM for a container image
syft nginx:latest -o spdx-json > nginx-sbom.spdx.json
# Generate for a directory (source code analysis)
syft dir:. -o cyclonedx-json > app-sbom.cdx.json
# Generate for a binary
syft file:./myapp -o spdx-json > myapp-sbom.spdx.json
Syft supports multiple output formats:
- SPDX (Software Package Data Exchange): Linux Foundation standard, widely used in government and finance
- CycloneDX: OWASP standard, popular for application security tooling
- Syft JSON: Syft’s native format, richest data
For Kubernetes workloads, generate SBOMs at build time in CI:
# .github/workflows/build-and-sign.yaml
name: Build, SBOM, Sign
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build container image
run: |
docker build -t myapp:$ .
docker save myapp:$ > myapp.tar
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: myapp:$
format: spdx-json
output-file: myapp.sbom.spdx.json
artifact-name: sbom.spdx.json
- name: Install Cosign
uses: sigstore/cosign-installer@v3
- name: Sign image and attach SBOM
run: |
# Sign the image
cosign sign --yes myapp:$
# Attach SBOM to the image in the registry
cosign attach sbom --sbom myapp.sbom.spdx.json myapp:$
# Sign the SBOM attestation
cosign attest --yes \
--predicate myapp.sbom.spdx.json \
--type spdx \
myapp:$
Vulnerability Scanning with Grype
Once you have an SBOM, use it to scan for vulnerabilities:
# Install Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
# Scan using an SBOM (faster than scanning the image directly)
grype sbom:myapp.sbom.spdx.json
# Scan and filter by severity
grype sbom:myapp.sbom.spdx.json --fail-on high
# Get JSON output for tooling integration
grype sbom:myapp.sbom.spdx.json -o json > vulnerabilities.json
# Example output
# NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY
# openssl 3.0.2 3.0.7 deb CVE-2022-3602 Critical
# curl 7.81.0 7.86.0 deb CVE-2022-32221 High
In CI, fail the build on critical vulnerabilities:
# In your pipeline
grype sbom:myapp.sbom.spdx.json --fail-on critical
if [ $? -ne 0 ]; then
echo "Critical vulnerabilities found! Build failed."
exit 1
fi
Verifying SBOMs and Signatures at Admission
Enforce SBOM attestation in Kubernetes using Kyverno:
# Require all containers to have a signed SBOM attestation
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-sbom-attestation
spec:
validationFailureAction: Enforce
background: false
rules:
- name: check-sbom-attestation
match:
any:
- resources:
kinds:
- Pod
namespaces:
- production
verifyImages:
- imageReferences:
- "myregistry.example.com/*"
attestations:
- type: https://spdx.dev/Document # SPDX SBOM type
attestors:
- count: 1
entries:
- keyless:
subject: "https://github.com/myorg/myrepo/.github/workflows/build.yaml@refs/heads/main"
issuer: "https://token.actions.githubusercontent.com"
rekor: "https://rekor.sigstore.dev"
This policy ensures that:
- Container images have a signed SBOM attestation
- The attestation was signed by a specific GitHub Actions workflow (using keyless signing)
- Unsigned or improperly signed images are rejected at admission
Automating Vulnerability Response
When a new CVE is disclosed, query your SBOM database:
#!/usr/bin/env python3
"""
Query which deployed images are affected by a CVE
"""
import json
import subprocess
from pathlib import Path
def check_sbom_for_cve(sbom_path: Path, cve_id: str) -> dict:
"""Check an SBOM file for a specific CVE"""
result = subprocess.run(
["grype", f"sbom:{sbom_path}", "--add-cpes-if-none", "-o", "json"],
capture_output=True,
text=True
)
if result.returncode == 0:
data = json.loads(result.stdout)
matches = [
m for m in data.get("matches", [])
if any(v["id"] == cve_id for v in m.get("vulnerability", {}).get("relatedVulnerabilities", []))
or m.get("vulnerability", {}).get("id") == cve_id
]
return {"affected": len(matches) > 0, "matches": matches}
return {"affected": False, "error": result.stderr}
def check_all_deployments(cve_id: str):
"""Check all deployed images for a CVE"""
# Get all running images from Kubernetes
result = subprocess.run(
["kubectl", "get", "pods", "-A", "-o",
"jsonpath={range .items[*]}{.metadata.namespace}/{.metadata.name}:{range .spec.containers[*]}{.image}\\n{end}{end}"],
capture_output=True, text=True
)
affected = []
for line in result.stdout.strip().split('\n'):
if ':' not in line:
continue
pod_ref, image = line.split(':', 1)
# Look for cached SBOM
sbom_path = Path(f"sboms/{image.replace('/', '_').replace(':', '_')}.spdx.json")
if sbom_path.exists():
check_result = check_sbom_for_cve(sbom_path, cve_id)
if check_result["affected"]:
affected.append({"pod": pod_ref, "image": image, "details": check_result})
return affected
# Usage: python check-cve.py CVE-2024-XXXX
if __name__ == "__main__":
import sys
cve = sys.argv[1] if len(sys.argv) > 1 else "CVE-2021-44228"
results = check_all_deployments(cve)
print(f"Found {len(results)} affected deployments:")
for r in results:
print(f" {r['pod']}: {r['image']}")
Storing and Querying SBOMs
For an organization running many images, you need SBOM storage:
# Grype DB for offline vulnerability database
grype db update
# Store SBOMs in a registry alongside the image
# (Using cosign to attach)
cosign attach sbom --sbom myapp.sbom.spdx.json myregistry.example.com/myapp:v1.2.3
# Retrieve the SBOM later
cosign download sbom myregistry.example.com/myapp:v1.2.3 > retrieved.sbom.json
For centralized SBOM management, tools like Dependency-Track (OWASP) provide a web UI and API for storing, querying, and alerting on SBOMs:
# Deploy Dependency-Track in Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: dependency-track
namespace: security
spec:
template:
spec:
containers:
- name: apiserver
image: dependencytrack/apiserver:4.11.0
resources:
limits:
memory: "4Gi"
env:
- name: ALPINE_DATABASE_MODE
value: "internal"
volumeMounts:
- name: data
mountPath: /data
Dependency-Track provides dashboards showing which projects have which vulnerable dependencies and alerts when new CVEs affect your known components.
Conclusion
SBOMs are becoming table stakes for any organization that takes supply chain security seriously. The regulatory pressure is real and growing. More importantly, the operational value — knowing exactly what’s in your software and being able to quickly assess CVE impact — is genuine.
The toolchain is mature: Syft generates SBOMs, Grype scans them, Cosign signs and attaches them to images, Kyverno enforces attestation at admission. Integrate this into your CI/CD pipeline, store SBOMs alongside your images, and you’ll have the foundation for real software supply chain security.
When the next Log4Shell happens — and there will be a next Log4Shell — you’ll be the team that answers “are we affected?” in minutes rather than days.