GitVerifying Signatures

Verifying Commit and Tag Signatures

Signing commits and tags is only half the story — verifying those signatures is equally important. Verification confirms that a commit was genuinely authored by the key holder and that the commit object has not been tampered with since signing. This page covers how Git stores signatures, all the tools for verification, and how to enforce signature requirements in your workflow.

How Git Stores Signatures

When you sign a commit, Git embeds the signature inside the commit object itself as a gpgsig header. The signature covers the entire commit object — tree hash, parent hash, author, committer, timestamp, and message. Any change to any of these fields after signing will invalidate the signature.

Inspect the raw commit object to see the embedded signature

Bash
git cat-file -p HEAD

Raw commit object with embedded GPG signature

Text
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
parent 9daeafb9864cf43055ae93beb0afd6c7d144bfa4
author Jane Smith <jane@example.com> 1673800365 -0500
committer Jane Smith <jane@example.com> 1673800365 -0500
gpgsig -----BEGIN PGP SIGNATURE-----

 iQEzBAABCAAdFiEEkpUkV5lUXv7FLx1XOqVcNDcVZ7IFAmPKa0UACgkQOqVcNDcV
 Z7I0twf/RKpI4WCb7qKzblEfOT7xQb5n+1A9...
 -----END PGP SIGNATURE-----

feat: add user authentication module

Implements JWT-based authentication with refresh tokens.
Fixes #42
git verify-commit

Verify a specific commit

Bash
# Verify HEAD
git verify-commit HEAD

# Verify a specific commit hash
git verify-commit 7d3e5f2a1b9c4d6e8f0a2b4c6d8e0f1a3b5c7d9e

# Verify multiple commits
git verify-commit HEAD HEAD~1 HEAD~2

Good signature output

Text
gpg: Signature made Tue Jan 15 14:32:45 2023 EST
gpg:                using RSA key 3AA5C34371567BD2
gpg: Good signature from "Jane Smith <jane@example.com>" [ultimate]

Bad signature output (tampered commit)

Text
gpg: Signature made Tue Jan 15 14:32:45 2023 EST
gpg:                using RSA key 3AA5C34371567BD2
gpg: BAD signature from "Jane Smith <jane@example.com>" [ultimate]
error: could not verify commit '7d3e5f2a'
git verify-tag

Verify a tag signature

Bash
git verify-tag v1.0.0

Good tag signature output

Text
object 7d3e5f2a1b9c4d6e8f0a2b4c6d8e0f1a3b5c7d9e
type commit
tag v1.0.0
tagger Jane Smith <jane@example.com> 1673800365 -0500

Release v1.0.0

gpg: Signature made Tue Jan 15 14:32:45 2023 EST
gpg:                using RSA key 3AA5C34371567BD2
gpg: Good signature from "Jane Smith <jane@example.com>" [ultimate]
git log --show-signature

Show signatures for all commits in log

Bash
# Full signature info for recent commits
git log --show-signature -5

# One-line summary with signature status
git log --format="%H %G? %GS" -10

Example --format output

Text
7d3e5f2a1b9c4d6e8f0a G Jane Smith <jane@example.com>
3b1a9c8d2e4f6a7b8c9d G Jane Smith <jane@example.com>
1f2e3d4c5b6a7980abcd N
9a8b7c6d5e4f3a2b1c0d N
4d3e2f1a0b9c8d7e6f5a U Unknown Key ID
Signature Status Codes

Code

Meaning

Action

G

Good — valid signature, key trusted

All clear

B

Bad — signature does not match the commit object (tampered)

Investigate immediately

U

Unknown — key not in your keyring, cannot verify

Import the signer's public key

X

Expired — signature used an expired key

Contact signer to re-sign with current key

Y

Expired OK — signature OK but key was already expired at time of signing

Acceptable if intentional

R

Revoked — key was revoked after signing

Treat as suspicious; investigate

E

Missing key — cannot check because key is unavailable

Import the public key

N

No signature — commit was not signed at all

Expected if signing not enforced

Scripting Signature Verification

Check all commits on current branch for valid signatures

Bash
# List any commits without a Good signature
git log --format="%H %G?" main..HEAD | grep -v " G$"

# Fail if any commit lacks a good signature (useful in CI)
UNSIGNED=$(git log --format="%H %G?" main..HEAD | grep -v " G$" | wc -l)
if [ "$UNSIGNED" -gt 0 ]; then
  echo "Error: Found commits without valid signatures"
  git log --format="%H %G? %an %s" main..HEAD | grep -v " G "
  exit 1
fi
Enforcing Signed Commits on GitHub
  • Go to Repository → Settings → Branches

  • Click Add branch protection rule or edit an existing rule for main

  • Enable Require signed commits

  • With this enabled, GitHub rejects any push that includes unsigned commits

  • This also blocks merging PRs that contain unsigned commits

Note
GitHub's "Require signed commits" protection only checks that the commit has a valid signature from a key registered with a GitHub account. It does not verify that the key belongs to the PR author specifically — it verifies that the key owner is a GitHub user with that email. Configure your team's allowed keys carefully.
CI Verification Example

.github/workflows/verify-signatures.yml

YAML
name: Verify Commit Signatures

on: [pull_request]

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Import team GPG keys
        run: |
          gpg --import team-keys/jane.asc
          gpg --import team-keys/bob.asc

      - name: Verify all commits in PR
        run: |
          git log --format="%H %G?" origin/main..HEAD | while read hash status; do
            if [ "$status" != "G" ]; then
              echo "Commit $hash has invalid or missing signature (status: $status)"
              exit 1
            fi
          done
          echo "All commits have valid signatures"
What to Do With a Bad Signature
Warning
A "B" (bad) signature status means the commit object was modified after signing. This is a serious security event — it indicates either tampering or a Git history rewrite performed after the commit was signed (such as a rebase or amend). Never silently ignore a bad signature. Investigate the source, check if the history was legitimately rewritten (e.g., the author rebased and forgot to re-sign), and treat it as a potential security incident if the modification is unexplained.