TecLeads TecLeads Blog
2026-07-17 · 9 min read

SBOMs Are Receipts, Not Supply Chain Security

Developer typing on a backlit keyboard in a dark room
devsecopssupply-chainsbomcontainer-securitypolicy-as-code

A build passed. The image was pushed. The deployment rolled out cleanly. Then someone on the incident bridge asked the question that makes a room go quiet: what exactly is running in production, and where did it come from?

Not what is in Git. Not what the Dockerfile says. Not what the pipeline was supposed to do. What is actually inside the artifact that Kubernetes pulled, who built it, from which source revision, with which dependencies, and whether anyone can prove that chain without hand waving.

That is where a lot of supply chain security programs get uncomfortable. They have scanners. They have dashboards. They may even have an SBOM stored somewhere. But when the artifact is already in a cluster and a vulnerability lands, the process turns into Slack archaeology.

SBOMs, signing, and provenance are not three separate checkbox projects. They are one control plane for answering a simple question: should this thing be allowed to run?

The SBOM is the receipt, not the lock

An SBOM is useful because it gives you a machine readable inventory of software components. That matters for SCA, compliance, incident response, and the dull but necessary work of answering vendor questionnaires without making things up.

But an SBOM by itself does not secure anything. It is a receipt. If you generate it on a developer laptop, upload it manually, and never bind it to the built artifact, it tells you very little about production.

The useful version is generated during the build, tied to the immutable image digest, and stored where other systems can query it. For container images, I usually want the SBOM attached to the image, not floating in a folder called security-reports-final-v3.

A practical starting point looks like this:

IMAGE=registry.example.com/payments/api
TAG=$(git rev-parse --short HEAD)

docker build -t ${IMAGE}:${TAG} .
docker push ${IMAGE}:${TAG}

DIGEST=$(crane digest ${IMAGE}:${TAG})
syft ${IMAGE}@${DIGEST} -o spdx-json > sbom.spdx.json
cosign attach sbom --sbom sbom.spdx.json ${IMAGE}@${DIGEST}

That gives you an SBOM connected to the artifact by digest. The digest part matters. Tags are labels. Digests are identities.

If your incident process starts with a CVE and a package name, the SBOM lets you ask a direct question: which images contain this package, and where are they deployed? Without that inventory, teams fall back to scanning everything again under pressure. That works badly at 2am, especially when old images still sit in registries and forgotten workloads still run in non production clusters that are one firewall rule away from being production.

Signing tells you who touched the thing

Container signing is where the conversation gets sharper. If the SBOM says what is in the artifact, signing says whether the artifact came from a trusted build path.

Cosign is the common tool here, and for good reason. It works with OCI registries and supports key based and keyless signing. Keyless signing with OIDC is attractive because long lived signing keys are another secret to lose, rotate, and explain during an audit.

A simple keyless signing step in CI can look like this:

cosign sign --yes ${IMAGE}@${DIGEST}

That command is short. The hard part is the surrounding discipline.

You need to decide which identity is allowed to sign. A developer shell should not be equivalent to the protected main branch build. A nightly experiment should not produce artifacts that your production cluster trusts. A forked pull request should not get a signature that looks like a release.

In GitHub Actions, for example, you would normally pair signing with restricted permissions and branch rules:

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

on:
 push:
 branches:
 - main

Then your admission policy can require signatures from that workflow identity. Not from anyone with registry push access. Not from the image tag that says stable. From the build identity you actually trust.

This is where devsecops either becomes real or stays theater. Security baked into every commit, build, and deploy means the pipeline has to make security decisions with evidence, not vibes. SAST and SCA are part of that. Secrets scanning is part of that. IaC policy and container hardening are part of that. But artifact identity is the spine. Without it, everything downstream is guessing.

Provenance answers the question nobody likes asking

Provenance is the build story in a form a machine can verify. Which repository? Which commit? Which workflow? Which builder? Were the inputs pinned? Did the build run in the expected environment?

SLSA provenance gives teams a shared model for this. You do not need to start by chasing the highest SLSA level. Start by producing provenance that ties the artifact to source and build identity, then enforce the claims that matter for your risk.

Cosign can attest provenance:

cosign attest --yes \
 --predicate provenance.json \
 --type slsaprovenance \
 ${IMAGE}@${DIGEST}

The content of provenance.json should not be ornamental. If nobody reads or enforces it, it is just another artifact in the evidence pile. The point is to make deployment systems ask useful questions.

Was this built from the expected repo? Was the commit on a protected branch? Did the build use the approved workflow? Was the image scanned after build? Was the base image from an approved registry? Did the Dockerfile run as root? Those questions belong in policy as code, because humans are inconsistent and Kubernetes is very literal.

Admission control is where the argument becomes real

A supply chain control that only reports after deployment is helpful for cleanup. It is not a gate.

Kubernetes admission control is where you turn SBOMs, signatures, and provenance into container security decisions. Kyverno and OPA Gatekeeper are both common choices. Pick the one your team can operate, write tests for, and debug when an urgent release gets blocked.

A Kyverno policy requiring signed images might look like this:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
 name: require-signed-images
spec:
 validationFailureAction: Enforce
 rules:
 - name: verify-cosign-signature
 match:
 any:
 - resources:
 kinds:
 - Pod
 verifyImages:
 - imageReferences:
 - "registry.example.com/*"
 attestors:
 - entries:
 - keyless:
 subject: "https://github.com/example/platform/.github/workflows/release.yml@refs/heads/main"
 issuer: "https://token.actions.githubusercontent.com"

That is not a finished enterprise policy. It is the shape of a useful one. It says production does not trust a registry path alone. It trusts an image that was signed by the right workflow from the right place.

From there, add checks carefully. Require images by digest in higher environments. Block mutable tags like latest. Require non root containers. Reject images from unapproved registries. Check for required attestations before allowing deployment.

Do not try to boil the ocean in one policy bundle. That is how teams create a wall of exceptions and then stop believing in the system. Start with high signal controls that break risky behavior on purpose.

The messy part is ownership

The tooling is not the hardest part. Ownership is.

Security teams can define policy, but platform teams usually run the admission controllers. Application teams own Dockerfiles and dependency choices. Compliance teams need evidence. Incident responders need search. If those groups only meet after a finding lands, the system will be full of gaps.

A sane operating model makes the pipeline produce evidence automatically and makes the platform enforce only what the organization is ready to support. That last phrase matters. If you enforce signed images, you need a documented break glass path. If you require SBOMs, you need a place to store and query them. If you block critical vulnerabilities, you need rules for exceptions, expiry, and owner approval.

Exception handling is where many programs quietly rot. A waiver with no expiry is not an exception. It is a new standard with worse documentation.

For compliance, this approach is also cleaner. Instead of screenshots of scanner dashboards, you can show signed artifacts, attached SBOMs, provenance attestations, admission policies, and audit logs that prove what was allowed to deploy. That evidence is much harder to argue with, and much less painful to gather.

What I would wire first

If you are starting from a normal pipeline, not a conference demo, I would do this in order.

First, build images in CI only and push by digest. Stop treating laptops as release infrastructure.

Second, generate an SBOM during the build and attach it to the image digest. Use Syft, Trivy, or the tool your platform already supports, but make the output queryable.

Third, sign release images with Cosign from a protected CI workflow. Keep the signing identity narrow.

Fourth, produce provenance for release builds. Do not wait for perfect metadata before you start.

Fifth, enforce one admission rule in report mode, then enforcement. Signed images are usually the cleanest first gate.

After that, layer in vulnerability policy, base image rules, runtime hardening, IaC checks, and secret detection as part of the same devsecops flow. The goal is not to collect tools. The goal is to make unsafe paths harder than safe ones.

If you only do one thing this week

Pick one production service and trace one deployed container image back to source.

Find the running pod. Get the image digest. Confirm whether there is an SBOM for that digest. Check whether the image is signed. Look for provenance that names the repo, commit, workflow, and builder. Then ask whether your cluster would reject the same image if it came from an untrusted path.

That exercise usually reveals the real state of the program faster than a strategy deck.

SBOMs are useful. Signing is useful. Provenance is useful. They become supply chain security when they are bound to the artifact and enforced at the point of deployment. Until then, they are paperwork with better file formats.


If this is on your plate, TecLeads does exactly this as part of our DevSecOps work. If you'd like a second pair of eyes on your setup, book a 30-minute call or explore what we do.

📍 Tech Pulse · today's quick question 🟢 Level: Basic Networking

What does a VPN mainly provide?

Pick an answer to see how other engineers voted.

Want a hand with this?

TecLeads helps engineering teams ship faster and more securely.

Book a 30-minute call

← All posts