Git Security¶
Git's cryptographic design provides integrity (every object is hashed), but it doesn't provide authentication or secrecy on its own. This guide covers signing commits to prove authorship, managing credentials safely, detecting and removing secrets from repositories, and securing your Git workflow end to end.
Why Sign Commits?¶
Anyone can set user.name and user.email to any value. Without signing, there's no proof that a commit was actually written by the person it claims. Signed commits use cryptographic signatures to prove that the committer holds a specific private key, and platforms like GitHub and GitLab display a "Verified" badge.
Two signing methods are available:
| Method | Key type | Git version | Platform support |
|---|---|---|---|
| GPG signing | GPG key pair | Any | GitHub, GitLab, Bitbucket |
| SSH signing | SSH key pair | 2.34+ | GitHub, GitLab |
GPG Signing¶
Setting Up GPG¶
# Generate a GPG key (choose RSA 4096 or Ed25519)
gpg --full-generate-key
# List your keys
gpg --list-secret-keys --keyid-format=long
# Output:
# sec ed25519/ABC123DEF456 2024-01-15 [SC]
# ABCDEF1234567890ABCDEF1234567890ABC123DE
# uid [ultimate] Jane Developer <jane@example.com>
The key ID after the algorithm (ABC123DEF456) is what you configure Git with:
# Tell Git which key to use
git config --global user.signingkey ABC123DEF456
# Sign all commits by default
git config --global commit.gpgsign true
# Sign all tags by default
git config --global tag.gpgsign true
Adding Your Key to a Platform¶
Export the public key and paste it into your platform's settings:
Copy the output (starting with -----BEGIN PGP PUBLIC KEY BLOCK-----) to:
- GitHub: Settings > SSH and GPG keys > New GPG key
- GitLab: User Settings > GPG Keys
Making Signed Commits¶
# Sign a single commit
git commit -S -m "Add signed authentication module"
# With commit.gpgsign = true, all commits are signed automatically
git commit -m "This is signed automatically"
# Create a signed tag
git tag -s v1.0 -m "Signed release 1.0"
SSH Signing (Git 2.34+)¶
SSH signing uses your existing SSH key - no GPG required. This is simpler if you already have SSH keys for authentication.
# Configure SSH signing
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
# Sign all commits
git config --global commit.gpgsign true
Allowed Signers File¶
For verifying SSH signatures locally, Git needs a list of trusted keys:
# Create an allowed signers file
echo "jane@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG8r..." > ~/.config/git/allowed_signers
# Tell Git about it
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
Verifying Signatures¶
# Verify a commit
git verify-commit HEAD
# Verify a tag
git verify-tag v1.0
# Show signatures in log
git log --show-signature
# Show signatures in one-line log
git log --oneline --format='%h %G? %s'
# %G? shows: G (good), B (bad), U (untrusted), N (no signature), E (expired)
Credential Management¶
Storing credentials safely is essential. Typing passwords or tokens for every push is impractical, but hardcoding them is dangerous.
Credential Helpers¶
Git's credential helpers cache or store authentication tokens:
# Cache in memory (default 15 minutes)
git config --global credential.helper cache
git config --global credential.helper 'cache --timeout=3600' # 1 hour
# macOS Keychain
git config --global credential.helper osxkeychain
# Windows Credential Manager
git config --global credential.helper manager
# Linux libsecret (GNOME Keyring)
git config --global credential.helper /usr/lib/git-core/git-credential-libsecret
Personal Access Tokens¶
All major platforms require tokens (not passwords) for HTTPS Git operations:
- GitHub: Settings > Developer Settings > Personal access tokens > Tokens (classic) or Fine-grained tokens
- GitLab: User Settings > Access Tokens
- Bitbucket: Personal Settings > App passwords
Use the token as your password when Git prompts. The credential helper caches it.
Never commit tokens
Tokens in your repository history are permanent (even after removal, they exist in old commits). Platforms scan for accidentally committed tokens and may revoke them, but the exposure window can be enough for damage.
Secret Scanning¶
Accidentally committing secrets (API keys, passwords, tokens, private keys) is one of the most common security mistakes. Prevention is far easier than cleanup.
Pre-Commit Detection¶
Install hooks that scan for secrets before they enter the repository:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
Dedicated Scanning Tools¶
Gitleaks scans repositories for hardcoded secrets:
# Install
brew install gitleaks # macOS
# Scan the current repo
gitleaks detect
# Scan with verbose output
gitleaks detect -v
# Scan specific commits
gitleaks detect --log-opts="HEAD~10..HEAD"
TruffleHog performs deep scanning including entropy analysis:
# Scan a repository
trufflehog git file://./
# Scan a remote repository
trufflehog github --repo https://github.com/user/repo
Platform Secret Scanning¶
- GitHub: Automatic secret scanning for public repos (free) and private repos (Advanced Security license). Detects known token formats from 100+ providers.
- GitLab: Secret Detection CI component scans MR diffs automatically.
Removing Secrets from History¶
When a secret has been committed, you need to rewrite history to remove it from every commit.
git filter-repo (Recommended)¶
git filter-repo is the modern replacement for git filter-branch. It's faster, safer, and easier to use:
# Install
pip install git-filter-repo
# Remove a file from all history
git filter-repo --path secrets.env --invert-paths
# Replace specific strings in all files across all history
git filter-repo --replace-text expressions.txt
The expressions.txt file maps secrets to replacements:
literal:sk_live_abc123def456==>***REDACTED***
regex:password\s*=\s*['"].*?[']==>password = "***REDACTED***"
BFG Repo-Cleaner¶
BFG is a simpler tool focused on common cleaning tasks:
# Remove a file by name
bfg --delete-files .env
# Replace strings
bfg --replace-text passwords.txt
# Remove large files
bfg --strip-blobs-bigger-than 100M
After either tool, force push the cleaned history:
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force --all
git push --force --tags
Force pushing after history rewrite
History rewriting changes every commit hash from the rewritten point onward. All collaborators must re-clone or carefully rebase their work. Coordinate with your team before force pushing. Notify everyone that the history has changed.
Security-Focused .gitignore¶
Prevent secrets from being committed in the first place:
# Secrets and credentials
.env
.env.*
!.env.example
*.key
*.pem
*.p12
*.pfx
credentials.json
service-account.json
**/secrets/
# SSH keys (if stored in repo for some reason)
id_rsa
id_ed25519
*.pub
# Cloud provider configs
.aws/credentials
.gcp/
Exercises¶
Further Reading¶
- Pro Git - Chapter 7.4: Signing Your Work - GPG signing commits and tags
- GitHub: Signing Commits - setting up GPG and SSH signing on GitHub
- GitLab: Signing Commits with GPG - GitLab GPG verification
- git-filter-repo Documentation - history rewriting tool
- Gitleaks - secret scanning tool
- TruffleHog - deep secret scanning
- OWASP Secret Management Cheat Sheet - comprehensive secret management guidance
Previous: Git Hooks and Automation | Next: Monorepos and Scaling Git | Back to Index