Did you know that anyone can commit code using your name and email without needing your password or any form of authentication? This isn’t a newly discovered vulnerability — it’s how Git has always worked, and most developers never think about it until it happens to them.
The real problem: Git doesn’t verify committer identity
Git doesn’t verify the identity of whoever is making a commit by default. All it takes is two simple commands:
git config user.email "[email protected]"
git config user.name "Nguyen Van A"
That’s it — from that point on, every commit on that machine will carry the name “Nguyen Van A,” even if the person making it isn’t him. GitHub and GitLab both display avatars based on email, but the commits themselves have no authentication mechanism whatsoever.
This happens more often than you’d think:
- A team member accidentally uses the wrong email in their git config on a new machine
- A CI/CD machine is configured with the wrong identity
- A bad actor wants to forge commit history in an open source project
Why is Git so easy to impersonate?
Git was designed with a distributed architecture — there’s no central server to verify identity. The email and name in git config are just plain metadata, no different from writing your name on a piece of paper.
Git never queries any server to check whether that email is real, or whether the user has the right to commit under that email identity. This is an intentional trade-off: Git prioritizes speed and offline capability, while identity security is the responsibility of the layer above — the hosting platform, CI/CD system, or team conventions.
Three approaches — and only one that actually works
Approach 1: Trust the platform (not recommended)
GitHub and GitLab match commit emails against registered accounts. But if someone knows your email and uses it in their git config, the platform will still show your avatar — even though the person who committed isn’t you.
Approach 2: Use SSH keys for pushing (protecting the wrong thing)
SSH keys authenticate who has permission to push to a repo, but they don’t authenticate who created each individual commit. These are two completely different things.
Approach 3: GPG Signed Commits (the right way)
GPG (GNU Privacy Guard) lets you cryptographically sign each commit. This encrypted signature can only be produced with your private key, and anyone can verify it using your public key. This is the only method that cryptographically guarantees a commit was created by the right person.
Doing it right: Setting up GPG Signed Commits step by step
Step 1: Install GPG
Ubuntu/Debian:
sudo apt install gnupg
macOS:
brew install gnupg
Verify the installation:
gpg --version
Step 2: Generate a GPG key pair
gpg --full-generate-key
When prompted, choose:
- Key type: RSA and RSA (option 1)
- Key size: 4096 bits (more secure than 2048)
- Expiry:
1y— always set an expiry, don’t leave it permanent - Name and email: must exactly match your
git config user.nameanduser.email
Step 3: Get your Key ID
gpg --list-secret-keys --keyid-format=long
The output looks like this:
sec rsa4096/3AA5C34371567BD2 2024-01-15 [SC] [expires: 2025-01-15]
1234567890ABCDEF1234567890ABCDEF12345678
uid [ultimate] Nguyen Van A <[email protected]>
The Key ID is the part after the / — in this case 3AA5C34371567BD2. Copy it for use in the next steps.
Step 4: Configure Git to use your GPG key
git config --global user.signingkey 3AA5C34371567BD2
git config --global commit.gpgsign true
The commit.gpgsign true option enables auto-signing for all commits — no need to remember the -S flag every time you commit.
Step 5: Add your public key to GitHub or GitLab
Export your public key as text:
gpg --armor --export 3AA5C34371567BD2
Copy the entire output (from -----BEGIN PGP PUBLIC KEY BLOCK----- to -----END PGP PUBLIC KEY BLOCK-----).
On GitHub: go to Settings → SSH and GPG keys → New GPG key, paste it in, and save.
Step 6: Test and verify
Commit as normal — no extra steps needed:
git commit -m "feat: add user authentication"
Verify the signature on your latest commit:
git log --show-signature -1
Output when signing succeeds:
commit a1b2c3d4e5f6...
gpg: Signature made Mon 15 Jan 2024 10:30:00 JST
gpg: using RSA key 3AA5C34371567BD2
gpg: Good signature from "Nguyen Van A <[email protected]>" [ultimate]
Author: Nguyen Van A <[email protected]>
Date: Mon Jan 15 10:30:00 2024 +0900
feat: add user authentication
A properly signed commit will display a green Verified badge on GitHub, right next to the commit message.
Handling common errors
Error: “gpg failed to sign the data”
GPG needs to know which terminal to use for the passphrase prompt. Add this to your ~/.bashrc or ~/.zshrc:
export GPG_TTY=$(tty)
Then reload:
source ~/.bashrc
Configuring pinentry on macOS
On macOS, it’s much more convenient to use pinentry-mac so the passphrase prompt appears as a popup instead of in the terminal:
brew install pinentry-mac
echo "pinentry-program /opt/homebrew/bin/pinentry-mac" >> ~/.gnupg/gpg-agent.conf
gpgconf --kill gpg-agent
Enforcing GPG signing across your entire team
Want to require it for the whole team? Go to GitHub → Repository Settings → Branches → Branch protection rules → enable “Require signed commits”. From then on, every commit merged into the protected branch must carry a valid GPG signature — no signature means no merge.
Verify in your CI/CD pipeline:
git verify-commit HEAD
Exit code 0 = valid signature, non-zero = missing or invalid signature.
Backing up your GPG key — a critical step that’s often skipped
I once lost important code from accidentally force-pushing the wrong branch — since then I’ve always been careful with any irreversible git operation. GPG keys are no different. Losing your private key means losing the ability to sign new commits with that key. Worse, if the key expires and you have no backup, you’ll have to generate a brand-new key from scratch — and every old commit will show as Unverified on GitHub.
Back up immediately after generating your key:
# Export both private and public keys
gpg --export-secret-keys 3AA5C34371567BD2 > my-gpg-private-key.asc
gpg --export 3AA5C34371567BD2 > my-gpg-public-key.asc
Store the .asc files on a USB drive or in a password manager (Bitwarden and 1Password both support file attachments). Don’t upload them to regular cloud storage.
To restore on a new machine:
gpg --import my-gpg-private-key.asc
Quick reference: commands to remember
# Generate a new key pair
gpg --full-generate-key
# List existing keys
gpg --list-secret-keys --keyid-format=long
# Export public key to add to GitHub
gpg --armor --export KEY_ID
# Configure git to use the key
git config --global user.signingkey KEY_ID
git config --global commit.gpgsign true
# Verify a signed commit
git log --show-signature -1
# Back up the private key
gpg --export-secret-keys KEY_ID > backup-private.asc
Setup takes about 15 minutes. In return, every commit you make carries a cryptographic signature — anyone can verify it, no one can fake it. If you contribute to large open source projects like curl or Kubernetes, many maintainers look for the Verified badge before reviewing code. It’s an implicit signal: this person knows what they’re doing.

