The SolarWinds breach. The Log4Shell vulnerability. The xz-utils backdoor. What these incidents have in common is that the attack didn’t come through the front door — it came through the software supply chain. Trusted code, from trusted sources, delivered through trusted update mechanisms, was weaponised against the people trusting it.
Software supply chain security is the discipline of knowing exactly what’s in your software, verifying it was built the way it claims, and detecting when something unexpected slips in. This guide covers the four pillars of modern supply chain security: SBOMs (knowing what you have), Sigstore/Cosign (proving who built it and that it hasn’t been tampered with), SLSA (attesting how it was built), and integrating all of this into CI/CD.
The Problem: You Don’t Know What You’re Running
A typical containerised application pulls in:
- A base OS image (Ubuntu, Alpine, Debian)
- Runtime dependencies (Node modules, Python packages, Go modules)
- Build tools that may leave artifacts behind
- Transitive dependencies of dependencies — often hundreds deep
Before modern supply chain tooling, you had limited visibility into any of this. You knew your direct dependencies. You didn’t know what they depended on, which of those had CVEs, or whether the container image you pulled from Docker Hub was actually the one the maintainer built.
Three questions every team should be able to answer:
- What’s in it? — SBOM
- Did the right person build it? — Artifact signing (Cosign/Sigstore)
- Was it built the right way? — SLSA provenance
SBOMs: A Bill of Materials for Software
A Software Bill of Materials (SBOM) is a machine-readable inventory of every component in a software artifact — libraries, packages, licenses, versions, and their relationships. Think of it as the ingredient list on food packaging, but for software.
Two dominant formats:
- SPDX (Software Package Data Exchange) — ISO/IEC 5962:2021 standard, backed by the Linux Foundation
- CycloneDX — OWASP standard, focused on security use cases, wider tool support
Generating SBOMs with Syft
Syft by Anchore is the most capable open-source SBOM generator. It understands container images, filesystems, and source trees.
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
|
# Install Syft
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# Generate an SBOM from a container image
syft nginx:latest -o spdx-json > nginx-sbom.spdx.json
syft nginx:latest -o cyclonedx-json > nginx-sbom.cdx.json
# Human-readable table output
syft nginx:latest
# NAME VERSION TYPE
# adduser 3.134 deb
# apt 2.6.1 deb
# base-files 12.4+deb12u5 deb
# bash 5.2.15-2+b8 deb
# ...
# libssl3 3.0.13-1~deb12u1 deb ← potential CVE target
# nginx 1.25.4-2~bookworm deb
# openssl 3.0.13-1~deb12u1 deb
# SBOM from a local directory (source code)
syft dir:./myapp -o cyclonedx-json > myapp-source-sbom.cdx.json
# SBOM from a local Docker image tarball
docker save myapp:latest | syft -o spdx-json -
# SBOM from a running container's filesystem
syft container:my-running-container -o cyclonedx-json > running-sbom.cdx.json
|
The SBOM output is a JSON file listing every package. Here’s an excerpt of CycloneDX format:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"version": 1,
"metadata": {
"timestamp": "2026-03-26T10:00:00Z",
"component": {
"type": "container",
"name": "nginx",
"version": "latest"
}
},
"components": [
{
"type": "library",
"name": "openssl",
"version": "3.0.13-1~deb12u1",
"purl": "pkg:deb/debian/openssl@3.0.13-1~deb12u1",
"licenses": [{ "license": { "id": "Apache-2.0" } }]
}
]
}
|
Scanning SBOMs for Vulnerabilities with Grype
Grype scans SBOMs (and images directly) against vulnerability databases:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
# Install Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
# Scan a container image
grype nginx:latest
# Scan an SBOM file (much faster — no image pull needed)
grype sbom:nginx-sbom.spdx.json
# Example output:
# NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY
# libssl3 3.0.13-1~deb12u1 3.0.14 deb CVE-2024-0727 Medium
# openssl 3.0.13-1~deb12u1 3.0.14 deb CVE-2024-0727 Medium
# ...
# Output only critical/high findings
grype nginx:latest --fail-on high
# JSON output for CI integration
grype nginx:latest -o json > grype-results.json
# Scan only fixed vulnerabilities (ones that have a patch available)
grype nginx:latest --only-fixed
|
SBOM in CI/CD
Generate and store an SBOM as part of every build:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
# .github/workflows/build.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: myapp:${{ github.sha }}
format: spdx-json
output-file: sbom.spdx.json
artifact-name: sbom.spdx.json
- name: Scan SBOM for vulnerabilities
uses: anchore/scan-action@v3
with:
sbom: sbom.spdx.json
fail-build: true
severity-cutoff: critical
|
Cosign and Sigstore: Signing Everything
Knowing what’s in your software doesn’t help if you can’t trust where it came from. Artifact signing lets you cryptographically verify that an image or binary was produced by a specific identity and hasn’t been modified since.
Sigstore is a Linux Foundation project that makes signing and verification as easy as running a single command. Its key innovation is keyless signing via OIDC: instead of managing private keys, you sign using a short-lived certificate tied to your identity (GitHub Actions, Google, GitHub account). The certificate is recorded in a public, append-only transparency log (Rekor) — anyone can verify the signature.
Cosign is the CLI tool for signing container images and other artifacts.
Installing Cosign
1
2
3
4
5
6
7
|
# Linux
curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64"
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
sudo chmod +x /usr/local/bin/cosign
# Verify the install
cosign version
|
Keyless Signing in GitHub Actions
The most important pattern: sign automatically during CI using the OIDC identity of the GitHub Actions workflow itself.
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
# .github/workflows/build-and-sign.yml
name: Build, Push, and Sign
on:
push:
branches: [main]
permissions:
contents: read
id-token: write # REQUIRED for keyless signing
packages: write
jobs:
build-sign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Cosign
uses: sigstore/cosign-installer@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image
id: build
uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
# Capture the digest for signing
outputs: type=image,push=true
- name: Sign the container image (keyless)
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: |
cosign sign --yes \
ghcr.io/${{ github.repository }}@${DIGEST}
# This:
# 1. Gets a short-lived OIDC token from GitHub Actions
# 2. Exchanges it for a certificate from Fulcio (Sigstore's CA)
# 3. Signs the image digest with that certificate
# 4. Records the signature in Rekor (public transparency log)
- name: Attach SBOM as attestation
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: |
# Generate SBOM
syft ghcr.io/${{ github.repository }}@${DIGEST} \
-o spdx-json > sbom.spdx.json
# Attach SBOM as a signed attestation on the image
cosign attest --yes \
--predicate sbom.spdx.json \
--type spdxjson \
ghcr.io/${{ github.repository }}@${DIGEST}
|
Verifying Signatures
Anyone pulling your image can verify it was signed by your GitHub Actions workflow:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# Verify the signature
cosign verify \
--certificate-identity-regexp "https://github.com/myorg/myrepo/.github/workflows/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myrepo:sha256-abc123...
# Output if valid:
# Verification for ghcr.io/myorg/myrepo:sha256-abc123... --
# The following checks were performed on each of these signatures:
# - The cosign claims were validated
# - Existence of the claims in the transparency log was verified offline
# - The code-signing certificate claims were verified
# [{"critical":{"identity":{"docker-reference":"ghcr.io/myorg/myrepo"},...}]
# Verify and extract the SBOM attestation
cosign verify-attestation \
--certificate-identity-regexp "https://github.com/myorg/myrepo/.github/workflows/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--type spdxjson \
ghcr.io/myorg/myrepo:sha256-abc123... \
| jq -r '.payload' | base64 -d | jq .
|
Key-Based Signing (for Air-Gapped or Private Environments)
For environments without OIDC, use traditional key pairs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# Generate a signing key pair
cosign generate-key-pair
# Outputs cosign.key (private) and cosign.pub (public)
# Protect cosign.key — treat it as a high-value secret (store in Vault or HSM)
# Sign an image
cosign sign --key cosign.key ghcr.io/myorg/myrepo:latest
# Verify with public key
cosign verify --key cosign.pub ghcr.io/myorg/myrepo:latest
# Sign a binary file
cosign sign-blob --key cosign.key \
--output-signature myapp.sig \
--output-certificate myapp.pem \
myapp-linux-amd64
# Verify a blob
cosign verify-blob \
--key cosign.pub \
--signature myapp.sig \
myapp-linux-amd64
|
Signing Other Artifacts (Helm Charts, Files)
Cosign works beyond container images:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# Sign a Helm chart
helm package ./mychart
cosign sign-blob --yes mychart-1.0.0.tgz \
--output-signature mychart-1.0.0.tgz.sig \
--output-certificate mychart-1.0.0.tgz.pem
# Sign an SBOM file itself
cosign sign-blob --yes sbom.spdx.json \
--output-signature sbom.spdx.json.sig \
--output-certificate sbom.spdx.json.pem
# Sign an OCI artifact (non-image)
cosign upload blob -f myfile.tar.gz \
ghcr.io/myorg/artifacts:myfile
cosign sign --yes ghcr.io/myorg/artifacts:myfile
|
SLSA: Attesting How Software Was Built
SLSA (Supply-chain Levels for Software Artifacts, pronounced “salsa”) is a security framework from Google, now under the OpenSSF umbrella. Where Cosign tells you who signed the artifact, SLSA tells you how it was built — specifically, whether the build process was trustworthy.
SLSA defines four levels of assurance:
| Level |
Requirements |
What It Prevents |
| SLSA 1 |
Provenance exists |
Accidental mistakes, basic tampering |
| SLSA 2 |
Signed provenance from a hosted build service |
Tampering after the fact |
| SLSA 3 |
Hardened build platform, non-forgeable provenance |
Insider threats on the build system |
| SLSA 4 |
Two-person review, hermetic builds |
Sophisticated insider attacks |
Most teams should target SLSA 2 initially, with SLSA 3 as a goal for critical software.
What Is Provenance?
Provenance is a signed document that records:
- Which source repository the build came from
- Which exact commit was built (git SHA)
- Which build system produced it
- What inputs (dependencies) were used
- The output artifact’s digest
A SLSA Level 2 provenance document looks like this:
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
|
{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://slsa.dev/provenance/v0.2",
"subject": [{
"name": "ghcr.io/myorg/myrepo",
"digest": { "sha256": "abc123..." }
}],
"predicate": {
"builder": {
"id": "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v1.9.0"
},
"buildType": "https://github.com/slsa-framework/slsa-github-generator/container@v1",
"invocation": {
"configSource": {
"uri": "git+https://github.com/myorg/myrepo@refs/heads/main",
"digest": { "sha1": "deadbeef..." },
"entryPoint": ".github/workflows/build.yml"
}
},
"metadata": {
"buildStartedOn": "2026-03-26T10:00:00Z",
"completeness": {
"parameters": true,
"environment": false,
"materials": true
}
}
}
}
|
Generating SLSA Provenance with the GitHub Generator
The SLSA GitHub Generator is the easiest way to reach SLSA Level 3 for GitHub Actions builds. It runs in a separate, isolated workflow that GitHub itself signs — making the provenance non-forgeable even by your own CI admins.
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
# .github/workflows/build.yml
name: Build and Publish
on:
push:
tags: ['v*']
permissions:
contents: read
jobs:
build:
outputs:
image: ${{ steps.image.outputs.image }}
digest: ${{ steps.build.outputs.digest }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set image name
id: image
run: echo "image=ghcr.io/${{ github.repository }}" >> "$GITHUB_OUTPUT"
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: build
uses: docker/build-push-action@v5
with:
push: true
tags: ${{ steps.image.outputs.image }}:${{ github.ref_name }}
# This job runs in a separate GitHub-controlled environment
# Its provenance is signed by GitHub's own key — SLSA Level 3
provenance:
needs: build
permissions:
actions: read
id-token: write
packages: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
with:
image: ${{ needs.build.outputs.image }}
digest: ${{ needs.build.outputs.digest }}
registry-username: ${{ github.actor }}
secrets:
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
Verifying SLSA Provenance with slsa-verifier
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
|
# Install slsa-verifier
curl -Lo slsa-verifier \
"https://github.com/slsa-framework/slsa-verifier/releases/latest/download/slsa-verifier-linux-amd64"
chmod +x slsa-verifier
sudo mv slsa-verifier /usr/local/bin/
# Verify a container image's provenance
slsa-verifier verify-image \
ghcr.io/myorg/myrepo:v1.2.3 \
--source-uri github.com/myorg/myrepo \
--source-tag v1.2.3
# Output if valid:
# PASSED: Verified SLSA provenance
# {
# "slsaVersion": "1.0",
# "builder": { "id": "https://github.com/slsa-framework/..." },
# "sourceURI": "github.com/myorg/myrepo",
# "sourceTag": "v1.2.3"
# }
# Verify a binary artifact
slsa-verifier verify-artifact myapp-linux-amd64 \
--provenance-path myapp-linux-amd64.intoto.jsonl \
--source-uri github.com/myorg/myrepo
|
Policy Enforcement with Sigstore’s Policy Controller
Having signatures and provenance is useful only if you enforce their presence. The Sigstore Policy Controller is a Kubernetes admission webhook that blocks unsigned or unverified images from running.
1
2
3
4
5
|
# Install with Helm
helm repo add sigstore https://sigstore.github.io/helm-charts
helm install policy-controller sigstore/policy-controller \
--namespace cosign-system \
--create-namespace
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# ClusterImagePolicy — require all images in the "production" namespace
# to be signed by our GitHub Actions workflow
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: require-signed-images
spec:
images:
- glob: "ghcr.io/myorg/**"
authorities:
- keyless:
url: https://fulcio.sigstore.dev
identities:
- issuer: https://token.actions.githubusercontent.com
subjectRegExp: "https://github.com/myorg/.*/.github/workflows/build.yml@refs/heads/main"
attestations:
- name: must-have-sbom
predicateType: https://spdx.dev/Document
- name: must-have-slsa
predicateType: https://slsa.dev/provenance/v0.2
|
1
2
3
4
5
6
7
|
# Label the namespace to enforce the policy
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
policy.sigstore.dev/include: "true"
|
Now any pod in the production namespace that tries to use an unsigned image gets rejected at admission time:
Error from server: admission webhook "policy.sigstore.dev" denied the request:
image ghcr.io/myorg/myrepo:latest is not signed
Dependency Review: Catching Problems at PR Time
GitHub’s dependency-review-action scans incoming changes to lock files and flags newly introduced vulnerable dependencies before they merge:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# .github/workflows/dependency-review.yml
name: Dependency Review
on: [pull_request]
permissions:
contents: read
pull-requests: write
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
allow-licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC
deny-licenses: GPL-3.0, AGPL-3.0 # block copyleft if that's your policy
comment-summary-in-pr: always
|
Renovate / Dependabot: Keeping Dependencies Fresh
Automation to keep dependencies updated means CVEs get patched promptly without manual effort:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
groups:
production-dependencies:
dependency-type: "production"
ignore:
- dependency-name: "lodash"
versions: ["4.x"] # pin specific versions if needed
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
|
Putting It All Together: A Complete Pipeline
A comprehensive supply chain security pipeline in GitHub Actions:
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
# .github/workflows/secure-build.yml
name: Secure Build Pipeline
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
permissions:
contents: read
id-token: write
packages: write
security-events: write
pull-requests: write
jobs:
# 1. Dependency review on PRs
dependency-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
# 2. Build
build:
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: build
uses: docker/build-push-action@v5
with:
push: ${{ github.event_name != 'pull_request' }}
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
# 3. Generate and attach SBOM
sbom:
needs: build
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: sigstore/cosign-installer@v3
- uses: anchore/sbom-action@v0
with:
image: ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}
artifact-name: sbom.spdx.json
output-file: sbom.spdx.json
- name: Scan SBOM for CVEs
uses: anchore/scan-action@v3
with:
sbom: sbom.spdx.json
fail-build: true
severity-cutoff: critical
output-format: sarif
output-file: grype.sarif
- name: Upload scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: grype.sarif
- name: Attach SBOM attestation
run: |
cosign attest --yes \
--predicate sbom.spdx.json \
--type spdxjson \
ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}
# 4. Sign the image
sign:
needs: [build, sbom]
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: sigstore/cosign-installer@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Sign image
run: |
cosign sign --yes \
ghcr.io/${{ github.repository }}@${{ needs.build.outputs.digest }}
# 5. SLSA provenance (separate workflow call for Level 3)
provenance:
needs: build
if: startsWith(github.ref, 'refs/tags/v')
permissions:
actions: read
id-token: write
packages: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
with:
image: ghcr.io/${{ github.repository }}
digest: ${{ needs.build.outputs.digest }}
registry-username: ${{ github.actor }}
secrets:
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
Quick Reference
| Tool |
Purpose |
Key Command |
| Syft |
Generate SBOMs |
syft image:latest -o cyclonedx-json |
| Grype |
Scan SBOMs for CVEs |
grype sbom:sbom.json --fail-on high |
| Cosign |
Sign/verify images & blobs |
cosign sign --yes image@digest |
| cosign verify |
Verify signature |
cosign verify --certificate-identity-regexp ... image |
| cosign attest |
Attach signed attestation |
cosign attest --predicate sbom.json --type spdxjson image |
| slsa-verifier |
Verify SLSA provenance |
slsa-verifier verify-image image --source-uri github.com/org/repo |
| Rekor |
Query transparency log |
rekor-cli search --email user@example.com |
| Policy Controller |
Enforce signatures in K8s |
ClusterImagePolicy CRD |
The Practical Roadmap
You don’t need to implement everything at once. A sensible progression:
Week 1 — Visibility: Add Syft to your CI pipeline. Generate SBOMs for your most important images and scan them with Grype. Just knowing what’s in your software is valuable before you act on it.
Week 2 — Scanning gates: Add --fail-on critical to Grype scans and block builds that ship critical CVEs. Add dependency-review to your PR workflow.
Week 3 — Signing: Add Cosign keyless signing to your main build workflow. Start verifying signatures in your deployment scripts.
Month 2 — Policy enforcement: Deploy the Sigstore Policy Controller to your staging cluster. Require signatures before images can run.
Month 3 — Provenance: Adopt the SLSA GitHub Generator for your release workflow. Attach SBOM attestations alongside signatures.
Each step compounds the previous. By the end you have: a complete inventory of everything you ship, CVE gating at build time, cryptographic proof of who built each artifact and from which exact source commit, and runtime enforcement that unsigned images simply cannot run. That’s a supply chain you can actually defend.
Comments