Container image signing in 2026: revisit cosign, Notation and in-toto attestations before you trust another image tag
Picture a software company in Rajkot that builds production planning and job work software for foundries, forging units and engineering workshops across Saurashtra (an invented example, not a real client). About 300 customers use the SaaS version, which runs on a managed Kubernetes cluster in a Mumbai cloud region. Nine larger customers, mostly auto component suppliers, insist on an on-premises install in their own plant server room, with no inbound access and very limited outbound internet. The company has about 35 developers and a platform team of three. Code lives on a self-managed GitLab server, CI builds images, and Harbor stores them.
One Friday, during an escalation for a customer in Morbi, a support engineer rebuilt planner-api:5.3 on his laptop with a debug flag switched on and pushed it over the same tag, “just to test one thing”. Nobody noticed. On Sunday the cloud provider upgraded a node pool, pods were rescheduled onto fresh nodes, and those nodes pulled planner-api:5.3 again. They got the laptop build. It ran for eleven days in production with a debug endpoint open before someone saw a strange log line. Three weeks later, a large buyer sent a security questionnaire with a simple question: “How do you prove that the image running in production was built by your CI from reviewed code?” Nobody could answer it with evidence.
This post looks at the tools that answer that question, as of 25 October 2026: cosign from the Sigstore project, Notation from the Notary Project, and in-toto attestations (with SLSA provenance as the most common kind). Signing alone does nothing unless something checks the signature before a container starts, so a good part of this post is about where verification is enforced: the Sigstore policy-controller, Kyverno, and Ratify with Gatekeeper.
The facts come from primary material: project READMEs and documentation, release notes, the Sigstore blog, GitHub release and security advisory data, CNCF project pages and the SLSA specification. Release data was fetched through the GitHub API on 25 October 2026. No hands-on drill was run for this post; see the short note near the end. Where I could not confirm something from a primary source, I say so. Earlier Revisit.Tech posts covered vulnerability scanners, container registries and Kubernetes admission policy; this one does not repeat them, it connects them.
The short version
- A tag is a mutable pointer, not an identity.
planner-api:5.3means “whatever digest the tag points to right now”. Only the digest (sha256:...) names fixed content. Sign digests, deploy digests, and verify digests. - A signature is only useful where it is checked. If nothing in the deploy path refuses an unsigned or wrongly signed image, signing is paperwork. The check belongs at admission in the cluster, and also in CI before promotion.
- cosign 3.1.4 is the broadest choice. It signs with keyless identities (short-lived certificates from Fulcio, logged in the Rekor transparency log) or with your own keys in a KMS, Vault or OpenBao. Cosign 3 stores signatures as OCI 1.1 referrers in the standard Sigstore bundle format. It releases often and has had several verification advisories in 2026, all fixed.
- Notation 1.3.2 fits a classic X.509 PKI. You sign with keys held through plugins (Azure Key Vault and AWS Signer are documented in its README), trust is decided by a trust policy file, and it supports RFC 3161 timestamps and CRL revocation checks. Its last stable release was in April 2025, and the v2 line is still at alpha. That is not a defect, but it is a fact to weigh.
- in-toto attestations are the “what” behind the “who”. A signature says who vouched for a digest. An attestation says something specific about it: this SBOM, this build provenance, this scan result. SLSA provenance is a standard predicate for “built by this builder, from this source, with these inputs”.
- For the Rajkot company, keys beat public keyless. GitLab’s documentation says keyless signing with public Sigstore works on GitLab.com; a self-managed GitLab needs self-hosted Sigstore. With nine air-gapped customers, a signing key in OpenBao (or a Notation key behind an internal CA) plus Kyverno verification is the simpler path.
Where things stand on 25 October 2026
All times are Indian Standard Time; GitHub gives release stamps in UTC, and I converted them.
Sigstore: cosign and its services
- cosign latest: 3.1.4 (20 October 2026, 11:57 PM). It is built with Go 1.27.3, adds a
cosign bundle create-from-containercommand, writes referrers correctly when an image index carries attestations, and no longer panics on a bundle entry with no hash. - cosign 3.1.3 and 2.6.5 (6 August 2026, 6:39 AM and 6:19 AM) fixed GHSA-fx35-mq7g-6g98, a high severity verification bypass using a public key inside a legacy bundle. The 2.6.5 notes say the fix was backported to the 2.6.x line, and they encourage everyone to keep moving to the bundle format.
- cosign 3.1.1 (9 June 2026, 11:04 PM; 3.1.0 was skipped because of a release pipeline bug) deprecated a set of flags for trust root and bundle input, made the standardised bundle format the default for signing and verifying, and says the deprecated flags will be removed in a future cosign v4.
- cosign 3.0.1 (8 October 2025, 6:34 AM) was the first published v3 release (3.0.0 was never published because of a CI failure). It turned on by default the standard bundle format, trust roots and service URLs through one file each (
--trusted-root,--signing-config), and signatures stored as OCI Image 1.1 referring artefacts. - Fulcio (the certificate authority): 1.9.1 (21 October 2026, 8:27 AM), built with Go 1.27.3. Fulcio’s documentation says its certificates are valid for 10 minutes.
- Rekor v1 (the transparency log in use on the public instance): 1.5.5 (21 October 2026, 5:33 AM). Rekor v2 lives in the
rekor-tilesrepository: 2.3.0 (11 June 2026, 3:24 AM), which dropped the separate DSSE entry type in favour ofhashedrekord. - The public instance stays on Rekor v1. A Sigstore blog post of 28 June 2026 says the sigstore.dev public good instance will keep Rekor v1 as the default log “for the foreseeable future”, because Rekor v2 changes client behaviour and a post-quantum change is coming anyway. Rekor v2 runs in production for ecosystems that opt in.
- Post-quantum plans: a Sigstore post of 2 October 2026 describes a separate ML-DSA based infrastructure with its own TUF root, opt-in at first. It targets a production-ready public instance for mid-2027, with the classical instance supported until a turndown “tentatively scheduled for 2029 or 2030”. Nothing changes for you today, but do not build anything that hard-codes today’s URLs.
- Licence: Apache 2.0 for cosign, Fulcio, Rekor and policy-controller.
Notary Project: Notation
- Latest stable: 1.3.2 (27 April 2025, 8:47 AM): dependency updates, including a fix for CVE-2025-30204 in
golang-jwt. - Feature history: 1.2.0 (29 August 2024) added OCI 1.1 support and RFC 3161 timestamping. 1.3.0 (21 January 2025) added CRL revocation checks with a file cache.
- Next major: 2.0.0-alpha.1 (13 March 2025, 8:35 AM) switches the default to the OCI referrers API instead of the referrers tag schema and adds
notation blobcommands for signing files. No later v2 build was published by 25 October 2026. The main branch is still active; its most recent commit was on 20 October 2026. - Governance: the Notary Project has been a CNCF project at the Incubating level since 24 October 2017. Notation is Apache 2.0.
in-toto and SLSA
- in-toto moved to CNCF Graduated on 10 February 2025.
- in-toto attestation framework: v1.2.0 (18 March 2026, 11:22 PM) added a Simple Verification Result predicate, an SPDX 3 predicate and an updated release predicate.
- SLSA: the current specification is version 1.2, with status Approved. Its main change over 1.1 is a new Source Track next to the Build Track.
Where verification runs
- Sigstore policy-controller: 0.15.1 (26 March 2026, 5:52 PM). No newer release by 25 October 2026.
- Kyverno: 1.19.2 (21 October 2026, 5:20 AM). Its notes say it addresses 24 published advisories (9 critical, 14 high, 1 moderate), six of them in image verification and attestation handling. More on this in the security section.
- Ratify: 1.4.8 (23 October 2026, 8:24 PM) and 1.4.7 (20 October 2026), both dependency bumps for vulnerabilities. The v2 line is at 2.0.0-beta.2 (18 September 2026), which moved its CRDs to
config.ratify.sh/v2beta1and asks you to uninstall and reinstall the Helm chart rather than upgrade in place. Ratify sits under the Notary Project organisation on GitHub and is a CNCF Sandbox project. - ORAS (a general tool for OCI artefacts): 1.3.6 (25 October 2026, 12:38 AM).
First question: what exactly do you want to prove, and where will you check it?
Before choosing a tool, write down the claims you want to make and the place each claim gets checked. For the Rajkot company, the list looks like this:
- Origin. Every image that runs in production was built by the GitLab CI pipeline on a protected branch or tag, not on a laptop. Checked at admission in the cloud cluster and in each on-premises cluster.
- Integrity. The content running is the same content that was built and tested. This is what digests give you, and signatures bind the digest to a signer.
- Contents. An SBOM exists for each release image, so the security team can answer “are we affected?” in minutes. Checked at promotion, and the SBOM is kept with the image.
- Build facts. Which commit, which pipeline, which builder image. This is SLSA provenance. Checked at admission for production namespaces.
- Third-party images. Images from upstream projects (ingress controller, database operator, monitoring agents) are either verified against the upstream project’s own signatures or copied into Harbor and re-signed after review.
Three things fall out of this list. First, the cloud cluster and the air-gapped clusters need the same answer to “is this signed by us?”, which pushes towards trust material you can carry on a USB disk: a public key or a CA certificate, not a live online service. Second, the self-managed GitLab rules out public keyless signing without extra work. Third, the hard part is not the signing command; it is the verification policy and the habit of deploying by digest.
What each one actually is
Why a tag is not an identity
An OCI image is addressed by a digest, the SHA-256 of its manifest. A tag is a name in a registry that points at a digest, and anyone with push rights can move it. Many registries, Harbor included, can mark tags immutable per project, and that helps, but it is a registry setting, not a property of the image. A signature over a digest says “this exact content was vouched for by this signer”. A signature over a tag does not exist; signing tools resolve the tag to a digest first, which is why the Notary Project quickstart tells you to always reference the digest.
cosign and Sigstore
Sigstore is a set of services and clients. cosign is the command line client for containers and files. In keyless mode, cosign creates a key pair in memory, gets an OpenID Connect token (from a person’s login or a CI job), and asks Fulcio for a certificate that binds that identity to the public key. The certificate is valid for 10 minutes. The signing event is recorded in Rekor, a public append-only log, and the private key is thrown away. A verifier then checks the signature, the certificate chain, the log entry, and, most importantly, that the identity in the certificate is the one you expected. Sigstore’s trust roots (Fulcio’s CA, Rekor’s key, timestamp authorities) are distributed through TUF, a framework for safe updates of trust metadata.
In key-based mode, cosign signs with a key you hold: a local encrypted key file, or a key in AWS KMS, Azure Key Vault, Google Cloud KMS, HashiCorp Vault, OpenBao or a Kubernetes secret. The documentation lists URI schemes for each, such as awskms://, hashivault:// and openbao://. By default cosign still records the signature in Rekor, unless you point it at your own services through a signing config or switch the upload off.
cosign also writes attestations with cosign attest: an in-toto statement whose subject is the image digest, wrapped in a signed DSSE envelope. Since v3, signatures and attestations are stored as OCI 1.1 referrers of the image, and cosign tree lists them.
Notation and the Notary Project
The Notary Project is a set of specifications for signing OCI artefacts with X.509 certificates; Notation is its command line client. You sign with a key and a certificate chain (in practice through a plugin for a KMS), and the signature is stored in the registry as a referrer with the artefact type application/vnd.cncf.notary.v2.signature. Signatures use JWS by default, or COSE if you ask for it.
Verification is driven by a trust policy: a JSON file that says, for a set of registry scopes, which trust stores (CA certificates, and TSA certificates for timestamps) to use, which certificate subjects are trusted, and how strict to be (strict, permissive and others). There is no public log and no online identity service. Notation adds RFC 3161 timestamps so a signature stays valid after the signing certificate expires, and it checks revocation through OCSP and, since 1.3.0, CRLs. In plain words: Notation is code signing as an enterprise PKI team already knows it, applied to registries.
in-toto attestations and SLSA provenance
An in-toto statement has three parts: a subject (the artefact and its digest), a predicateType (a URI that says what kind of claim this is), and a predicate (the claim itself). It is then signed, usually inside a DSSE envelope. Common predicate types are SPDX and CycloneDX SBOMs, vulnerability scan results, VEX documents, test results and SLSA provenance.
SLSA provenance describes how an artefact was built: the build definition (type, external parameters such as the source repository and commit), the resolved dependencies, and run details (the builder’s identity, timing). The SLSA Build Track levels then describe how much you can trust that statement: at L1 provenance exists; at L2 the build runs on a hosted platform that generates and signs the provenance; at L3 the platform is hardened so that a build cannot tamper with its own provenance or the signing material. Writing a provenance file inside your own CI job and signing it with a key the same job can use is useful, but by itself it is closer to L1 than L3. Be honest about that in the security questionnaire.
OCI referrers: where signatures live
OCI image-spec and distribution-spec 1.1 added a subject field and a referrers API: a registry can answer “which artefacts point at this digest?”. Signatures, SBOMs and attestations are pushed as small manifests whose subject is the image. For registries without the API, there is a fallback called the referrers tag schema: a tag named after the digest that holds an index of referrers. Notation 1.x signs with the tag schema by default for compatibility and always tries the API first when reading; the 2.0 alpha changes the default to the API. Cosign 3 uses referrers too. The practical point: when you copy an image between registries, you must copy its referrers as well, or the signature stays behind.
Side by side
Licence and governance
- cosign and Sigstore services: Apache 2.0. Sigstore runs a free public good instance (Fulcio, Rekor, timestamp authority, TUF root) that most open source projects use.
- Notation: Apache 2.0, under the Notary Project, CNCF Incubating since 2017.
- in-toto: CNCF Graduated in February 2025. SLSA is a specification, now at 1.2.
- policy-controller, Kyverno, Ratify: all Apache 2.0, as noted in the earlier admission policy post.
Trust model
- cosign keyless: you trust an identity (for example, a CI workflow path on a branch) as vouched for by an OIDC issuer, Fulcio’s CA and the Rekor log. There are no long-lived keys to leak, but you depend on the issuer and the Sigstore services.
- cosign with keys: you trust a public key. Simple to explain and easy to carry offline; the private key becomes something to guard and rotate.
- Notation: you trust a CA (or a specific certificate) plus subject names in the trust policy. Fits companies that already run an internal CA or buy code signing certificates.
Keys and identity in CI
- cosign keyless works where the CI system issues OIDC tokens that Fulcio accepts. GitLab’s own documentation lists GitLab.com as the offering for keyless signing with public Sigstore and points self-managed GitLab to self-hosted Sigstore. GitHub Actions, Google and Microsoft identities are supported on the public instance.
- cosign with keys supports the major cloud KMS services, Vault and OpenBao, so the private key need not leave the key service.
- Notation signs through plugins. Its README links guides for Azure Key Vault and AWS Signer. For other key stores, check which plugins exist and who maintains them before you commit.
Where signatures and attestations are stored
- cosign 3: OCI 1.1 referrers, Sigstore bundle format by default. Older cosign versions stored signatures under a
sha256-<digest>.sigtag; verifiers that only know one layout will not find the other. - Notation 1.3.2: referrers tag schema by default when signing, referrers API first when reading.
Transparency and timestamps
- cosign keyless: a public transparency log entry is part of the design. It proves when signing happened and lets you monitor for unexpected signatures with your identity. It also makes the digest and your identity public, which matters for private repositories.
- cosign with keys: you choose: public Rekor, your own Rekor, or no log with RFC 3161 timestamps instead.
- Notation: no log. RFC 3161 timestamps from a TSA you trust, configured through a
tsatrust store in the trust policy.
Revocation and rotation
- cosign keyless: nothing to rotate on your side; certificates expire in minutes. If a CI identity is abused, you change the expected identity in policy and look for its entries in the log.
- cosign with keys: there is no revocation list for a raw key. Rotation means signing new releases with a new key, adding the new public key to every verifier first, and later removing the old one. Plan the overlap.
- Notation: certificates expire and can be revoked through OCSP or CRL. With timestamps, old signatures stay valid after expiry; Notation’s documentation notes that it does not use the timestamp to decide whether a signature was made before a revocation.
Air-gapped and offline verification
- cosign with keys: the easiest offline story. Ship the public key, verify against the image and its referrers in the local registry, and skip the online log check deliberately.
- cosign keyless: possible offline with bundles and a copy of the trusted root, but trust material must be refreshed by hand. The policy-controller documentation says a serialized TUF repository works in an air gap, and that in this mode you rotate keys and certificates manually.
- Notation: naturally offline: trust store and trust policy files. CRL and OCSP checks need network access to the CA’s endpoints, so decide what happens when they are unreachable.
Admission enforcement support
- Sigstore policy-controller: cosign keys, keyless identities, attestations with CUE or Rego policies, custom trust roots. Namespaces opt in with the label
policy.sigstore.dev/include: "true", and an image that matches no policy is denied unless you changeno-match-policy. Its documentation, read on 25 October 2026, says only attestations, not plain signatures, are supported in the bundle format, which matters because cosign 3 signs in that format by default. - Kyverno ImageValidatingPolicy: cosign attestors (keys, KMS, keyless identities, certificates, custom trusted roots) and Notary attestors (certificates and TSA certificates), attestation checks for in-toto predicates and referrer types, and settings to rewrite tags to digests and to require digests.
- Ratify with Gatekeeper: a verification engine used as a Gatekeeper external data provider; its quick start shows a Notation check. Choose this if Gatekeeper is already your policy engine.
Where verification actually happens
It helps to picture three gates, each with a different job.
Gate 1: promotion in CI. Before an image is copied from the staging project in Harbor to the release project, a CI job runs cosign verify or notation verify against the digest and checks the attestations. This catches mistakes early and gives a clear error to the developer.
Gate 2: admission in the cluster. When a Deployment or Pod is created, the admission webhook resolves the image to a digest, verifies the signature and required attestations, and rejects the request if they fail. This is the gate that would have stopped the laptop build in the Rajkot story. It must be set to fail closed for production namespaces; a webhook that allows everything when it times out is a polite suggestion, not a gate.
Gate 3: the runtime pull. Kubernetes pulls by whatever reference is in the Pod spec. If admission rewrote the tag to a digest, the node pulls exactly what was verified. If not, a node created later can pull a different image under the same tag, which is precisely what happened on that Sunday. Policy-controller says it resolves tags for this reason, and Kyverno has mutateDigest. Turn these on.
Admission webhooks need registry access to fetch signatures, so registry credentials, network paths and timeouts become part of your availability design, and system namespaces need care so the webhook cannot block its own restart. The admission policy post covers failure policies and exceptions in more depth.
Worked example for the Rajkot scenario
The names, hosts and keys below are invented. Treat the snippets as sketches to adapt, not tested files; field names change between versions, so check them against the documentation for the exact version you install.
Step 1: build once, record the digest
The pipeline builds the image once, pushes it to the staging project in Harbor, and saves the digest as a job artefact. Everything after this uses the digest. (The image builders post covers the build step itself.)
IMAGE=harbor.example.in/staging/planner-api
# after the build tool pushes $IMAGE:$CI_COMMIT_SHORT_SHA
DIGEST=$(crane digest "$IMAGE:$CI_COMMIT_SHORT_SHA")
echo "$IMAGE@$DIGEST" > image.ref
Step 2, option A: cosign with a key in OpenBao
The platform team creates a signing key in OpenBao’s transit engine, called planner-release. Only the release job on protected tags gets a short-lived token that can use it, and nobody can export the private key. (The Vault and OpenBao post covers the secrets manager side.)
# once, by the platform team: publish the public key
cosign public-key --key openbao://planner-release > planner-release.pub
# in the release job (protected tags only)
cosign sign --key openbao://planner-release "$(cat image.ref)"
The public key goes into Git, into the Kyverno policy, and onto the USB disk for the on-premises customers. Decide about the transparency log on purpose. A public Rekor entry would publish the digest and the public key; for a private product, the company decides not to use the public log and relies on the registry plus admission checks instead. That loses the public timestamped record, so record signing events in the CI audit log and keep them.
Step 2, option B: Notation with an internal CA
If the company already runs an internal CA (for example step-ca, covered in the TLS certificate automation post), it can issue a code signing certificate for the release job and use Notation with a plugin for its key store. Signing with a timestamp:
notation sign --signature-format cose \
--timestamp-url "$TSA_URL" --timestamp-root-cert tsa-root.crt \
--key planner-release "$(cat image.ref)"
And the trust policy that every verifier uses:
{
"version": "1.0",
"trustPolicies": [
{
"name": "planner-release-images",
"registryScopes": [ "harbor.example.in/release/planner-api" ],
"signatureVerification": { "level": "strict" },
"trustStores": [ "ca:rajkot-codesign", "tsa:rajkot-tsa" ],
"trustedIdentities": [ "x509.subject: CN=planner-release-ci, O=Example Planner Pvt Ltd, C=IN" ]
}
]
}
Note that trustedIdentities names one subject, not "*". A wildcard there trusts any certificate the CA ever issued.
Step 3: attach an SBOM and provenance
Generate the SBOM from the pushed digest, then attest it. For provenance, the job writes a small SLSA v1 predicate from CI variables: the repository URL, commit SHA, pipeline URL and the builder image digest.
REF=$(cat image.ref)
syft "$REF" -o spdx-json > sbom.spdx.json
cosign attest --key openbao://planner-release --type spdxjson \
--predicate sbom.spdx.json "$REF"
cosign attest --key openbao://planner-release --type slsaprovenance1 \
--predicate provenance.json "$REF"
cosign tree "$REF"
cosign tree should now show one signature and two attestations attached to the digest. With Notation, you would push the SBOM as an artefact with oras attach and sign that artefact’s digest.
Be clear about what this provenance proves. It is written by the same job that holds the signing key, so it shows what the pipeline says it did. It does not prove the pipeline could not lie. Moving provenance generation to a separate, locked-down job, or to a build platform that signs provenance itself, is the path towards SLSA Build L3.
Step 4: verify before promotion
REF=$(cat image.ref)
cosign verify --key planner-release.pub "$REF"
cosign verify-attestation --key planner-release.pub \
--type slsaprovenance1 "$REF" > /dev/null
# copy to the release project only if both pass
If the company skips the public log, verification must be told so explicitly, with the flag your cosign version documents for ignoring the transparency log (for example --insecure-ignore-tlog=true). The word “insecure” in the flag is a reminder that you have replaced the log with other controls; write down which ones.
Step 5: enforce at admission with Kyverno
The company already runs Kyverno, so it uses an ImageValidatingPolicy. A sketch for production namespaces:
apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
name: planner-signed-and-built-by-ci
spec:
validationActions: [Deny]
webhookConfiguration:
timeoutSeconds: 15
failurePolicy: Fail
matchConstraints:
namespaceSelector:
matchLabels:
env: prod
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: ['CREATE', 'UPDATE']
resources: ['pods']
matchImageReferences:
- glob: 'harbor.example.in/release/*'
validationConfigurations:
mutateDigest: true
verifyDigest: true
required: true
attestors:
- name: release
cosign:
key:
data: |
-----BEGIN PUBLIC KEY-----
(contents of planner-release.pub)
-----END PUBLIC KEY-----
ctlog:
insecureIgnoreTlog: true
attestations:
- name: provenance
intoto:
type: https://slsa.dev/provenance/v1
validations:
- expression: >-
images.containers.map(image, verifyImageSignatures(image, [attestors.release])).all(e, e > 0)
message: image is not signed by the release key
- expression: >-
images.containers.map(image, verifyAttestationSignatures(image, attestations.provenance, [attestors.release])).all(e, e > 0)
message: image has no signed SLSA provenance
Start with validationActions: [Audit] for a week, read the reports, fix the images that fail, and then switch to Deny. Run Kyverno 1.19.2 or later; the reasons are in the security section. If the team prefers Notation, the attestor becomes a notary block with the CA certificate and TSA certificate.
Step 6: the nine air-gapped customers
Each quarterly update is shipped as a bundle on an encrypted disk or through the customer’s file transfer system. The bundle carries images and their referrers, not just images. Two ways to do it:
# with ORAS: copy the image and everything that refers to it
oras copy -r harbor.example.in/release/planner-api@sha256:... \
--to-oci-layout ./bundle/planner-api:5.4.0
# or with cosign: save and load images with signatures and attestations
cosign save --dir ./bundle/planner-api harbor.example.in/release/planner-api@sha256:...
At the customer site, the images are loaded into the local registry, and the local Kyverno has the same policy with the same public key. Because the key-based design needs no live service, verification works without internet. The company also writes down how it will rotate the key: the new public key goes out in one quarterly update, both keys are trusted for one cycle, and the old key is removed in the next.
What changes for the support engineer
He can still build on his laptop for a test cluster. He cannot get that image into production: Harbor’s release project accepts pushes only from the release job’s robot account, the tag is immutable there, and the cluster rejects any digest not signed by the release key. A hotfix goes through a protected tag and the same pipeline, which on a good day takes about fifteen minutes. That is the trade.
Security and hygiene
The signer is now your crown jewel
Whatever signs releases decides what the clusters will run. For key-based signing, that is the key and whoever can use it; for keyless, it is the CI identity and the branch protection behind it. An attacker who can run the release job can sign anything. So protect the path, not only the key: protected branches and tags, required reviews, a release job that cannot be edited from a merge request, short-lived tokens, and an audit log of every signing event.
Notable advisories
cosign published four advisories in 2026, all fixed:
- GHSA-fx35-mq7g-6g98, high, 7.4 (6 August): verification bypass through a public key in a legacy bundle for
verify-blobandverify-blob-attestation; keyless identity checks could be ignored. The advisory says OCI image verification and the standard bundle format were not affected. Fixed in 3.1.3 and 2.6.5. - GHSA-w6c6-c85g-mmv6 (CVE-2026-39395), medium (7 April):
verify-blob-attestationcould report success when payload parsing failed. Fixed in 3.0.6 and 2.6.3. - GHSA-wfqv-66vq-46rm (CVE-2026-24122), low (20 February): signatures considered valid with certificates that outlived an expired CA certificate. Fixed in 3.0.5.
- GHSA-whqx-f9j3-ch6m (CVE-2026-22703), medium (10 January): verification could accept any valid Rekor entry under certain conditions. Fixed in 3.0.4 and 2.6.2.
sigstore-go, the library that cosign 3 now signs through: CVE-2026-49834 (medium, multi-log threshold bypass through a single compromised log, fixed in 1.2.0) and CVE-2026-54787 (low, signature timestamps not checked against a key’s validity period, fixed in 1.2.1). If you build your own verifier on this library, check your version.
Fulcio and Rekor matter if you self-host them. Fulcio: CVE-2026-49478 (high, 8.2, SSRF and JWKS substitution through OIDC discovery redirects for meta-issuers, fixed in 1.8.6), CVE-2026-22772 (medium, SSRF through a meta-issuer regex bypass, fixed in 1.8.5) and GHSA-55r8-p8r6-2f8m (medium, blind SSRF in JWKS lookup, fixed in 1.9.0). Rekor: CVE-2026-48702 (high, 7.5, out of memory through unbounded gzip decompression in Alpine package parsing, fixed in 1.5.2), and CVE-2026-24117 and CVE-2026-23831 (medium, fixed in 1.5.0).
Kyverno matters most for this post, because it is the gate. Version 1.19.2 fixed, among others:
- GHSA-2r43-6962-xcw5, critical, 9.1:
verifyImagescached results by image reference string (for example a tag) without binding the entry to a verified digest. Verify a signed digest once, move the tag, and admission could pass from cache and pin the new, unverified digest. Affects 1.18.0 to 1.19.1. This is the tag problem from the Rajkot story, inside the verifier. - GHSA-h3pm-q9ww-f439, high: a forged
kyverno.io/verify-images-scopedannotation could make the validating webhook trust a verification that never happened. Up to 1.19.1. - GHSA-fcfr-fr8g-qjqv, high: the cosign identity check was skipped when the signing certificate had no Subject Alternative Name. Up to 1.19.1.
- GHSA-24cc-9745-hxfc, high: a remote panic in attestation parsing could lead to an image verification bypass.
- GHSA-3f2c-hfj8-542w and GHSA-fr24-3hvf-393q, high: unbounded decompression in Notary signature and attestation handling, a memory exhaustion risk.
Earlier, GHSA-5cjf-wwfg-pj4c (high, 10 September 2026) showed that exceptions for ImageValidatingPolicy ignored the image list and fully bypassed signature verification instead of partially exempting; fixed in 1.19.1. The lesson: the verifier is security-critical code, and it needs the same update routine as the things it protects.
Notation: no advisories in 2025 or 2026 on the Notation CLI. The library notation-go had CVE-2024-56138 (medium, missing revocation check on timestamp certificates) and CVE-2024-51491 (low), fixed in 1.3.0-rc.2. Ratify: CVE-2025-27403 (Azure authentication providers could send tokens to non-Azure registries), fixed in 1.2.3 and 1.3.2. policy-controller: only an old advisory, CVE-2022-35930, fixed in 0.2.1.
Habits that matter more than the version number
- Verify identity, not just validity. A valid signature from an unexpected signer is worse than no signature, because it looks fine. For keyless, pin both the issuer and the exact subject (workflow path and ref). For Notation, name the subject in
trustedIdentities. - Pin by digest everywhere that matters: Kubernetes manifests in Git, Helm values, and base images in Dockerfiles.
- Keep signing and verifying clients close in version. The Sigstore Rekor v2 posts warn specifically about signing with a newer client while verifying with an older one.
- Do not hard-code Sigstore service URLs. Use the TUF-distributed signing config and trusted root; the public infrastructure will change shape again with the post-quantum move.
- Allow unsigned third-party images only by digest, with an owner and an expiry date, never with a wildcard.
- Scan and sign are different questions. A green scan says “no known issues today”; a signature says “this is what we built”. You need both, as the vulnerability scanners post argued from the other side.
Operational costs to budget for
- Key rotation. With keys, rotation is a planned project: new key, distribute the public key to every verifier (including air-gapped sites), overlap, retire. Put a date on it. With certificates, add certificate renewal and CRL hosting to the list.
- Transparency log choices. Public Rekor costs nothing to run, but entries are public and permanent. A private Rekor is another stateful service with its own backups, monitoring and advisories. No log means you rely on timestamps and your own audit trail.
- Registry work. Signatures and attestations are extra manifests. Retention and garbage collection rules must keep referrers alive as long as the image, and mirrors and replication must copy them. The container registries post has more on Harbor, zot and Distribution here.
- Admission latency and availability. Every Pod creation now makes registry calls. Set timeouts, cache results safely (by digest, as the Kyverno fix shows), and know what happens during a registry outage.
Decision guide
Lean towards cosign keyless if
- Your CI runs on GitHub Actions or GitLab.com, or another issuer the public Sigstore instance accepts, and your clusters can reach Sigstore’s TUF root and services.
- You publish open source or customer-visible images and want anyone to verify them without receiving a key from you.
- You are comfortable with signing identities and digests appearing in a public log, and you will monitor the log for your identity.
Lean towards cosign with your own key if
- CI is self-managed, verifiers include air-gapped sites, or you do not want entries in a public log.
- You already run a KMS, Vault or OpenBao, and can give only the release job permission to use the key.
- You accept that rotation is your job, and you write the rotation plan before the first signature.
Lean towards Notation if
- Your organisation already runs a PKI, buys code signing certificates, or must show auditors a classic chain of trust with revocation and timestamps.
- You sign mostly in Azure or AWS, where the documented plugins exist.
- You accept a slower stable release pace and will watch the v2 line.
Add in-toto attestations and SLSA provenance when
- Someone asks “what is in it?” or “how was it built?”, and a signature alone does not answer. Start with an SBOM attestation and a basic provenance predicate; improve the provenance’s trust level over time.
Pick the gate by what you already run
- Kyverno if it is already your policy engine; it supports cosign and Notary attestors in one policy type. Stay current.
- Sigstore policy-controller if you want a focused, Sigstore-only webhook with CUE or Rego checks on attestations; check its bundle format support against how you sign.
- Ratify if Gatekeeper is your engine and you want Notation verification; plan for the v2 migration.
Checklist
- Every production manifest references images by digest, or admission rewrites tags to digests.
- Release images are signed by digest, by the CI release job only, from protected branches or tags.
- The private key cannot be exported, or keyless identities are pinned to an exact workflow path and ref.
- Verification checks the signer’s identity, not only that a signature is present.
- An SBOM attestation and a provenance attestation exist for each release image, and the gate checks at least provenance.
- Admission verification is enforced, fails closed for production namespaces, and was tested once with an unsigned image and a wrongly signed one.
- The verifier (Kyverno, policy-controller or Ratify) is on a current release, with someone reading its advisories.
- Image copies, mirrors and air-gapped bundles carry referrers, and verification was tested at the destination.
- Registry retention keeps signatures and attestations as long as the images they belong to.
- A key rotation or certificate renewal plan exists, with dates, and it covers the air-gapped sites.
- Third-party images are either verified against upstream signatures or reviewed, copied and re-signed, with exceptions by digest and expiry.
Common mistakes
- Signing
planner-api:latestin one job and deployingplanner-api:latestin another, and assuming they are the same image. - Signing everything and verifying nothing. A signature that no gate checks is a comment.
- Verifying “is there a valid signature?” without checking who signed it. Any valid key or any Fulcio identity then passes.
- Using
"*"in Notation’strustedIdentitiesor a loosesubjectRegExpin a keyless policy, so that anyone the CA or issuer knows is trusted. - Leaving the admission webhook in audit mode for months, or with a failure policy that ignores errors, so every outage quietly turns verification off.
- Mirroring images to a new registry or an air-gapped site without their referrers, then switching verification off “temporarily” because signatures cannot be found.
- Upgrading signing to cosign 3 defaults without checking that every verifier understands the bundle format and referrers.
- Running an old Kyverno with tag-based image references and assuming verification holds, even though 1.19.2 fixed a cache bypass of exactly that kind.
- Treating an attestation written by the build job itself as strong evidence of how the build ran.
Drill note
No hands-on drill was run for this post. Nothing was installed, and no image was signed or verified on the box. Every statement here comes from the projects’ documentation, release notes, blog posts and security advisories listed in the sources, checked on 25 October 2026, and the snippets are sketches based on that documentation. Before you enforce signing, run your own pilot: sign one image, verify it in CI, enforce in one namespace in audit mode, try an unsigned image, a tag moved to a new digest and a signature from the wrong key, then copy everything to a disconnected test cluster and verify there.
Unlearn
- Unlearn that a tag names an image. It names a pointer, and the pointer can move.
- Unlearn that signing is a security control by itself. Verification at the gate is the control; signing only makes it possible.
- Unlearn that “keyless” means “nothing to trust”. It moves trust to the identity provider, the CA and the log.
- Unlearn that a scan, an SBOM and a signature are the same thing. They answer different questions.
Re-learn
- Re-learn content addressing: digests for identity, tags for humans.
- Re-learn the three gates: promotion in CI, admission in the cluster, and the pull that follows admission.
- Re-learn the basic PKI ideas behind all three tools: who holds the key, how others learn to trust it, how it is rotated, and what happens when it is lost.
- Re-learn attestations as statements with a subject, a type and a claim, so you can ask precise questions of your build.
Closing
The Rajkot incident was not a tooling failure; it was a missing rule: production ran whatever the tag pointed to. cosign 3.1.4 gives the widest set of choices, from public keyless signing to keys in OpenBao, and the most active development, with an update routine to match. Notation 1.3.2 brings a clean X.509 model with timestamps and revocation for teams that already think in certificates, on a slower release line. in-toto attestations and SLSA provenance turn “we signed it” into “here is what it contains and how it was built”. For the Rajkot company, the sensible path is a release key in OpenBao used only by the protected release job, an SBOM and a provenance attestation for each release digest, Kyverno 1.19.2 enforcing signatures and provenance by digest in production, and the same public key on the disk that goes to each air-gapped plant. Then the answer to the buyer’s question is a command, not a promise.
Sources
Sigstore and cosign
- sigstore/cosign on GitHub
- cosign 3.1.4 release
- cosign 3.1.3 release
- cosign 2.6.5 release
- cosign 3.1.1 release
- cosign 3.0.1 release
- Sigstore docs: signing containers
- Sigstore docs: keyless signing overview
- Sigstore docs: Fulcio
- Sigstore docs: policy-controller
- sigstore/policy-controller 0.15.1 release
- sigstore/fulcio releases
- sigstore/rekor releases
- sigstore/rekor-tiles 2.3.0 release
- Sigstore blog: Cosign v3 is now available
- Sigstore blog: Rekor v2 GA
- Sigstore blog: sigstore.dev and Rekor evolution
- Sigstore blog: Post-Quantum Cryptography for Sigstore
- GitLab docs: use Sigstore for keyless signing and verification
Notary Project and Notation
- notaryproject/notation on GitHub
- Notation 1.3.2 release
- Notation 1.3.0 release
- Notation 2.0.0-alpha.1 release
- Notary Project quickstart: sign and validate a container image
- Notary Project: manage trust policies
- Notary Project: timestamping
- CNCF: Notary Project
- notaryproject/ratify releases
in-toto, SLSA and OCI
- CNCF: in-toto
- in-toto attestation framework v1.2.0
- SLSA specification
- SLSA 1.2: what’s new
- OCI distribution-spec v1.1.1: listing referrers
- ORAS 1.3.6 release
Kyverno
Security records
- GHSA-fx35-mq7g-6g98 (cosign legacy bundle public key bypass)
- GHSA-w6c6-c85g-mmv6 (cosign verify-blob-attestation false positive)
- GHSA-wfqv-66vq-46rm (cosign certificates outliving expired CA)
- GHSA-whqx-f9j3-ch6m (cosign accepting any valid Rekor entry)
- GHSA-9vcr-p3rj-q5q6 (sigstore-go multi-log threshold bypass)
- GHSA-wqqc-jjcq-vfxm (sigstore-go timestamp validity check)
- GHSA-f5mr-q85p-6hh6 (Fulcio OIDC discovery redirect SSRF)
- GHSA-59jp-pj84-45mr (Fulcio meta-issuer regex SSRF)
- GHSA-55r8-p8r6-2f8m (Fulcio blind SSRF in JWKS lookup)
- GHSA-47q9-m4ww-924m (Rekor unbounded gzip decompression)
- GHSA-2r43-6962-xcw5 (Kyverno verifyImages tag cache bypass)
- GHSA-h3pm-q9ww-f439 (Kyverno forged annotation bypass)
- GHSA-fcfr-fr8g-qjqv (Kyverno identity check skipped without SAN)
- GHSA-5cjf-wwfg-pj4c (Kyverno ImageValidatingPolicy exception bypass)
- GHSA-45v3-38pc-874v (notation-go timestamp revocation check)
- GHSA-44f7-5fj5-h4px (Ratify Azure token leak)
Internal
- Revisit.Tech: vulnerability scanners with Trivy, Grype and OSV-Scanner
- Revisit.Tech: container registries with Harbor, zot and CNCF Distribution
- Revisit.Tech: Kubernetes admission policy with Kyverno, Gatekeeper and ValidatingAdmissionPolicy
- Revisit.Tech: container image builds with BuildKit, Buildah and Kaniko
- Revisit.Tech: Vault and OpenBao
- Revisit.Tech: TLS certificate automation with cert-manager, Certbot and step-ca
Releted Posts
Container registries in 2026: revisit Harbor, Zot and CNCF Distribution before you push another image
Picture a product engineering team in Bengaluru that runs about forty microservices on Kubernetes for a B2B payments product used by NBFCs across Karnataka and Maharashtra (an invented example, not a real client).
Read moreMesh VPNs in 2026: revisit Headscale, NetBird and plain WireGuard before you open another SSH port to the internet
Take a pump manufacturer in Coimbatore that makes agricultural and domestic pumps at two plants, one near the city and a smaller one at Kaniyur (an invented example, not a real client).
Read moreTLS certificate automation in 2026: revisit cert-manager, Certbot and step-ca before the next certificate expires on you
Think of a bicycle and auto parts manufacturer in Ludhiana that sells through about 400 dealers across Punjab, Haryana, Rajasthan and the hill states (an invented example, not a real client).
Read more