Keyless commit signing with Sigstore gitsign Jump to heading

Every long-lived signing key is a liability: it has to be generated, distributed to the machines that need it, protected at rest, rotated on a schedule, and revoked the moment a laptop is lost. For automated pipelines that liability is acute — a GPG or SSH private key baked into a CI secret is a standing credential that any compromised job can exfiltrate. Sigstore’s gitsign removes the key entirely. It signs each commit against a short-lived certificate minted from an OIDC identity, records the signature in a public transparency log, and throws the private key away seconds later. This page is a focused recipe within Verifying Signed Commits in CI Pipelines, which covers how automated pipelines produce and validate commit provenance.

When to use this approach Jump to heading

Reach for keyless signing when:

  • Your pipeline needs to produce signed commits — bumping a version, committing a changelog, applying an automated dependency update — and you do not want a durable signing key living in a CI secret store.
  • Human contributors sign locally but you want their identity bound to an SSO account (Google, GitHub, an internal OIDC provider) rather than a keyring they manage by hand.
  • You need public, tamper-evident provenance: a transparency-log entry that proves who signed what and when, independent of the Git host.
  • You are already invested in Sigstore for artifact signing (cosign) and want commit provenance from the same trust root.

Keyless signing is not the right fit if: your environment is air-gapped with no path to a Fulcio/Rekor deployment (run a private Sigstore stack or stay on static keys), or your verification consumers cannot tolerate an online transparency-log check on every commit. For the static-key alternatives and how they compare, see GPG vs SSH Commit Signing.

The diagram below shows the identity-to-signature flow: gitsign never persists a key; the certificate authority and transparency log do the remembering.

Keyless commit signing flow with gitsign, Fulcio, and RekorSequence: the signer obtains an OIDC identity token, gitsign generates an ephemeral keypair and requests a short-lived certificate from Fulcio, signs the commit, uploads the signature to the Rekor transparency log, then discards the private key. Verification later checks the certificate identity and the Rekor inclusion proof.OIDC identitySSO or ambient tokengitsignephemeral keypairFulcio CA~10-min certsign commitdetached x509 sigRekor loginclusion proofprivate key discardedno secret at rest

Step-by-step recipe Jump to heading

Step 1 — Install gitsign and confirm the binary Jump to heading

gitsign ships as a single static binary. Install it from a release, Homebrew, or the Go toolchain, then confirm it is on PATH:

# Homebrew (macOS / Linux)
brew install sigstore/tap/gitsign

# Or with Go 1.21+
go install github.com/sigstore/gitsign@latest

Verify the install resolves and reports a version:

# Expected: a semantic version string, e.g. gitsign version v0.x.y
gitsign --version
command -v gitsign

Step 2 — Point Git at gitsign as the x509 signing program Jump to heading

gitsign is invoked as Git’s signing helper using the x509 (S/MIME-style) signature format rather than GPG or SSH. Configure four settings — the format, the program, whether to sign by default, and the OIDC parameters:

# Use x509 certificates as the signature format
git config --global gpg.format x509
# Route x509 signing through gitsign instead of gpgsm
git config --global gpg.x509.program gitsign
# Sign every commit automatically
git config --global commit.gpgsign true
# Also sign tags
git config --global tag.gpgsign true

# OIDC parameters (defaults target the public Sigstore instance)
git config --global gitsign.fulcio         https://fulcio.sigstore.dev
git config --global gitsign.rekor          https://rekor.sigstore.dev
git config --global gitsign.issuer         https://oauth2.sigstore.dev/auth

The mechanics of gpg.format and gpg.x509.program are the same signing interface used by GPG and SSH — only the backing program changes. For the broader picture of how Git selects a signing backend, see GPG vs SSH Commit Signing.

Verify the configuration was written:

# Should print: x509  /  gitsign  /  true
git config --get gpg.format
git config --get gpg.x509.program
git config --get commit.gpgsign

Step 3 — Sign a commit interactively via OIDC Jump to heading

On a workstation, the first signed commit opens a browser to your OIDC provider. Authenticating returns a short-lived identity token that gitsign exchanges with Fulcio for a certificate:

# Triggers the OIDC browser flow on first use, then signs
git commit -m "chore: enable keyless signing"

The browser round-trip proves the human identity; gitsign then mints an ephemeral keypair, requests a ~10-minute certificate binding that identity to the public key, signs the commit object, uploads the entry to Rekor, and discards the private key. Nothing signing-related is written to your keyring.

Verify the commit carries an x509 signature and shows the expected identity:

# %G? prints the signature status; the signer line shows the OIDC identity
git log --format='%H %G? %GS' -1

Step 4 — Verify the signature and Rekor entry locally Jump to heading

git log --show-signature will report the certificate as coming from an unknown authority, because Git does not know the Sigstore trust root. Use gitsign verify instead — it validates the certificate chain to Fulcio and checks the Rekor transparency-log inclusion proof, and it lets you assert the expected identity:

# Assert both the signer identity and the OIDC issuer that vouched for it
gitsign verify \
  --certificate-identity="[email protected]" \
  --certificate-oidc-issuer="https://accounts.google.com" \
  HEAD

The --certificate-identity and --certificate-oidc-issuer flags are the security-relevant part: a signature is only meaningful if you constrain which identity and which issuer you accept. Verifying without them proves the commit was signed by someone Sigstore trusts, which is rarely what you want. This identity-constraint model is exactly what commit gates build on — see Commit Verification Gates for turning these assertions into enforced policy.

Confirm a good signature exits zero and a wrong identity exits non-zero:

# Exit 0 on match; non-zero if the identity or issuer does not match
gitsign verify --certificate-identity="[email protected]" \
  --certificate-oidc-issuer="https://accounts.google.com" HEAD && echo "verified"

Step 5 — Sign in CI using the ambient OIDC token Jump to heading

CI has no browser, so gitsign falls back to the platform’s ambient OIDC token. On GitHub Actions, granting id-token: write exposes the token-request environment variables that gitsign detects automatically — no interactive step, no stored key:

# .github/workflows/release-bump.yml
permissions:
  contents: write   # push the signed commit
  id-token: write   # mint the ambient OIDC token gitsign exchanges with Fulcio

jobs:
  bump:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install gitsign
        run: go install github.com/sigstore/gitsign@latest
      - name: Configure keyless signing
        run: |
          git config --global gpg.format x509
          git config --global gpg.x509.program gitsign
          git config --global commit.gpgsign true
          # gitsign auto-detects ACTIONS_ID_TOKEN_REQUEST_URL / _TOKEN in CI
      - name: Create signed commit
        run: |
          git config user.name  "release-bot"
          git config user.email "[email protected]"
          echo "1.4.2" > VERSION
          git add VERSION
          git commit -m "chore(release): bump to 1.4.2"
          git push

The signer identity in CI is the workflow, not a person — something like https://github.com/acme/app/.github/workflows/release-bump.yml@refs/heads/main, vouched for by the https://token.actions.githubusercontent.com issuer. That workflow-scoped identity is the value you constrain against downstream.

Verify the ambient flow produced a signature without any prompt in the job log:

# In the same job, after the commit step
git log --format='%G? %GS' -1

Step 6 — Gate the pipeline on gitsign verify Jump to heading

A signature nobody checks is theater. Add a verification step — in the same pipeline or a downstream one — that fails unless the commit was signed by an approved workflow identity:

      - name: Verify commit provenance
        run: |
          gitsign verify \
            --certificate-identity-regexp="^https://github.com/acme/app/\.github/workflows/.+" \
            --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
            HEAD

Use --certificate-identity-regexp when several workflows are legitimate signers; use the exact --certificate-identity when only one is. Verification reaches Rekor to confirm the transparency-log inclusion proof, so allow outbound access to the Rekor endpoint from the runner.

SAFETY WARNING: Rewriting history after signing — git rebase, git commit --amend, or git reset --hard on signed commits — silently drops or invalidates the signatures on every rewritten commit, and pushing the result will fail verification. Re-sign the rewritten range with git rebase --exec 'git commit --amend --no-edit -S' <base> and force-push only after confirming gitsign verify passes on each new SHA.

Confirm the gate actually blocks: temporarily assert a wrong issuer and check the step fails.

# Must exit non-zero — proves the gate is not a no-op
gitsign verify --certificate-oidc-issuer="https://wrong.example.com" HEAD || echo "gate blocks as expected"

Trade-offs vs static SSH/GPG keys Jump to heading

Keyless signing changes where trust lives — from a secret you hold to an identity a provider vouches for. That is a real improvement for automation, but it is not free.

DimensionStatic GPG / SSH keyKeyless (gitsign)
Secret at restLong-lived private key to store, protect, rotateNone — ephemeral key discarded after each commit
Identity bindingKey fingerprint you distribute out of bandOIDC identity (SSO / workflow) bound by Fulcio
RevocationManual: distribute revocation, update allowlistsAutomatic: cert expires in ~10 minutes
VerificationOffline against a known public keyOnline Rekor check (offline possible with pinned roots)
CI credential riskKey in a CI secret can be exfiltratedAmbient token is short-lived and job-scoped
Audit trailWhatever your host recordsPublic, tamper-evident transparency log
Failure modeWorks offline; breaks if key lostDepends on Fulcio/Rekor availability

The decisive factors are usually availability and network posture. Keyless trades a storage problem (guarding a durable secret) for an availability problem (reaching Fulcio and Rekor). For internet-connected CI producing bot commits, that trade strongly favors keyless. For air-gapped release signing, a hardware-backed static key — see Signing Git Commits with a YubiKey — or a private Sigstore deployment is the pragmatic choice.

Validation checklist Jump to heading

Before treating keyless signing as production-ready, confirm each item:

Frequently asked questions Jump to heading

If there is no key, what does gitsign actually sign with? Jump to heading

gitsign generates a fresh keypair in memory for each commit, exchanges a short-lived OIDC identity token with Sigstore’s Fulcio certificate authority for an X.509 certificate that binds that identity to the public key, signs the commit, records the signature in the Rekor transparency log, and then discards the private key. The certificate is valid for only about ten minutes, so there is no durable secret to store, distribute, or rotate — the transparency log and the certificate chain carry the proof instead of a key you hold.

How does gitsign sign in CI without an interactive browser? Jump to heading

In CI, gitsign uses the platform’s ambient OIDC token instead of the browser flow. On GitHub Actions, granting the job id-token: write exposes ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN, which gitsign detects and exchanges with Fulcio non-interactively. The signed identity is the workflow’s identity — the repository and workflow ref, vouched for by the token.actions.githubusercontent.com issuer — not a human. You then constrain verification against that workflow identity.

Does keyless verification require network access to Sigstore? Jump to heading

By default gitsign verify checks the Rekor transparency log, which requires reaching a Rekor instance. For air-gapped or reproducible verification, pin the Fulcio root and Rekor public keys into a trusted-root bundle and verify offline against a cached inclusion proof. Most teams run online verification inside CI, where outbound access to Rekor is available, and reserve offline verification for release-artifact attestation where the trust roots are bundled with the artifact.