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.