Security
Commit Signing — Policy & Setup
Every commit that reaches main in this repository must carry a cryptographic signature that GitHub can verify. The policy is enforced in three independent layers, so a single bypass does not break the chain. This document explains the policy, walks through SSH and GPG setup, and lists the verification commands you can run locally before pushing.
Why
Closes #853. Local hooks alone are bypassable — git commit --no-verify, git push --no-verify, or unsetting DOTFILES_ALLOW_UNSKIPPED_PUSH all let an unsigned commit reach a remote if GitHub-side enforcement is missing. The chain below removes every escape hatch.
Enforcement layers
- Local commit hook —
dot_config/git/hooks/executable_commit-msgis deployed by chezmoi. It rejects an unsigned commit at thecommit-msgstage on the developer machine. - Local pre-push hook —
scripts/git-hooks/pre-pushrunsgit verify-commitagainst every commit in the push range. A single unverified commit aborts the push.--no-verifyskips this layer; the next two catch it. - GitHub Rulesets —
.github/rulesets/main.jsondeclaresrequired_signaturesonrefs/heads/main. The rule is part of the repo so it's reproducible across forks. Apply withgh ruleset import .github/rulesets/main.json. compliance-guard.ymlworkflow — runs on every PR targetingmain. Walks the commit range withgit verify-commitand marks unsigned commits in the PR summary; fails the workflow whenunsigned_count > 0.
A merge to main therefore requires (Ruleset accepts the push) AND (the workflow's signed-commit check passes) AND (the maintainer's push key is allowed). The protection holds even if a contributor's local hooks are missing or skipped.
Setting up SSH signing (recommended)
SSH signing is the 2026 default. It reuses your existing SSH key — no new key material, no GPG agent, no Kleopatra UI. GitHub has recognised SSH signatures since 2022.
# 1. Tell git to sign with SSH.
# 2. Point at the SSH key you want git to use.
# 3. Turn on auto-signing for every commit and tag.
# 4. Tell GitHub which SSH key signs your commits.
# 5. (Optional) Populate the allowed_signers file so
# `git log --show-signature` can verify locally.
After this, git log --show-signature shows Good "git" signature for [email protected] with ED25519 key SHA256:… on every new commit.
Setting up GPG signing (legacy / for tag-signing mirrors that don't yet support SSH)
# 1. Generate or import a key. ED25519 is the modern recommendation;
# RSA-4096 is the conservative fallback.
# 2. Find the long key ID.
# 3. Tell git which key.
# 4. Export and upload the public key to GitHub.
|
Verifying locally before pushing
# Every commit in the push range.
|
# Or the canonical command the pre-push hook uses:
for; do
&& ||
done
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
error: gpg failed to sign the data | gpg-agent isn't running, or GPG_TTY not exported | export GPG_TTY=$(tty) in your shell rc; restart agent with gpgconf --kill gpg-agent |
error: Load key "/.../id_ed25519": Permission denied | SSH key permissions too open | chmod 600 ~/.ssh/id_ed25519 |
| GitHub shows "Unverified" on a commit signed locally | Signing key not uploaded to GitHub | gh ssh-key add … --type signing (SSH) or gh gpg-key add (GPG) |
| Pre-push hook rejects a merge commit you didn't author | Upstream commit lacks a signature | Either pull the rebased branch, or fast-forward instead of merging |
Ruleset import via gh complains "invalid JSON" | Rulesets API expects the target + rules envelope, not just the rules array | Use the file as-is — gh ruleset import .github/rulesets/main.json |
Re-applying the ruleset after a manual edit
If someone edits the ruleset in the GitHub UI by mistake, the file-of-truth wins. Re-apply:
(or -X PUT against the existing ruleset's ID if it already exists).
References
.github/rulesets/main.json— the enforced policy..github/workflows/compliance-guard.yml— the workflow that fails PRs containing unsigned commits.scripts/git-hooks/pre-push— the local pre-push gate.dot_config/git/hooks/executable_commit-msg— the local commit-msg gate.- GitHub: About commit signature verification
- GitHub: Telling Git about your SSH key