SBOMs and Software Supply Chain Security: From Executive Order to Daily Practice

SBOMs and Software Supply Chain Security: From Executive Order to Daily Practice

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:

  1. Container images have a signed SBOM attestation
  2. The attestation was signed by a specific GitHub Actions workflow (using keyless signing)
  3. 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.

Share this post: LinkedIn Reddit WhatsApp Mastodon
Jesse Borden

Jesse Borden

Software Engineer with an interest in hands on learning

I have several years of professional Information Technology (IT) experience leading staff and projects within the Department of War (DOW). I have managed Service Desk, Web Application Development, and System Administration teams. My two greatest passions are learning and conti...