For about twenty years, code signing meant key custody. Generate a key, protect it for its lifetime, and accept that losing it costs you your identity while leaking it costs everyone else theirs. Every practice around it - HSMs, air-gapped signing machines, elaborate ceremonies - existed because the key was a long-lived secret that had to stay secret.
That problem is gone. Not mitigated, gone: in a Sigstore signature there's no key to keep, because the key is generated, used once, and thrown away inside the same command.
Which is a genuine win, and it moved the failure somewhere less visible. I covered Sigstore as one block in issue #005 back in February, at the level of "cosign signs your images". This is the other half: what you're actually trusting once the key is gone, and why the interesting bugs now live in the verify path rather than the sign path.
🏗️ Architectural Pattern: a certificate that expires before you finish reading this
The exchange
The flow is short enough to hold in your head.
You authenticate to an OIDC provider - Google, GitHub, your own issuer - and get an ID token. Cosign generates a throwaway keypair locally. It sends the public key and the token to Fulcio, the CA. Fulcio validates the token against the provider, then issues an X.509 certificate binding that public key to the identity in the token: the email or workflow reference goes in the Subject Alternative Name, and the OIDC issuer goes in a dedicated extension.
The certificate's valid for ten minutes.
You sign, the private key is discarded, and the signature plus the certificate go into a Rekor entry, which timestamps the fact that this certificate signed this digest while the certificate was still valid.
Why ten minutes is the point
A ten-minute certificate can't be revoked in any meaningful sense, and it doesn't need to be. Revocation infrastructure - CRLs, OCSP, the whole apparatus - exists because certificates outlive the circumstances that justified them. Shrink the window below the time it takes to notice and respond to a compromise and the problem stops being worth solving.
The cost is that a ten-minute certificate is useless as a verification anchor on its own. By the time anyone checks your signature, the certificate expired months ago. So verification can't ask "is this certificate valid now". It asks "was this certificate valid when the signature was made", and the only reason that question has an answer is the transparency log entry that timestamped it.
That inverts a dependency people don't expect. In classic signing, the log is an audit nicety. Here it's load-bearing: without a log entry, an expired certificate proves nothing.
What you're trusting instead
The key is gone and the trust didn't evaporate, it moved. A verifier is now trusting, in order: that the OIDC provider correctly authenticated whoever asked; that Fulcio correctly validated the token; that the log entry is genuine and inclusion-proven; and that the identity in the SAN is one you meant to accept.
The last one is yours. The first three belong to somebody else, which is fine - that's what a public good looks like - but it's worth saying plainly, because "keyless" gets read as "trustless" often enough to matter.
Links
🆚 The Showdown: Rekor v2 against the log it replaced
Rekor v2 reached GA this year, and it's an unusual upgrade in that most of the release notes are things being taken away.
Gone: signed timestamps returned by the log. Gone: the search index. Gone: entry types intoto, rekord, helm, tuf, rfc3161, jar, rpm, cose and alpine, leaving hashedrekord for artifacts and dsse for attestations. Gone: attestation storage, so you keep your attestations next to your artifacts like any other file.
Underneath, the Trillian backend was replaced with a tile-based log built on Tessera, and each shard now has its own URL along the lines of logYEAR-rev.rekor.sigstore.dev rather than hiding behind one endpoint.
The case for deleting things. A transparency log's only job is to make it impossible to sign something without leaving a permanent, publicly auditable trace. Every additional feature - a search index, ten entry formats, blob storage - is surface that has to stay correct forever, because a transparency log you have to migrate off is not much of a transparency log. Cutting to two entry types and a tile-based backend makes it cheap enough to run that other people can run their own, which is the actual goal.
The case against, and it's real. The search index going away means you can no longer ask the log "show me everything signed by this identity" without building that index yourself. For anyone doing detection - watching for signatures made by an identity that shouldn't be signing - that was the useful interface, and its replacement is "coming later as a separate verifiable service".
The client-side consequence is the one to plan for: shard URLs must come from the TUF root, not from your config. Hardcode a log URL today and you've built a thing that breaks on the next rotation. Rekor v1 keeps running in parallel and will eventually freeze for new uploads, with a year's notice.
Links
👮 The Policy: the verify command is the whole security boundary
Signing is now easy enough that people do it. Verification is where the work moved, and it's the half that gets copy-pasted.
Cosign v2 made two flags mandatory for keyless verification, and the reason is worth internalising:
cosign verify \
--certificate-identity 'https://github.com/acme/api/.github/workflows/release.yml@refs/heads/main' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
ghcr.io/acme/api:1.4.2Without those, "verify" means "some identity that Fulcio was willing to certify signed this", which is every GitHub account in existence. The command would succeed on an image signed by an attacker with a free account, and it'd look exactly like success.
The regex habit is the same bug wearing a hat. --certificate-identity-regexp='.*' shows up constantly, usually added to make a pipeline go green. It restores precisely the property the mandatory flags were introduced to remove. If you need a pattern, anchor it: pin the issuer exactly, and let the regex cover only what varies, like a tag or a branch under a repository you control.
Pin the workflow, not the repository. For CI identities the SAN contains the workflow file and the ref. Accepting any workflow in a repository accepts the one an attacker adds in a pull request, so the identity you pin should name the release workflow on the branch you protect.
The verification path is where the bugs are
If that sounds theoretical, January settled it. CVE-2026-22703 let a crafted bundle verify successfully even though its embedded Rekor entry didn't reference the artifact's digest, signature or public key. Cosign checked the log entry's signature and skipped the comparison that ties the entry to the thing in front of it, so any valid entry would do.
The practical effect is that an attacker who already had your identity could sign something and attach a Rekor entry pointing at an unrelated event, which removes the audit trail - the one property the log exists to provide.
Two details make it more instructive than an ordinary CVE. It affected cosign v2 users on default flags, while v3 users on default flags were unaffected. And it was a regression: the same class of bug had been fixed before and came back through a refactor.
Fixed in v2.6.2 and v3.0.4. Current releases are v3.1.3 and v2.6.5, and if your CI's pinned to a cosign version from last year, that's this week's fifteen-minute job.
The honest part
None of this makes an artifact good. A verified signature says a particular identity signed a particular digest at a particular time, and that's all it says. A compromised CI account produces perfectly valid signatures over malicious images.
What it buys you is attribution and an audit trail nobody can edit without being seen, which is a large improvement over an unsigned artifact and a smaller one than the word "verified" suggests.
And the monitoring gap is real. Sigstore's value proposition assumes somebody notices when an identity signs something it shouldn't. With the search index gone from Rekor v2, the tooling for that is thinner than it was, so if your threat model depends on detection rather than on after-the-fact forensics, that's a project rather than a flag.
Links
Where the difficulty went
The thing I keep noticing about this transition is that it didn't remove difficulty, it relocated it into a place with worse feedback.
Key custody was hard, and it was hard in a way that announced itself. Everyone knew the HSM was a chore. Nobody accidentally believed they had good key management when they didn't.
Identity-based verification is easy to get wrong and comfortable to leave wrong, because a misconfigured verify command returns zero and prints a checkmark. The pipeline is green, the dashboard says signatures are enforced, and the policy admits anyone with a GitHub account. There's no chore to remind you.
We checked ours after reading the January advisory. Two of nine pipelines pinned issuer and identity properly. Five used a regexp with .* somewhere in it. Two verified with --insecure-ignore-tlog, which somebody added during an incident in March and nobody removed.
Questions? Feedback? Reply to this email. I actually read them.
Ilia


