
Supply Chain
OSS Supply Chain Security in 2026: From SBOM Generation to Sigstore Signing to Admission Enforcement — a Production Pipeline
A complete guide to building a Kubernetes supply chain security pipeline: SBOM generation with Syft, SLSA L3 provenance via slsa-github-generator, Sigstore keyless signing, and Kyverno admission enforcement.
The 2026 Black Duck OSSRA report is not comforting reading. Open source vulnerabilities have roughly doubled over the prior cycle, driven in part by the explosion in AI-generated code that pulls in transitive dependencies without a second thought. The attack surface of the average containerized workload now extends through hundreds of third-party packages, their upstream suppliers, the build pipelines that assembled them, and the registries that stored the result. Any one of those links can be compromised — and historically, the most sophisticated attacks have not touched your application code at all. They have touched the packaging layer upstream of it.
The industry's response has been to mandate Software Bills of Materials. NIS2 requires supply chain security as a hard audit control for essential entities. DORA makes ICT third-party risk a first-class regulatory obligation. The OpenSSF, backed by US federal cybersecurity priorities, has been pushing SBOM generation and artifact signing as the supply chain baseline. All of this is correct. It is also woefully incomplete. Generating an SBOM and dropping it in an artifact store is not a security control. It is a compliance artifact — useful evidence for an auditor, useless at stopping a malicious image from reaching production.
This article builds the complete trust chain, the one that actually closes: source commit → CI build → SLSA L3 provenance attestation → Cosign keyless signing → Rekor transparency log → Kubernetes admission enforcement → runtime drift detection. We will show the real commands, the real YAML, and the real gaps that most "supply chain security" implementations leave open. The goal is a pipeline where a container that was not built from your source, by your CI, in the last known-good state, cannot reach production — not as a policy aspiration but as a cryptographic guarantee enforced at the admission gate. That guarantee is strongest when you control the forge and CI doing the building in the first place — the case for owning the pipeline end to end with Forgejo, Woodpecker, and Zot. If you remain on GitHub as the forge, self-hosted runners with ARC let you own the execution layer where signing and SBOM generation happen.
Why Your SBOM Is a JFrog Artifact, Not a Security Control
The phrase "we have SBOMs" has become the supply chain equivalent of "we have a firewall." Both statements are true of almost every breached organization. An SBOM — a structured list of the components in a software artifact — is valuable exactly as far as the consumption pipeline that reads it. Without that pipeline, it is a JSON file sitting in a registry, consulted by nobody, triggering nothing.
The consumption question has three parts. First: who scans the SBOM for known CVEs, and how often? New vulnerabilities are published continuously. An SBOM generated at build time is stale by the next morning. A one-time scan at build time catches what was known then — it does nothing about the CVE-2026-XXXXX published forty-eight hours after deployment. Second: what happens when a critical CVE matches a transitive dependency? If the answer is "an email goes to someone's inbox," your mean time to remediation is measured in weeks, not hours. Third: how does the SBOM connect to admission enforcement? If a vulnerable image is blocked only on the next deployment and the running pod is untouched, you have a detection, not a control.
The open-source-as-method discipline here means treating your supply chain pipeline exactly as you would treat any other critical system: with continuous observability, automated remediation loops, and audit-grade evidence at every stage — not a batch job that runs once a quarter before an audit.
The Complete Trust Chain: Build Provenance, Artifact Signing, Admission Enforcement
The pipeline has six distinct stages, each producing an artifact that the next stage verifies. Source commit triggers a CI build. The build produces a container image and two attestations: a SLSA provenance document (who built this, from what source, with what inputs) and a CycloneDX SBOM (what is inside it). Both attestations are signed with Cosign in keyless mode — no private key to manage, no HSM to operate. The signing event is logged to Rekor, an append-only transparency log, producing an inclusion proof. The signed image, signature, and SBOM are pushed to the OCI registry as a bundle of OCI referrers. At deploy time, the Kubernetes admission controller — Kyverno or the Sigstore Policy Controller — pulls the image reference, queries the registry for the signature referrer, verifies the signature against the Rekor log and the expected OIDC issuer, and either admits or denies the pod. Nothing in this chain requires a shared secret. The trust anchor is the OIDC identity of the CI runner itself.
Generating SBOMs at Scale: Syft, Trivy, and What to Do with the Output
syft (Anchore) and trivy (Aqua Security) are the two production-grade SBOM generators in the CNCF ecosystem. syft produces richer package metadata for multi-language images and integrates cleanly with grype for CVE matching. trivy bundles SBOM generation with its own scanner in a single binary, which simplifies pipeline configuration. Both support CycloneDX 1.5 and SPDX 2.3. For most pipelines, generate CycloneDX — it is the format Dependency-Track and most admission-time SBOM policies understand.
# Generate a CycloneDX SBOM for a container image using syft
# Run after 'docker build' or 'ko build' produces the image
IMAGE="ghcr.io/your-org/your-service:${GIT_SHA}"
syft "${IMAGE}" \
--output cyclonedx-json=sbom.cdx.json \
--select-catalogers "+cargo-auditable-binary-cataloger" \
--source-name your-service \
--source-version "${GIT_SHA}"
# Attach the SBOM to the image as an OCI referrer (requires ORAS or cosign)
cosign attach sbom \
--sbom sbom.cdx.json \
--type cyclonedx \
"${IMAGE}"
# Verify the SBOM is reachable via the referrer API
oras discover "${IMAGE}" --artifact-type application/vnd.cyclonedx+jsonThe cosign attach sbom command stores the SBOM as an OCI artifact in the same registry namespace as the image, linked by the image digest. This is the correct storage model: the SBOM travels with the image, not in a separate artifact system that can fall out of sync. Any tool that can resolve OCI referrers can pull the SBOM without additional credentials or index lookups.
The second tool in the consumption loop is grype, which takes a Syft SBOM and matches it against vulnerability databases (NVD, GitHub Advisory, OSV). Run grype sbom:./sbom.cdx.json in CI to fail the build on critical findings, and run it again on a schedule against the SBOM stored in the registry to catch vulnerabilities disclosed after the build. This is the loop-close that most teams miss: building clean does not mean staying clean.
SLSA Levels in Practice: Reaching Level 3 with slsa-github-generator in an Afternoon
SLSA (Supply chain Levels for Software Artifacts) is an OpenSSF framework that defines a graduated set of guarantees about build provenance. The levels are not arbitrary — each one is designed to close a specific class of attack. SLSA L1 establishes that the build is scripted (no manual steps that could inject artifacts). SLSA L2 adds the requirement that the build runs on a hosted build service and that the provenance is signed by the build service itself, not by the developer — meaning a compromised developer workstation cannot forge the provenance. SLSA L3 adds isolation and ephemerality requirements that prevent even a compromised build platform from tampering with provenance.
For internal services shipping to your own Kubernetes clusters, L3 is achievable in one workflow addition — and this pipeline demonstrates it. The slsa-github-generator project's generator_container_slsa3.yml reusable workflow produces genuine SLSA L3 provenance: it runs in an isolated, ephemeral environment that the repository owner cannot tamper with, making the provenance non-forgeable even by a compromised CI configuration. GitHub's own actions/attest-build-provenance action (generally available as of late 2024) is the simpler path: it produces L2 provenance signed by GitHub's OIDC issuer — a meaningful baseline that closes the developer-workstation compromise class and requires no additional setup beyond a single workflow step. Choose L3 (slsa-github-generator) for regulated, externally-distributed, or high-assurance artifacts; L2 (actions/attest-build-provenance) for internal services where an afternoon's setup budget is the constraint.
# .github/workflows/build-and-attest.yml
# Produces SLSA L3 provenance via the slsa-github-generator reusable workflow
# Requires: contents:write and id-token:write permissions at the workflow level
name: Build, Attest, Sign
on:
push:
branches: [main]
permissions:
contents: write
id-token: write # Required for Sigstore OIDC signing
packages: write # Required for GHCR push
attestations: write # Required for GitHub artifact attestations
jobs:
build:
runs-on: ubuntu-latest
outputs:
image: ${{ steps.build.outputs.image }}
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- name: Build and push image
id: build
uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
# Outputs the digest as a step output
outputs: type=image,name=ghcr.io/${{ github.repository }},push-by-digest=true
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
format: cyclonedx-json
artifact-name: sbom.cdx.json
upload-artifact: true
provenance:
needs: [build]
permissions:
actions: read
id-token: write
packages: write
# slsa-github-generator reusable workflow — produces L3 signed provenance
uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]
with:
image: ghcr.io/${{ github.repository }}
digest: ${{ needs.build.outputs.digest }}
registry-username: ${{ github.actor }}
secrets:
registry-password: ${{ secrets.GITHUB_TOKEN }}Sigstore, Cosign, and the Transparency Log: Keyless Signing Without the Key Management Tax
The traditional objection to artifact signing was operational: you need a private key, you need to protect it, you need to rotate it, you need to trust everyone who has access to it, and if it leaks you need to revoke and re-sign everything. Sigstore's keyless signing model eliminates all of this. The trust anchor is not a long-lived private key — it is the OIDC identity of the process that performs the signing. In a GitHub Actions workflow, that identity is the workflow's OIDC token, issued by GitHub and bound to the specific workflow run, repository, branch, and commit SHA. Cosign exchanges this OIDC token with Fulcio (Sigstore's certificate authority) for a short-lived signing certificate (valid for ten minutes), signs the artifact with it, and logs the signing event to Rekor. The certificate expires immediately after use. There is no key to leak.
# Keyless signing with cosign in a GitHub Actions environment
# COSIGN_EXPERIMENTAL is no longer required as of cosign 2.x
IMAGE_DIGEST="ghcr.io/your-org/your-service@sha256:abc123..."
# Sign the image — cosign auto-detects GitHub OIDC in Actions environment
cosign sign \
--yes \
"${IMAGE_DIGEST}"
# Verify signature from outside CI — use the expected OIDC issuer + subject
cosign verify \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--certificate-identity-regexp "^https://github.com/your-org/your-repo/.github/workflows/build-and-attest.yml@refs/heads/main$" \
"${IMAGE_DIGEST}" \
| jq '.[0] | {issuer: .optional.Issuer, subject: .optional.Subject, repo: .optional["github-workflow-repository"]}'
# Verify SLSA provenance attestation
cosign verify-attestation \
--type slsaprovenance \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--certificate-identity "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.0.0" \
"${IMAGE_DIGEST}" \
| jq -r '.payload' | base64 -d | jq '.predicate.buildDefinition'Rekor, the Sigstore transparency log, plays a role analogous to Certificate Transparency in the TLS ecosystem. Every signing event is logged as an immutable entry. Verifiers can check that the log inclusion proof is valid — confirming that the signature was recorded at a specific time and has not been tampered with since. More importantly, Rekor enables monitoring: you can subscribe to entries matching your OIDC identity and get alerted if anything is signed with your workflow's identity that you did not authorize. This is the audit trail that closes the circle between signing and detection of unauthorized signing.
The question is not whether your images are signed. The question is whether you will notice when something is signed with your identity that you did not authorize — and whether your admission controller will reject it before it runs.
Enforcing at the Gate: Sigstore Policy Controller vs. Kyverno Image Verification
Two mature options exist for Kubernetes admission-time image verification: the Sigstore Policy Controller (a CNCF project) and Kyverno's verifyImages rule. Both are production-grade; the choice is architectural. If you already run Kyverno for general policy enforcement — which you should, as detailed in our capabilities documentation — add image verification to your existing Kyverno cluster policy rather than operating a second admission webhook. If you are greenfield and supply chain verification is your only admission requirement, the Sigstore Policy Controller is a lighter installation.
# Kyverno ClusterPolicy: enforce signed images with SLSA provenance
# Requires Kyverno 1.10+ with Sigstore verification enabled
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
annotations:
policies.kyverno.io/title: Require Signed Container Images
policies.kyverno.io/description: >-
Blocks pods whose containers reference images that are not signed by
the expected GitHub Actions OIDC issuer on the main branch. Verification
includes the Cosign keyless signature and the SLSA L3 provenance attestation.
spec:
validationFailureAction: Enforce
background: false # Admission-time only — do not scan running pods
rules:
- name: verify-image-signature
match:
any:
- resources:
kinds: [Pod]
namespaces: [production, staging]
verifyImages:
- imageReferences:
- "ghcr.io/your-org/*"
attestors:
- entries:
- keyless:
subject: "https://github.com/your-org/*/.github/workflows/build-and-attest.yml@refs/heads/main"
issuer: "https://token.actions.githubusercontent.com"
rekor:
url: https://rekor.sigstore.dev
attestations:
- predicateType: https://slsa.dev/provenance/v1
attestors:
- entries:
- keyless:
subject: "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.0.0"
issuer: "https://token.actions.githubusercontent.com"
conditions:
- all:
- key: "{{ buildDefinition.buildType }}"
operator: Equals
value: "https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1"
- key: "{{ buildDefinition.externalParameters.workflow.ref }}"
operator: Equals
value: "refs/heads/main"The validationFailureAction: Enforce mode is non-negotiable in production. Audit mode logs violations without blocking — it is useful for a migration period, but an admission policy that does not block is not an admission policy. Set a calendar reminder: if your supply chain enforcement is in Audit mode, it has a deadline to flip to Enforce. The thesis behind treating infrastructure security as an engineering practice rather than a compliance posture is exactly this: controls that observe but do not prevent are detection, not protection.
The Drift Problem: Runtime Verification After Images Are Already Running
Admission enforcement is necessary but not sufficient. A pod that passed admission six weeks ago may now be running an image with a known-critical vulnerability discovered yesterday. The signed image was valid when admitted — it remains signed, and Kyverno will not retroactively evict the running pod. This is the drift problem: your running fleet diverges from your current security posture as new vulnerabilities are disclosed.
Runtime drift detection operates on two distinct layers. The first is vulnerability drift: continuously re-scanning the SBOMs of running images against updated CVE databases. This is where grype and Dependency-Track play their role — not just at build time, but on a scheduled basis against the full set of image digests currently running in the cluster. kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.spec.containers[*].image}{"\n"}{end}' | sort -u gives you the live set; feed each unique digest to grype sbom:$(cosign download sbom $IMAGE 2>/dev/null) to get the current vulnerability state.
The second layer is binary drift: detecting when the contents of a running container differ from the image it was launched from. This is the class of attack that follows a container escape or a supply chain compromise that targets the container runtime rather than the image build. KubeVigil's supply-chain checks include image digest verification against running pods — confirming that what Kubernetes reports as the running image digest matches the expected digest from the registry, and flagging any divergence for investigation. Falco's container drift rules cover the file-system mutation case: detecting writes to the container root filesystem that would indicate post-admission tampering.
Wiring It to NIS2 and DORA Supply Chain Audit Evidence
NIS2 Article 21 requires essential entities to implement measures addressing supply chain security, specifically the security relationship between each entity and its direct suppliers. DORA Article 28 requires financial entities to manage ICT third-party risk, including software components sourced from external parties. Both regulations require *demonstrable* controls — not a policy document, but evidence that the control was active and functioning at audit time. This is where the pipeline built above has a specific advantage: every stage produces a cryptographically verifiable artifact with a timestamp and an immutable log entry.
The audit evidence package for a supply chain control under NIS2/DORA should contain: the SBOM for each production image (stored as OCI referrers, retrievable by digest), the Cosign signature and verification log (from Rekor), the SLSA provenance attestation (asserting which source commit and which CI workflow produced the image), and the Kyverno admission policy and its PolicyReport showing which images were admitted and which were rejected over the audit period. This is a complete chain of custody from source to running workload, with no trust gaps that an auditor can probe.
- SBOM inventory: CycloneDX JSON per image digest, stored as OCI referrers, retrievable with
cosign download sbomor ORAS CLI. Satisfies the "know what you are running" requirement. - Vulnerability scan results: Grype JSON reports per image, timestamped, stored in your artifact system. Demonstrates continuous monitoring against current CVE databases.
- Signing provenance: Rekor log entries for every signed image, retrievable by log index or inclusion proof. Provides non-repudiation — the signing event cannot be back-dated or forged.
- SLSA attestations: Provenance predicates asserting build origin, source digest, and builder identity. Satisfies the build integrity requirement and closes the attribution gap between source and artifact.
- Admission enforcement record: Kyverno
ClusterAdmissionReportandAdmissionReportCRDs. Shows which images were verified and admitted, and which were blocked — the evidence that the control was active. - Drift detection alerts: Grype scheduled scan results and any CVE alerts triggered against running images. Demonstrates continuous compliance, not point-in-time.
The audit-grade rigor principle that runs through every Stribog engagement is exactly this: controls produce evidence by design, not as an afterthought before the audit. If you have to reconstruct evidence after the fact, the control was not audit-grade — it was a process someone remembered to follow, which means someone else will eventually forget.
The Pipeline Is a System, Not a Checklist
The individual components of this pipeline — syft, cosign, slsa-github-generator, grype, Kyverno — are all mature, well-documented, and freely available. The reason most organizations have not implemented the full chain is not tooling maturity; it is the systems thinking required to wire them together into a coherent control, rather than treating each tool as an independent project. An SBOM project that never connects to a CVE scanner. A signing project that never connects to admission enforcement. A vulnerability scanner that fires alerts that go nowhere. These are the failure modes in practice.
The open-source-as-method discipline means treating this pipeline as you would any production system: version-controlled configuration, tested policy changes, monitored operation, and alert paths that reach on-call engineers rather than ticketing queues that drain on a quarterly schedule. The supply chain is as much a production system as the application it delivers. Operate it accordingly.
The complete pipeline described here — build → attest → sign → log → enforce → detect — is not aspirational. The tooling is stable, the documentation is mature, and the operational overhead is low once the initial wiring is done. The cost of not doing it is increasingly concrete: regulatory exposure under NIS2 and DORA, expanding CVE surface from AI-generated dependency graphs, and the reputational and operational cost of a supply chain incident that a signed admission policy would have caught at the gate. The pipeline is a system. Build it, operate it, and trust it — because your admission controller is the one gate in the entire chain that will not fail silently.
§FAQ/Common questions
Frequently asked
What is the difference between SLSA Level 1, 2, and 3, and which should I target?
SLSA L1 requires a scripted build with generated (but unauthenticated) provenance. SLSA L2 adds the requirement that provenance is signed by the build service itself, making it non-forgeable by the developer — achievable with GitHub's `actions/attest-build-provenance` in a single step. SLSA L3 requires an isolated, ephemeral build environment that the build platform itself cannot tamper with — this is what `slsa-github-generator`'s `generator_container_slsa3.yml` workflow provides. The pipeline in this article achieves L3. For most regulated and externally-distributed artifacts, L3 is the appropriate target; `actions/attest-build-provenance` (L2) is the right starting point when the goal is a quick baseline with minimal setup overhead.
Does Sigstore keyless signing require storing a private key anywhere?
No. Cosign keyless signing uses short-lived certificates issued by Sigstore's Fulcio CA in exchange for a valid OIDC token from your CI environment (GitHub Actions, GitLab CI, Tekton, etc.). The certificate is valid for ten minutes and is used only for a single signing operation. There is no long-lived private key to protect, rotate, or revoke. The trust anchor is the OIDC issuer URL and the identity claim (workflow path, repository, branch ref) — pin these in your admission policy for the strongest verification guarantees.
Should I use the Sigstore Policy Controller or Kyverno for image verification?
If you already run Kyverno for policy enforcement — which is the recommended baseline for any serious Kubernetes security posture — add image verification via Kyverno's verifyImages rule rather than operating a second admission webhook. Kyverno 1.10+ supports full Cosign keyless verification including SLSA L3 provenance attestation conditions as shown in this article. The Sigstore Policy Controller is a good choice for greenfield clusters where supply chain verification is the only admission requirement; it is lighter to install but provides a narrower policy surface.
What happens to pods already running when a critical CVE is found in a dependency?
Admission enforcement is point-in-time — it verifies images at pod creation, not continuously during the pod's lifetime. Running pods are not automatically evicted when a new CVE is discovered. The remediation path is: Grype's scheduled scan detects the CVE in the SBOM of the running image, fires an alert, triggers a rebuild workflow producing a new signed image, and the new image is deployed via your normal rollout process. If you version-pin your admission policy to require images newer than a specific date, the old image will be blocked on the next restart or re-deployment. Runtime tools like Falco and KubeVigil provide continuous detection of behavioral anomalies in running pods while the rebuild is in progress.
Is generating an SBOM sufficient for NIS2 supply chain compliance?
No. An SBOM satisfies the inventory requirement but not the monitoring or control requirements. NIS2 Article 21 requires measures that include both knowing what you are running (SBOM) and demonstrating that your supply chain is controlled and monitored. Demonstrable controls require: continuous CVE scanning against stored SBOMs, artifact signing with non-repudiable provenance, admission enforcement that blocks unsigned or unverified images, and audit evidence of each control's operation over time. The SBOM is the foundation, not the completion.
How do I handle images from third-party base registries that I do not sign?
You have three options. First, mirror and re-sign: pull the upstream image, run your own SBOM generation and CVE scan against it, then re-sign it with your workflow identity and push to your internal registry. Admission policies then require your signature on all images, which forces mirroring for any third-party image. Second, allow-list by digest: pin trusted upstream images by SHA256 digest in your admission policy and update the digest when you verify a new upstream version. Third, namespace-scope your signing policy: enforce signing only in production and staging namespaces, allowing development namespaces to pull upstream images directly. Option one is the most secure; option two is the most practical for teams that depend on frequently-updated upstream images.
Further reading
- Your own ACME: a sovereign internal PKI with step-ca and cert-manager
- Self-managed Kafka off Confluent: Strimzi, Redpanda, and NATS
- Digital sovereignty: from policy slogan to testable architecture
- Gaia-X in practice: federation, labels, and the sovereignty gap
- Confidential computing: protecting data-in-use with hardware TEEs
- Open Source: KubeVigil Supply-Chain Checks
- Capabilities: Infrastructure & Security Engineering
- Thesis: Engineering Sovereignty
- Enforcing signatures with Kyverno admission
- Mapping supply-chain controls to NIS2/DORA
- High-risk AI under the EU AI Act: an on-prem compliance path
- Golden paths that bake in signing
- Immutable nodes and provenance
- Owning the forge and CI that build the signed artifacts
- Catching the breach at runtime with Falco and Tetragon
- Self-hosted GitHub Actions runners on Kubernetes with ARC
Executive Briefing
Thirty minutes to clarify your infrastructure risk
Walk us through your vendor footprint and regulatory constraints. We will tell you honestly where sovereignty creates leverage — and where it does not. No pitch deck. No obligation.