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.
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.
Supply chain security gets vague fast. To stay concrete, the three scenarios we design against:
our-org/api:v3.4.1 that wasn't built by our CI. Detected by provenance attestation + verification at deploy.We can't prevent all of these. We can ensure that when one happens, we know fast and have the data to investigate.
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:
What the SBOM doesn't do: prevent the compromised-dependency scenario at build time. It just makes the response faster after.
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:
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.
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:
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.
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 SBOM + signing setup pays off most when a CVE drops:
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.
For reference, the current stack:
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.
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.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
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.
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.
Explore more articles in this category
GitHub Actions OIDC gets all the attention, but GitLab, Buildkite, and CircleCI issue the same signed tokens. Here's how to trust them without opening a hole.
How pods can talk to AWS, GCP, and Azure with no static keys — using audience-bound projected ServiceAccount tokens and the cluster OIDC issuer.
Our best engineer quit citing on-call. We rebuilt the whole thing: saner rotations, runbooks that actually help at 3am, and escalation that doesn't punish asking for help.
Evergreen posts worth revisiting.