Three commands carry the whole job: sign an image by digest, verify it against a public key, then verify it keyless from GitHub Actions with no key at all. Two more flags matter only when your registry or admission controller has not caught up to the v3 defaults, and this walks both paths.
Short version: Cosign v3 turns --new-bundle-format, --trusted-root, and --use-signing-config on by default, so signatures now land as OCI 1.1 referring artifacts in the standardized bundle format instead of a sha256-<digest>.sig tag. Your signing command looks identical; Harbor, air-gapped registries, and older admission controllers are what break. --new-bundle-format=false and --use-signing-config=false are the two fallbacks that keep CI signing while the rest of the stack catches up.
The release notes bury the change that breaks things. Cosign v3 shipped on October 8, 2025, and Sigstore's v3.0 release announcement records that the upgrade flips three opt-in flags to on-by-default at the same time: --new-bundle-format, --trusted-root, and --use-signing-config. The pitch is "fewer flags, one standardized format." The cost is that your signatures now land in your registry in a shape older tooling cannot see, and a cosign verify call that passed last quarter can fail, or hang, against an air-gapped registry.
This walks the full sign-and-verify loop and foregrounds the version-specific breakage you only learn by hitting it: Harbor not detecting the new bundle, verify reaching out to the TUF CDN even when you handed it a local key, and --tlog-upload=false turning into a hard error instead of a quiet no-op. It is for platform and supply-chain engineers already running admission-time image verification who are moving up from v2.x. A signature answers where an image came from, not what got pulled into it, so pair this with dependency cooldowns on the way in.
Prerequisites
cosignv3.0.4 or later. Check withcosign version. The v3.0.x line through v3.1.1 is where the default flip and the early breakage fixes live.- Docker or any OCI client, plus push access to a registry. To exercise the new format end to end, your registry must support OCI 1.1 referrers. GHCR and recent registries do; Harbor needs 2.15.0 (see Common pitfalls).
- For the keyless path: a GitHub Actions runner with
id-token: writepermission. No long-lived keys. - An image you can push, referenced by digest. Signing a mutable tag signs whatever the tag happened to point at, which is rarely what you mean.
Where does a v3 signature actually go?
Two forks decide whether the upgrade is invisible or painful: whether your registry speaks OCI 1.1 referrers, and whether the thing checking signatures at admission time reads the new bundle.
Step-by-step
1. Confirm your version and the default that changed
cosign version
# cosign: v3.0.4 (or later)
In v3 the new bundle format is on by default. The Sigstore blog states --new-bundle-format, --trusted-root, and --use-signing-config all moved from opt-in to default-on. You no longer pass them. You now pass their negation when something downstream breaks.
2. Generate a key pair (skip if you go keyless)
cosign generate-key-pair
# writes cosign.key (encrypted private) and cosign.pub
Simplest path for a private registry. For CI, prefer keyless (Step 5) so there is no private key sitting somewhere to leak.
3. Resolve the digest, then sign by digest
IMG=ghcr.io/yourorg/app
DIGEST=$(docker buildx imagetools inspect "$IMG:latest" --format '{{.Manifest.Digest}}')
cosign sign --key cosign.key "$IMG@$DIGEST"
On v3 this writes a signature in the standardized bundle format (media type vnd.dev.sigstore.bundle.v0.3+json, per the Harbor issue thread) and stores it as an OCI 1.1 referring artifact instead of the legacy sha256-<digest>.sig tag. That storage change is the root of most upgrade surprises. Nothing about your signing command looks different. Where the signature lands does.
4. Verify with the public key
cosign verify --key cosign.pub "$IMG@$DIGEST" | jq .
A clean run prints the verified bundle JSON. If this hangs or errors on the network even though you supplied a local key, jump to Common pitfalls: v3.0.2 still tried to reach the TUF CDN in air-gapped setups (sigstore/cosign issue #4550).
5. Sign keyless from GitHub Actions
permissions:
id-token: write # required: mints the OIDC token Fulcio trusts
packages: write
steps:
- uses: sigstore/cosign-installer@v3
- run: cosign sign --yes "ghcr.io/${{ github.repository }}@${DIGEST}"
No --key. Cosign exchanges the workflow's OIDC token for a short-lived Fulcio certificate and records the signature in Rekor. There is no private key to steal because the certificate is ephemeral. This is the path I'd push any team toward for release artifacts.
6. Verify keyless by identity
cosign verify \
"$IMG@$DIGEST" \
--certificate-identity="https://github.com/yourorg/app/.github/workflows/release.yml@refs/heads/main" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" | jq .
Per the Cosign verification docs, one of --certificate-identity or --certificate-identity-regexp is mandatory for keyless flows, alongside --certificate-oidc-issuer. Pin the exact workflow path and ref. A regexp that matches any workflow in the org defeats the point of identity-based verification.
7. Wire verification into admission (Kyverno or policy-controller)
Keep enforcing at the cluster edge, but confirm your policy engine speaks the new bundle format before you flip the default fleet-wide. Test one namespace first:
kubectl run probe --image="$IMG@$DIGEST" -n verify-test
If the pod is rejected with a signature-not-found error while cosign verify on the CLI passes, your controller is still hunting for the legacy .sig tag. See Common pitfalls.
Verify it works
CLI verification should exit 0 and print a JSON bundle carrying the certificate subject and the Rekor entry. Then confirm independently that the signature actually exists as a referrer in the registry:
cosign tree "$IMG@$DIGEST"
# lists the attached signature/attestation artifacts
If cosign verify passes but your registry UI shows no signature, that is the OCI 1.1 referrer visibility gap, not a failed signature. The signature is there. The registry just can't render it yet.
Common pitfalls
- Harbor (and other registries) do not show the signature. Signatures created with the new bundle format carry media type
vnd.dev.sigstore.bundle.v0.3+json, which Harbor's signature detection did not recognize. goharbor/harbor issue #22401 targets the fix for Harbor 2.15.0. Until your registry supports it, sign with the legacy layout:cosign sign --new-bundle-format=false --key cosign.key "$IMG@$DIGEST". - Air-gapped verify still phones home. In sigstore/cosign issue #4550, v3.0.2 kept trying to reach the TUF CDN even with a local key on a Nexus-only network. The working offline shape is
cosign verify --key cosign.pub --offline --new-bundle-format=false --trusted-root trusted_root.json --local-image <dir>. Offline validation of the new protobuf bundle had not landed in an early v3 release, so--new-bundle-format=falseis the reliable disconnected path today. --tlog-upload=falsenow errors instead of silently skipping. Per the v3.0.4 release notes, disabling transparency-log upload is no longer allowed when--use-signing-config(now default) is set. Cosign fails before it writes the bundle. To sign without Rekor, also pass--use-signing-config=false.--bundleis now required where it was optional. The flag that names the output bundle file moved from optional to required in v3, so any script that omitted it will error on the bump.- CI cache signing regressed. moby/buildkit issue #6737 reports GitHub Actions cache signing breaking since cosign 3.0.4. If your build cache step fails right after the upgrade, pin the installer to a known-good version rather than chasing it live in CI.
- Signing a tag, not a digest. Always resolve and sign
@sha256:.... A tag is mutable, and you will eventually verify a different image than the one you signed. This one bites quietly, months later.
FAQ
Why does Harbor not show my Cosign v3 signature? Harbor's detection did not recognize the vnd.dev.sigstore.bundle.v0.3+json media type, and the signature now sits as a referring artifact rather than a .sig tag. Issue #22401 targets Harbor 2.15.0; sign with --new-bundle-format=false until then.
How do I verify on an air-gapped network? cosign verify --key cosign.pub --offline --new-bundle-format=false --trusted-root trusted_root.json --local-image <dir>. The legacy layout is the dependable disconnected path while offline validation of the protobuf bundle catches up.
Can I still sign without uploading to Rekor? Yes, but --tlog-upload=false alone now errors. Pass --use-signing-config=false with it.
Do I need --certificate-identity for keyless verify? Yes, one of --certificate-identity or --certificate-identity-regexp, plus --certificate-oidc-issuer. Pin the workflow path and ref rather than a broad regexp.
Should I sign the tag or the digest? The digest. Resolve it first, then sign @sha256:..., because a tag can point somewhere else by the time anyone verifies it.
Wrap-up
Signing works the same way it did on v2; three behaviors underneath it changed quietly, and those are what to hold in your head: the new bundle format, OCI 1.1 referrer storage, and signing-config-driven Rekor upload. Two levers earn a place in your muscle memory, --new-bundle-format=false and --use-signing-config=false, and they keep you signing while Harbor, your air-gapped registry, and your admission controller catch up to the defaults.
Do this before you enforce anywhere: confirm your policy engine (Kyverno or Sigstore policy-controller) verifies the new bundle format in a test namespace, and version-pin cosign-installer in CI so a point release can't silently change your supply-chain guarantees overnight. And sign more than the image: attest the SBOM and its VEX exceptions so the bill of materials travels with the signature and can't be swapped in your registry.
A verified signature still proves only provenance. It does not stop a dependency from running code during your build, which is exactly what the arrayref build.rs attack showed. If you would rather have the signing loop and the admission policy wired into your own pipelines by someone who has already done the v3 bump, get in touch.

Comments
Be the first to comment.