Supply Chain Security — SBOMs, Attestation, and What to Actually Verify
SBOMs and signed attestations sound like checkboxes until you need to answer "did this artifact come from our pipeline?" The minimum viable supply-chain story we run.
Key takeaways
SBOMs and signed attestations sound like checkboxes until you need to answer "did this artifact come from our pipeline?" The minimum viable supply-chain story we run.
On this page
Supply Chain Security: SBOMs, Attestation, and What to Actually Verify
For a long time, "supply chain security" was a buzzword that meant "we scan our images." After log4shell, the XZ backdoor, and several smaller incidents, the conversation got serious. Today we generate SBOMs at build, sign artifacts with Sigstore, and verify provenance at deploy. None of this is exotic — most teams could land the same baseline in a couple of weeks. This post is what we actually run, what each piece prevents, and what we deliberately skipped.
The threat model in three scenarios#
Supply chain security gets vague fast. To stay concrete, the three scenarios we design against:
- Someone tampered with the artifact in transit. Our CI built image X with hash A; somewhere between the registry and production, it became image X with hash B. Detected by signing + verification.
- Someone pushed a malicious artifact under our name. Attacker got access to the registry and uploaded
our-org/api:v3.4.1that wasn't built by our CI. Detected by provenance attestation + verification at deploy. - Our build pulled in a compromised dependency. Upstream npm/PyPI package got hijacked; the build succeeded but the artifact contains malicious code. Detected (after the fact) by SBOM-driven advisory matching.
We can't prevent all of these. We can ensure that when one happens, we know fast and have the data to investigate.
SBOM at build time#
A Software Bill of Materials lists every package + version that went into the artifact. We generate one per build using Syft:
syft <image-ref> -o spdx-json > sbom.json
The SBOM is attached as an OCI artifact alongside the image (so it travels with the image to the registry). Anyone who pulls the image can pull the SBOM too. The format we use is SPDX JSON — widely supported by downstream tooling.
What the SBOM enables:
- Vulnerability response at speed. When the next log4shell-class CVE drops, "do we use the affected package, and where?" is a query against SBOMs across all our artifacts. Minutes, not days.
- License compliance. Auditing dependencies for license terms is a SBOM query.
- Drift detection. Comparing SBOMs across builds shows when a dependency silently updated.
What the SBOM doesn't do: prevent the compromised-dependency scenario at build time. It just makes the response faster after.
Signed attestations#
For every artifact we produce, the CI also produces a signed attestation: a statement of "this artifact was built by this pipeline run, from this commit, by this identity." Signing uses Sigstore (specifically cosign), which is keyless — the signature is tied to the CI's OIDC identity, not a long-lived key we have to manage.
# At build time, in CI
cosign sign-blob --bundle attestation.bundle --certificate-identity-regexp \
"https://github.com/company/.+" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--yes \
<image-digest>
The attestation includes provenance fields (workflow name, commit SHA, builder identity) following the SLSA framework. The bundle gets pushed to the same OCI registry as the image.
What this enables:
- Verify the artifact came from our pipeline. Before deploying, we check the signature. If it's missing or doesn't match expected pipeline identity, refuse to deploy.
- Tamper detection. A modified image won't match the signed digest.
- Audit trail. Every artifact in production traces back to a specific pipeline run and commit.
What it doesn't do: certify that the pipeline itself is uncompromised. If an attacker gets into the CI, they can sign artifacts that look legitimate. Pipeline integrity is a separate problem.
Verification at deploy#
The pieces above are useless without enforcement. Two enforcement points:
At registry pull. When the Kubernetes runtime pulls an image, an admission controller (Kyverno or Gatekeeper) checks the image's signature. Unsigned images, or signatures from unexpected identities, get rejected.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
spec:
validationFailureAction: Enforce
rules:
- name: verify-our-images
match:
resources:
kinds: ["Pod"]
verifyImages:
- imageReferences:
- "our-registry.io/company/*"
attestors:
- entries:
- keyless:
subject: "https://github.com/company/.+"
issuer: "https://token.actions.githubusercontent.com"
The first version of this policy is the most enlightening one. We deployed it in audit-only mode (logging violations but not blocking). Within a week we'd found:
- An old image we'd manually built and pushed years ago, unsigned.
- A third-party image we used directly (their signatures didn't match our policy).
- A test image someone pushed during debugging, still in use somewhere.
Fixed each, then switched to enforce mode. Now any unsigned image in our registry namespace fails admission.
At CI for downstream pipelines. If pipeline A produces an artifact that pipeline B consumes (e.g. a base image), B verifies A's signature before using it. Catches the case where the base image got tampered with between A and B.
What we don't do#
A few patterns that look reasonable and didn't earn a place:
Verify signatures on every container we run. We verify our own; we don't verify every third-party image (the ecosystem isn't there yet). Most images we use are from well-known projects whose maintainers don't sign with our trust roots. Pragmatically, we treat third-party images as a separate trust decision: pin to specific tags, scan for CVEs, but don't require signatures.
Full SLSA Level 3. SLSA defines four levels of supply chain hardening. Level 3 requires hermetic builds (no network during build, all deps prefetched and signed) and source-tracking that's substantially more elaborate than what we run. We're at roughly Level 2 (provenance attestations, signed artifacts, no privileged build runners) and that catches the bulk of the risk for our threat model.
Reproducible builds. The promise: anyone can rebuild the artifact byte-for-byte from source and verify it matches. Real but hard. Practical for some pure-Go binaries; very hard for typical Node/Python with mixed deps. Not on our roadmap.
Vendor-attestation registries. Some products promise centralized registries of "trusted" artifact attestations. We rely on Sigstore's public transparency log (Rekor) instead — it's open, auditable, and the standard the ecosystem is converging on.
The vulnerability response loop#
The SBOM + signing setup pays off most when a CVE drops:
- Identify affected artifacts. Query SBOMs across all our images: which contain the vulnerable package version?
- Trace to deployments. Which deployments use those images? Tooling: cluster scan for current images vs SBOM index.
- Prioritize. Customer-facing first; internal next.
- Patch. Bump the dependency, rebuild, verify, deploy.
- Confirm. Re-scan deployed images, confirm new versions are running.
Steps 1 and 2 are minutes with this infrastructure. Without it, they're "everyone search the codebases by hand for a week." That's the actual ROI.
Stack we run#
For reference, the current stack:
- SBOM: Syft, generating SPDX JSON, attached as OCI artifact alongside the image.
- Signing: Cosign with Sigstore (keyless via GitHub OIDC).
- Vulnerability scanning: Trivy in CI (blocks high/critical CVEs without exception).
- Admission control: Kyverno verifying signatures at pod admission.
- Registry: AWS ECR.
- CI: GitHub Actions.
Total cost (operational): a couple of hours per week for vulnerability triage and exception management. The setup itself was about two weeks of focused work.
What to read next#
- Container security scanning — protecting Docker images — the upstream scanning that runs alongside this
- Handling vulnerabilities in production — the discipline around responding when CVEs hit
- Cloud security best practices: securing AWS infrastructure — broader infra security
- Cross-cloud identity federation patterns — the CI identity used for keyless signing
Supply chain security feels like a Big Topic until you break it into pieces. SBOM at build. Sign with provenance. Verify at deploy. Each piece prevents a specific scenario. The full setup is a couple of weeks of work and pays for itself the first time you respond to a major CVE.
Stay Updated
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
Edge Databases for Low-Latency Apps — D1, Turso, Neon Serverless
Edge compute is useless without an edge data layer. Three serverless databases that put data within ms of your edge functions, with the tradeoffs that aren't on the marketing pages.
Kubernetes Resource Requests — Right-Sizing Without Guessing
Bad resource requests waste money or trigger OOMs. The methodology we use to right-size requests based on actual usage, and the gotchas the autoscalers don't fix.
More from DevOps
Explore more articles in this category
WebAssembly Use Cases: Where Wasm Actually Shines in 2026
A practitioner's tour of where WebAssembly earns its keep in 2026, from browser apps to edge compute, plus the places it still doesn't fit.
Go vs Python Performance: What the Difference Really Is
A practical look at why Go usually outruns Python at runtime, where Python holds its own, and how to pick per workload.
What Is WebAssembly? A Practical Introduction
A grounded look at WebAssembly, the portable binary format that runs code at near-native speed inside a secure sandbox.
You might have missed
Evergreen posts worth revisiting.