GPG Signed Commits: A Love Story That Ends in Rage
You know how this goes. Some security-conscious part of your brain decides signed commits are a great idea. You spend 90 minutes generating a GPG key, uploading it to GitHub, configuring git, and feeling righteous. Then six months later you’re on a new machine, or you rotated keys, or your key expired, and suddenly git commit -S spits out:
error: gpg failed to sign the datafatal: failed to write commit objectYou Google it. You find a Stack Overflow thread from 2017 with 47 different answers and none of them work for your exact version of gpg, gpg-agent, pinentry, and whatever cursed combination of shell environment variables you’re running. It’s 11 PM. You have a deadline tomorrow. You turn off commit signing, tell yourself you’ll fix it later, and go to bed feeling like a fraud.
Here’s the thing: GPG was designed in 1997. It’s not bad software — it’s just a poor fit for developer identity workflows. The root problem is that GPG signs with a long-lived key tied to an email address, and managing that key lifecycle is entirely your problem. Rotation, revocation, cross-machine distribution — all manual, all error-prone, all completely beside the point of what you actually wanted: provable authorship of commits.
Sigstore has a different take. And it’s genuinely better.
What Sigstore Actually Is
Sigstore is a Linux Foundation project that provides open-source infrastructure for signing and verifying software artifacts. You’ve probably encountered it if you’ve looked at supply chain security recently — it’s what powers cosign for container image signing, which we covered in Keyless Container Signing with Cosign and GitHub OIDC. The core idea is the same here, applied to git commits.
The two Sigstore components that matter for commit signing:
Fulcio — a certificate authority that issues short-lived X.509 certificates. Critically, it doesn’t ask you to generate or manage a key. Instead, it hands you a cert valid for ~10 minutes, bound to your identity from an OIDC provider (GitHub, Google, Microsoft, or your own). The cert proves you are who you say you are right now, signed by a trusted CA, with no key management required.
Rekor — a transparency log. Every signature operation gets appended here. This means even after your ephemeral cert expires (and it will, in 10 minutes), there’s an immutable public record that you signed that artifact at that moment in time. Auditable forever. You cannot quietly un-sign something.
Gitsign is the piece that glues this to git. It’s a drop-in replacement for the gpg binary that git calls during commit signing. Same interface, completely different backend — it talks to Fulcio and Rekor instead of your local GPG keyring.
Installing Gitsign
Gitsign is a single binary. Pick your install method:
# Homebrew (macOS / Linux)brew install sigstore/tap/gitsign
# Direct binary (Linux, pick your arch)curl -sSfL https://github.com/sigstore/gitsign/releases/latest/download/gitsign_linux_amd64 \ -o /usr/local/bin/gitsign && chmod +x /usr/local/bin/gitsign
# Go installgo install github.com/sigstore/gitsign@latestVerify it landed:
gitsign --versionThat’s literally it. No daemon, no keyring setup, no gpg --gen-key ritual.
Wiring It Into Git
Two config changes, global or per-repo:
# Tell git to use gitsign instead of gpggit config --global gpg.x509.program gitsigngit config --global gpg.format x509
# Turn on signing for all commitsgit config --global commit.gpgsign trueIf you only want this for a specific repo:
cd /your/repogit config gpg.x509.program gitsigngit config gpg.format x509git config commit.gpgsign trueOne optional quality-of-life setting — this tells gitsign to match your identity by email rather than requiring the full OIDC issuer URL, which makes git log --show-signature less noisy:
git config --global gitsign.connectorID https://github.com/login/oauthIf you use Google identity instead:
git config --global gitsign.connectorID https://accounts.google.comYour First Signed Commit
Make a commit like you normally would:
git commit -m "Add deployment config"Gitsign intercepts the signing step and opens a browser window — your default OIDC flow. Log in with GitHub (or Google, or whatever you configured). The browser closes, the commit completes. The whole thing takes maybe 5 seconds after you authenticate.
What just happened under the hood:
- Gitsign requested a signing certificate from Fulcio, presenting your OIDC token as proof of identity
- Fulcio issued an ephemeral X.509 cert valid for 10 minutes, bound to your email from the OIDC provider
- Gitsign signed the commit with that cert
- The signature was recorded in Rekor’s transparency log
- The cert was embedded in the commit
The cert is now expired. That’s fine — the Rekor record is what matters for later verification.
Verifying Signatures
Local verification:
git verify-commit HEADExpected output (healthy):
tlog index: 142857301gitsign: Signature made using certificate ID 0xdeadbeef... | CN=sigstore-intermediate,O=sigstore.devgitsign: Good signature from [[email protected]]Validated against Rekor transparency logFor the full log view:
git log --show-signatureYou’ll see the certificate subject (your email), the Rekor index, and a confirmation it validated against the transparency log. Compare that to the GPG equivalent, which either says Good signature from... or tortures you with key trust levels and expired subkey warnings.
If you want to verify a specific commit programmatically:
--certificate-oidc-issuer=https://github.com/login/oauth \ $(git rev-parse HEAD)This is the format you’d use in a CI pipeline or a policy check.
CI Verification
This is where keyless signing really shines. In GPG land, your CI pipeline needs the public key imported somewhere. In Gitsign land, it just needs gitsign installed and the right flags.
GitHub Actions example:
# In your workflow- name: Install gitsign run: | curl -sSfL https://github.com/sigstore/gitsign/releases/latest/download/gitsign_linux_amd64 \ -o /usr/local/bin/gitsign && chmod +x /usr/local/bin/gitsign
- name: Verify latest commit signature run: | gitsign verify \ --certificate-identity=${{ github.event.pusher.email }} \ --certificate-oidc-issuer=https://github.com/login/oauth \ ${{ github.sha }}No secrets. No key distribution. The commit either validates against the public Rekor log or it doesn’t.
You can also use gitsign-credential-cache if you’re in an environment where the browser OIDC flow isn’t an option (headless CI that’s creating commits, for example). That’s a more advanced setup involving a running credential cache daemon, but it exists if you need it.
The Caveats (Because Nothing Is Free)
10-minute cert expiry is by design, not a bug. The whole point is that you don’t have long-lived keys to leak or rotate. Your identity comes from the OIDC provider fresh every time. But this means: you cannot sign commits offline. If you’re on a plane with no internet, git commit will hang waiting for Fulcio. Factor that in.
Rekor is a public transparency log. Every commit you sign gets recorded publicly with your email and the repository context. For most open-source work this is a feature — it’s auditable. For private work where you’d rather not have your commit timing and email address in a public log, this is worth thinking about. You can run a private Rekor instance, but that’s an ops project.
git log --show-signature spits out raw certificate data when gitsign isn’t installed on the machine doing the viewing. It’s not unreadable, but it’s ugly. Anyone verifying your signatures needs gitsign installed too. Not a dealbreaker, but not invisible either.
GitHub’s verified badge. As of writing, GitHub’s web UI does not show the green “Verified” badge for gitsign-signed commits the way it does for GPG-signed ones. The signature data is in the commit — you can verify it yourself — but GitHub hasn’t added first-class UI support for Fulcio-based signatures yet. This is a GitHub limitation, not a Sigstore one, and it’ll probably change.
GPG vs Gitsign: The Honest Comparison
| GPG | Gitsign | |
|---|---|---|
| Key management | Your problem | Fulcio’s problem |
| Key expiry pain | Real and frequent | Certs expire in 10 min, silently |
| Identity source | Key you generated | OIDC provider |
| Offline signing | Yes | No |
| Public audit trail | No | Yes (Rekor) |
| CI setup | Import public key | Install binary + flags |
| New machine setup | Export/import key | Log in with GitHub |
| ”it’s 2 AM” survivability | Low | High |
If you’re doing supply chain security seriously — especially in a team environment — the auditability of Rekor is a genuine upgrade over GPG. You get a timestamped, tamper-evident record of who signed what and when, backed by a CA that didn’t require you to manage a keyserver.
If you need offline signing or you’re working on air-gapped systems, GPG is still your only real option. But honestly, if you’re on an air-gapped system, you’ve got bigger problems than your signing tool.
Quick Reference
# Installbrew install sigstore/tap/gitsign
# Configure globallygit config --global gpg.x509.program gitsigngit config --global gpg.format x509git config --global commit.gpgsign true
# Commit (triggers browser OIDC flow)git commit -m "your message"
# Verifygit verify-commit HEAD
# Verify with explicit identitygitsign verify \ --certificate-oidc-issuer=https://github.com/login/oauth \ $(git rev-parse HEAD)Worth It?
For personal projects: honestly, optional. Signed commits are more “good hygiene” than “essential security” at the single-developer scale.
For team projects, OSS contributions, or anything touching a software supply chain: absolutely worth it. Gitsign removes the single biggest barrier to commit signing adoption, which was always the GPG setup tax. The Rekor audit trail is a genuine capability upgrade.
And you’ll never again see gpg failed to sign the data at 11 PM. That alone is worth something.