Skip to content

Sigstore Explained: Keyless Signing for Software

Lesson 6 of 10Advanced14 min readGit Security & DevSecOps · Supply Chain SecurityVerified: Sigstore project documentation; sigstore/cosign-installer v4, September 2026

Signing software has always been possible and has always been rare, for one practical reason: key management.

A signing key has to be generated, stored somewhere safe, made available to the build, protected from everyone who should not have it, rotated, and revoked if it leaks. Every one of those steps is where projects give up.

Sigstore’s contribution is to remove the key. Not to make key management easier — to make there be no long-lived key at all.

Sigstore replaces “a key you keep” with “an identity you can prove, right now”.

A keyless signing operation

A vertical sequence: obtain an OIDC identity token, generate an ephemeral key pair, exchange the token for a short-lived certificate from Fulcio, sign the artifact, record the signature in the Rekor transparency log, discard the private key.

OIDC identity tokenFrom GitHub, Google, or another providerEphemeral key pairGenerated for this signature onlyCertificate from FulcioBinds the key to the identity, valid for minutesSign the artifactWith the ephemeral private keyRecord in RekorAppend-only transparency logDiscard the private keyNothing long-lived remains

The result is a signature that anybody can verify, attributable to a specific identity, with no key anywhere for anybody to steal.

ComponentRole
FulcioThe certificate authority. Issues short-lived certificates binding a public key to an OIDC identity
RekorThe transparency log. An immutable, append-only ledger of signing events
CosignThe client. Signs and verifies artifacts, container images and attestations
GitsignSigns Git commits using the same infrastructure
Policy ControllerEnforces verification policy, typically as a Kubernetes admission controller

Two of these carry the concept.

The components are a moving target — the project adds and renames things — so verify against the Sigstore documentation before depending on a specific name.

Fulcio solves the identity problem. You prove who you are to an identity provider you already use — GitHub, Google, an enterprise IdP — and Fulcio issues a certificate saying “this public key belongs to that identity, for the next few minutes”. The certificate records the identity in a way verifiers can assert on.

Rekor solves the timing problem. A short-lived certificate expires, so how does a signature made last year still verify? The transparency log records the signature, the certificate and the artifact digest at the moment of signing. A verifier checks the log entry to establish that the signature was made while the certificate was valid.

This is the part that seems backwards on first encounter, and it is the whole design.

A traditional signing key lives for years. Its value to an attacker grows with time and with what it has signed. Protecting it becomes a permanent operational obligation, and the failure mode — a leaked key — is catastrophic and hard to detect.

A Sigstore certificate is valid for minutes. There is no key to protect after the signing operation completes, because it has been discarded. There is nothing to rotate, nothing to revoke, and nothing a repository compromise can yield.

The trade is that verification depends on the transparency log. Without it, an expired certificate proves nothing. That dependency is deliberate, and it buys something in return: an auditable public record of every signature made.

Rekor is more than a workaround for certificate expiry.

Monitoring. An identity owner can watch the log for signatures made in their name. If your release identity signs something you did not release, the log is where that becomes visible — which is a detection capability that key-based signing does not offer at all.

Auditability. Anybody can enumerate what an identity has signed. For a public project that is transparency in the ordinary sense.

Non-repudiation, of a sort. An entry proves a signature existed at a point in time, recorded in an append-only structure many parties can check.

Detection of key compromise. Where key-based signing has to wait for someone to notice a bad artifact, the log offers the possibility of noticing the signing event — which is earlier, and which is the difference between finding out from your monitoring and finding out from a user.

The cost is that signing events are public. For a private artifact whose existence is sensitive, that matters — the log records the digest and the identity, not the content, but the existence of a release is itself information. Sigstore supports private deployments for this reason, and it is a real consideration for organisations whose product roadmap can be inferred from their release cadence.

Signing a container image:

Terminal window
{/* Keyless: obtains an OIDC token interactively, or from the CI environment */}
cosign sign ghcr.io/YOUR_ORG/YOUR_IMAGE@sha256:YOUR_DIGEST

Verifying it — and note that verification is where the policy lives:

Terminal window
cosign verify ghcr.io/YOUR_ORG/YOUR_IMAGE@sha256:YOUR_DIGEST \
--certificate-identity-regexp "^https://github.com/YOUR_ORG/YOUR_REPO/" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com

Those two flags are the assertion. --certificate-identity (or its regexp form) states which identity must have signed; --certificate-oidc-issuer states which identity provider must have vouched for that identity.

In a workflow:

permissions:
id-token: write
packages: write
contents: read
steps:
- uses: sigstore/cosign-installer@v4
- name: Sign the image
run: cosign sign --yes "ghcr.io/${REPO}@${DIGEST}"
env:
REPO: ${{ github.repository }}
DIGEST: ${{ steps.build.outputs.digest }}

id-token: write is what makes it keyless. Without it there is no OIDC token, Cosign has no identity to present to Fulcio, and there is nothing to bind a certificate to.

The same infrastructure applied to Git commits: sign with an ephemeral key bound to an OIDC identity rather than with a GPG or SSH key you manage.

The appeal is that it removes the key-management obstacle that keeps commit signing adoption low. The practical caveat is verification: GitHub’s Verified badge is based on GPG, SSH and S/MIME keys registered to an account, so a Gitsign-signed commit is not verified the same way. Verification uses Sigstore tooling instead.

That makes Gitsign a good fit for a project whose consumers verify with Sigstore anyway, and a poor fit for a team whose signing goal is the badge in the web interface. For most teams, SSH signing remains the pragmatic answer for commits, and Sigstore is for artifacts.

The public Sigstore instance is free and appropriate for open-source projects. Two situations make a private deployment worth considering.

Signing events are sensitive. The public log records digests and identities. For an organisation whose release cadence, product names or internal repository structure should not be public, that is a disclosure — not of content, but of activity.

The environment is air-gapped. Fulcio and Rekor are network services. A build that cannot reach them cannot sign keylessly.

A private deployment means running Fulcio and Rekor yourself, integrating your own OIDC provider, and distributing your own trust root to verifiers. That is real infrastructure with real operational requirements — the log in particular has to be highly available and durable, because a lost log invalidates every signature that depends on it.

The intermediate position that suits many organisations: use the public instance for artifacts you publish externally, where transparency is a feature, and accept that internal artifacts’ digests appear in a public log alongside them. The information disclosed by a digest and an identity is small, and it is worth weighing deliberately rather than assuming either way.

GitHub’s artifact attestations are built on Sigstore. The difference is how much you operate.

GitHub attestationsCosign directly
IdentityThe workflow’s, automaticallyWhichever OIDC identity you configure
SetupA permissions block and one stepInstall the tool, configure signing
Verificationgh attestation verifycosign verify with identity flags
StorageGitHub, optionally the registryWherever you push it
Outside GitHub ActionsNoYes
Custom predicatesWhat the actions supportAnything you construct

They interoperate, because both produce Sigstore-signed in-toto statements. An image attested by GitHub can be verified with cosign verify-attestation, which matters when the verification happens in a Kubernetes admission controller rather than in a workflow.

Use GitHub attestations for artifacts built in GitHub Actions, which is most cases. Use Cosign directly when you build elsewhere, sign something that is not a build output, or need a predicate the actions do not produce.

A signature says “this identity produced these bytes”. Cosign can also attach attestations — signed statements about the artifact, carrying a structured claim.

Terminal window
{/* Attach an SBOM as an attestation, keyless */}
cosign attest --yes --predicate sbom.spdx.json --type spdxjson ghcr.io/YOUR_ORG/YOUR_IMAGE@sha256:YOUR_DIGEST
Terminal window
{/* Verify it, asserting both the identity and the predicate type */}
cosign verify-attestation --type spdxjson --certificate-identity-regexp "^https://github.com/YOUR_ORG/YOUR_REPO/" --certificate-oidc-issuer https://token.actions.githubusercontent.com ghcr.io/YOUR_ORG/YOUR_IMAGE@sha256:YOUR_DIGEST

The --type flag matters on both sides. An artifact can carry several attestations of different types — provenance, SBOM, a vulnerability scan result, a test report — and verifying without specifying the type checks whichever ones happen to be there rather than the one you care about.

This is the mechanism by which everything in this cluster travels together. The image is signed; a provenance attestation says where it came from; an SBOM attestation says what is inside; and all three are bound to the same digest and verifiable against the same identity.

Verification in a pipeline covers the path you control. Policy Controller — Sigstore’s Kubernetes admission controller — covers everything arriving at the cluster, including images pushed by somebody bypassing your deployment workflow entirely.

The shape of a policy is a list of image patterns, the authorities that may have signed them, and the identity constraints:

apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: require-signed-images
spec:
images:
- glob: "ghcr.io/YOUR_ORG/**"
authorities:
- keyless:
url: https://fulcio.sigstore.dev
identities:
- issuer: https://token.actions.githubusercontent.com
subjectRegExp: "^https://github.com/YOUR_ORG/YOUR_REPO/\\.github/workflows/release\\.yml@.*$"

The subjectRegExp is the whole control, and it is worth writing carefully. Matching your whole organisation means any workflow in any repository can produce an admissible image. Matching the specific release workflow means an attacker needs to get code into that workflow’s path, which is a much harder problem — and one the repository controls in Repository Security are designed to make harder still.

Two operational realities to plan for before enabling enforcement.

Failure during an incident. A verification failure at 03:00 blocks a deployment somebody urgently needs. There must be a documented, visible way to proceed, or somebody will disable the policy and it will not come back.

Images you did not build. Base images, sidecars, operators, monitoring agents. A policy requiring signatures from your organisation blocks all of them, so real policies have per-namespace or per-pattern rules for third-party images — which is where most of the configuration effort actually goes.

The order that produces a working control rather than a signed artifact nobody checks.

  1. Sign one artifact keylessly in a workflow. id-token: write, the installer, one cosign sign step.

  2. Verify it by hand, with identity flags, from your laptop. This is where you learn what the flags assert.

  3. Verify it wrongly on purpose — no flags, then the wrong identity — and observe what each produces. The first passing is the lesson.

  4. Publish the expected identity where consumers will find it: which issuer, which subject. Without this, a consumer can verify that a signature exists and not that it is yours.

  5. Add verification to your deployment, failing on mismatch.

  6. Add attestations — provenance, then SBOM — so the signature carries a claim rather than only an identity.

  7. Move enforcement to admission, once failures are rare and the third-party image exceptions are worked out.

Steps 1 and 2 take an afternoon. Step 7 takes a quarter, mostly spent on step 7’s second bullet in the admission section above — the images you did not build.

It does not vouch for the artifact. A signature attributable to a known identity says who produced it. Nothing about quality or safety.

It does not tell you which identities to trust. That is your policy, and it is the part that requires thought — the infrastructure will happily verify a signature from anybody, because that is what an open signing system is for.

It does not eliminate trust. You trust Fulcio to issue certificates correctly, Rekor to be append-only and complete, and the OIDC provider to authenticate correctly. The trust is relocated to auditable public infrastructure, not removed.

It does not work offline for signing. Fulcio and Rekor are network services. Air-gapped environments need a private deployment, with all the operational weight that implies.

Threat. An artifact is deployed that was not produced by the identity you expect — a substituted image, a package published by somebody who obtained publish rights, an artifact built by a workflow nobody reviewed.

Attack surface. The registry, the artifact store, the publishing credentials, and the verification policy itself — which fails open in mundane ways.

Impact. An artifact of an attacker’s choosing running with your production access, delivered by your own channel.

Control. Sign at build time with an identity the build cannot fabricate; verify at use time against an identity you specify locally; refuse on mismatch.

Verification. Sign an artifact and verify it with the correct identity. Then verify it with a different expected identity and confirm the check fails. Then verify an artifact you did not sign at all. Three tests, five minutes, and they distinguish a working policy from a command that always succeeds.

The failure mode this model is really guarding against is not cryptographic. It is a verification step that passes for the wrong reasons — no identity flags, an over-broad regexp, or an empty variable that makes the constraint match everything.

“Keyless” invites the reading that nothing is trusted. Worth being precise, because the trust has moved rather than vanished.

The OIDC provider. GitHub’s assertion that this token belongs to that workflow in that repository is the foundation of the whole identity. A compromise there is a compromise of every signature bound to it.

Fulcio. That certificates are issued only to identities that actually authenticated.

Rekor. That the log is append-only and complete. A log that could be rewritten would let a backdated signature look legitimate.

The root of trust. The set of keys used to verify Fulcio and Rekor themselves, distributed through a signed trust root that clients update.

Your policy. That it asserts the right identity and fails closed.

Compared with key-based signing, this is a larger set of trusted parties — and they are public, auditable, operated as a community project, and monitored. Traditional signing has a smaller set with one entry: whoever has a copy of the key, which is unknown by construction.

Neither is trustless. Sigstore’s claim is that trust in monitored public infrastructure is easier to reason about than trust in the physical security of a file, and for most projects that is straightforwardly true.

Verifying without identity constraints. The single most consequential error, and the command still prints a success message.

Trusting an identity pattern that is too broad. --certificate-identity-regexp matching your whole organisation means any workflow in any of your repositories can produce a signature that passes — which includes a workflow added in a pull request, if that pull request can run with an identity token.

Omitting id-token: write. No OIDC token, no keyless signing, and a failure that reads as a generic permission problem rather than a missing capability.

Signing tags rather than digests. The tag can move after signing; the signature then describes an image that the tag no longer points at, and verification of the tag fails confusingly.

Assuming a signature means the artifact is safe. It means an identity produced it, and says nothing about what is inside — vulnerability scanning is a separate activity with a separate answer.

Ignoring the public log for private artifacts. Signing events are public in the public instance — digests and identities rather than content, and still enough to infer a release cadence and a repository structure.

Treating Gitsign as a route to the Verified badge. GitHub’s badge is based on registered GPG, SSH and S/MIME keys, so a Gitsign-signed commit verifies with Sigstore tooling and not in the web interface. That is a reasonable trade for some projects and a surprise for anyone expecting otherwise.

Sigstore trades a key you must protect forever for an identity you can prove for a minute, plus a public log that remembers you proved it. That is the whole design, and everything else follows from it.

  • Sigstore eliminates the long-lived signing key rather than making key management easier
  • Fulcio issues short-lived certificates binding an ephemeral public key to an OIDC identity
  • Rekor’s append-only log is what lets a signature verify after its certificate has expired
  • The log also enables monitoring: an identity owner can see signatures made in their name
  • Cosign signs and verifies; Gitsign signs commits; Policy Controller enforces at admission
  • id-token: write is what makes keyless signing possible in a workflow
  • Verification without --certificate-identity establishes essentially nothing
  • Signing events are public on the public instance, which matters for private artifacts
  • GitHub’s artifact attestations are Sigstore underneath, with less to operate

Use a disposable public repository and a container image.

  1. Build and push an image by digest. Sign it in a workflow with Cosign, keyless.

  2. Verify it without identity flags. Predict: does it pass? What has it established?

  3. Verify with --certificate-identity-regexp and --certificate-oidc-issuer. Compare the output.

  4. Change the regexp to match a different repository. Predict: does it fail?

  5. Look up the signing event in the Rekor log by artifact digest. Predict: what is recorded — the image, or a hash of it?

  6. Remove id-token: write and re-run. Predict: where does it fail, and does the error name the cause?

  7. Sign by tag instead of digest, then push a different image to the same tag. Re-verify. Predict: what does the signature now describe?

  8. Delete the repository and the image.

GitHub Actions Security ChecklistToken permissions, fork pull requests, script injection and supply chain — with the attack each item prevents.

The repository security templates — secrets management and least-privilege token guides — are in the Professional Toolkit.