Supply Chain Security: SBOM, SLSA, and Dependency Auditing

Supply chain attacks target the build process itself — injecting malicious code into dependencies, build tools, or CI infrastructure rather than the application directly. The SolarWinds, Log4Shell, an

Introduction#

Supply chain attacks target the build process itself — injecting malicious code into dependencies, build tools, or CI infrastructure rather than the application directly. The SolarWinds, Log4Shell, and XZ Utils incidents demonstrated the scale of damage possible. SBOM (Software Bill of Materials) and SLSA (Supply chain Levels for Software Artifacts) are the industry’s response.

Software Bill of Materials (SBOM)#

An SBOM is a machine-readable inventory of all components in a software artifact.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Generate SBOM for a Python project using syft
syft packages python:requirements.txt -o spdx-json > sbom.spdx.json
syft packages dir:. -o cyclonedx-json > sbom.cyclonedx.json

# For Docker images
syft packages my-image:latest -o spdx-json > image-sbom.spdx.json

# Scan SBOM for known vulnerabilities with grype
grype sbom:sbom.spdx.json

# Output:
# NAME         VERSION  VULNERABILITY  SEVERITY
# cryptography 39.0.0   CVE-2023-xxxx  HIGH
# requests     2.28.0   CVE-2023-yyyy  MEDIUM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Python: programmatic dependency scanning
import subprocess
import json

def check_dependencies() -> list[dict]:
    """Run pip-audit and return vulnerabilities."""
    result = subprocess.run(
        ["pip-audit", "--format", "json", "--output", "-"],
        capture_output=True,
        text=True,
    )
    if result.returncode == 0:
        return []
    data = json.loads(result.stdout)
    return data.get("vulnerabilities", [])

vulnerabilities = check_dependencies()
for vuln in vulnerabilities:
    print(f"{vuln['name']} {vuln['version']}: {vuln['id']} ({vuln['fix_versions']})")

CI/CD: Dependency Scanning#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# GitHub Actions: dependency scanning on every PR
name: Security Scan

on: [push, pull_request]

jobs:
  dependency-audit:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Python dependency audit
      run: pip-audit --strict  # fail if vulnerabilities found

    - name: Generate SBOM
      uses: anchore/sbom-action@v0
      with:
        image: my-registry/my-app:${{ github.sha }}
        format: spdx-json
        output-file: sbom.spdx.json

    - name: Scan SBOM for vulnerabilities
      uses: anchore/scan-action@v3
      with:
        sbom: sbom.spdx.json
        fail-build: true
        severity-cutoff: high

    - name: Upload SBOM as artifact
      uses: actions/upload-artifact@v4
      with:
        name: sbom
        path: sbom.spdx.json

Pinning Dependencies#

Unpinned dependencies can pull in malicious new versions automatically.

1
2
3
4
5
6
7
# UNSAFE: version ranges allow unexpected updates
requests>=2.0.0
flask~=3.0

# SAFE: pin exact versions
requests==2.31.0
flask==3.0.2
1
2
3
4
5
6
7
# Python: generate pinned requirements with hashes
pip-compile --generate-hashes requirements.in -o requirements.txt

# requirements.txt with hashes:
# requests==2.31.0 \
#     --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 \
#     --hash=sha256:...
1
2
3
4
5
# Node.js: package-lock.json pins exact versions and hashes
npm ci  # uses lockfile exactly, does not update

# Verify integrity
npm audit

SLSA: Supply Chain Levels for Software Artifacts#

SLSA defines four levels of build integrity:

Level Requirements
1 Build process documented; provenance generated
2 Tamper-resistant provenance; hosted build service
3 Source and build integrity verified; isolated build environment
4 Two-party review; hermetic builds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# GitHub Actions: SLSA Level 3 provenance with slsa-github-generator
name: Build with SLSA Provenance

on:
  push:
    tags: ["v*"]

permissions:
  id-token: write
  contents: read
  actions: read

jobs:
  build:
    uses: slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@v1.10.0
    with:
      go-version: "1.22"

Signed Container Images with Cosign#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Sign a container image
cosign sign \
  --key cosign.key \
  my-registry/my-app:2.3.1

# Verify signature
cosign verify \
  --key cosign.pub \
  my-registry/my-app:2.3.1

# Sign with OIDC (keyless, uses GitHub Actions OIDC token)
# In GitHub Actions:
cosign sign \
  --yes \
  --identity-token ${{ steps.auth.outputs.access_token }} \
  my-registry/my-app:${{ github.sha }}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Kubernetes: enforce signed images with Kyverno
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-image-signature
    match:
      any:
      - resources:
          kinds: [Pod]
    verifyImages:
    - imageReferences: ["my-registry.example.com/*"]
      attestors:
      - count: 1
        entries:
        - keys:
            publicKeys: |-
              -----BEGIN PUBLIC KEY-----
              ...
              -----END PUBLIC KEY-----

Detecting Typosquatting#

1
2
3
4
5
6
7
8
9
10
# pip-audit: checks for known typosquatted packages
pip-audit

# Check if a package name is suspiciously similar to a popular one
# "requets", "reqeusts", "boto" vs "boto3"

# Use an allowlist of approved packages in CI
cat allowed_packages.txt | sort > allowed_sorted.txt
pip freeze | cut -d= -f1 | sort > installed_sorted.txt
comm -23 installed_sorted.txt allowed_sorted.txt  # packages not in allowlist

Conclusion#

Supply chain security requires defense at multiple layers: pin dependencies with hashes, scan for known CVEs in CI, generate and attest SBOMs, sign container images, and verify signatures before deployment. SLSA provides a framework for measuring and improving build provenance integrity. Start with dependency pinning and automated scanning — these provide the most immediate value with the least complexity.

Contents