Cosign 3.1.3 Removed Offline Signing
cosign v3.1.3 refuses --tlog-upload=false with --signing-config, which blocks signing on a host with no public Sigstore egress. The fallback to v2.6.5 signs in under a second and puts a .sig tag straight into the registry catalogue.
The plan was ordinary: generate a key pair, sign an image digest without uploading anything to a transparency log, and verify it back. The first command failed on the version of cosign that was installed.
The refusal
Error: --tlog-upload=false is not supported with --signing-configcosign v3.1.3 removed offline signing via --tlog-upload=false. On a host with no egress to the public Rekor — which is the whole point of the flag — there is no path forward on v3.x without standing up a private signing configuration first. The POC fell back to cosign v2.6.5 for the rest of the test, and the blocker went into the postmortem table alongside the other environment defects: cosign v3.1.3 removed offline signing → blocked air-gapped signing test → fell back to cosign v2.6.5; recorded as a finding.
This is worth naming precisely because it is a minor version bump in appearance. Nothing about "3.1.3" announces that an air-gapped workflow stops working.
Offline signing on v3 needs a private signing-config; v2.6.5 still takes --tlog-upload=false directly.
What v2.6.5 actually did
The key pair is a throwaway P-256 ECDSA pair — the public half is safe to commit and lives in the policy file; the private half belongs in a secret store and reaches CI as a masked variable. The signing step against a POC-local registry at 127.0.0.1:5000:
Pushing signature to: 127.0.0.1:5000/poc/tc01
cosign_sign_exit=0
cosign_sign_seconds=0Exit 0, and the elapsed-seconds counter never ticked over to 1. Signing is not the slow part of a supply chain; it is a hash and a push.
Verification against the public key, with tlog verification skipped, comes back clean and loud about the trade-off:
WARNING: Skipping tlog verification is an insecure practice that lacks
transparency and auditability verification for the signature.
Verification for 127.0.0.1:5000/poc/tc01@sha256:9825525c...f1a18f1 --
- The cosign claims were validated
- The signatures were verified against the specified public key
cosign_verify_exit=0That warning is the honest cost of offline signing: you keep the signature, you lose the independent record that it was ever created.
Where the signature lives
The part that surprises people the first time is that a cosign signature is not metadata attached to the image. It is another image, sitting in the same repository under a derived tag. Listing the tags after signing:
{"name":"poc/tc01","tags":["v1","sha256-9825525c82a30eb0225568ea5db14418ddeb759f8f6720fab1289e219f1a18f1.sig"]}Two tags where there was one. The .sig entry is the digest of the signed image with the : rewritten to -, because tags cannot contain colons.
Three operational consequences follow from that one line. Registry retention policies that delete by tag pattern will happily delete signatures. Registry quota and garbage collection count them. And anything that verifies at admission time needs pull credentials for the repository, not some separate signature service — Kyverno fetching a signature is Kyverno doing a normal registry read.
Re-running the key generation step also prompts rather than clobbering: File cosign-poc.key already exists. Overwrite? — declining exits with user declined the prompt. Harmless interactively, and a hang waiting for stdin in CI if you forget --yes.
Next: verifying a real signature at admission — one signed Pod, one unsigned Pod, and the exact error in between.