Git Secrets Management and Pre-commit Hooks
Preventing secrets from entering repositories using pre-commit hooks, secret scanning tools, and automated detection. Protect API keys, tokens, and credentials from accidental commits.
Introduction
Every day, developers accidentally commit secrets to Git repositories. API keys, database passwords, private certificates, and authentication tokens end up in version control, where they’re instantly exposed to anyone with repository access — and potentially to the entire internet if the repository is public.
Once a secret is committed, it’s in the Git history forever. Even if you delete it in a subsequent commit, the original value remains in the object database. The only true fix is to rotate the compromised credential and rewrite history — an expensive and error-prone process.
This comprehensive guide covers prevention strategies: pre-commit hooks that block secrets before they’re committed, scanning tools that detect leaked credentials, and operational practices that keep your repositories clean. Prevention is orders of magnitude cheaper than remediation.
When to Use / When Not to Use
When to implement secret prevention:
- Every repository — without exception
- Especially public/open-source repositories
- Before onboarding new developers
- After any secret leak incident
- As part of CI/CD pipeline security
When not to rely solely on prevention:
- For secrets already in history — use history rewriting tools
- As a replacement for secret rotation — always rotate leaked secrets
- For encrypted secrets — use proper secret management systems
Core Concepts
Secret prevention operates at multiple layers:
graph TD
DEV["Developer"] -->|commits| HOOK["Pre-commit Hook\nlocal detection"]
HOOK -->|blocks| SECRET["Secret Detected"]
HOOK -->|allows| CLEAN["Clean Commit"]
CLEAN -->|pushes to| REMOTE["Remote Repository"]
REMOTE -->|scans with| SERVER["Server-side Scanning\nGitHub/GitLab secret detection"]
SERVER -->|alerts| ALERT["Security Alert"]
SECRET -->|rotates| ROTATE["Rotate Credential"]
ALERT -->|rotates| ROTATE
The defense-in-depth approach: local hooks catch most mistakes, server-side scanning catches what slips through, and monitoring detects any remaining leaks.
Architecture or Flow Diagram
flowchart LR
CODE["Code Change"] -->|git add| STAGED["Staged Files"]
STAGED -->|git commit| PRE_COMMIT["Pre-commit Hook"]
PRE_COMMIT -->|scans with| DETECT["Secret Detection Engine"]
DETECT -->|regex patterns| PATTERNS["Pattern Matching\nAWS keys, tokens, passwords"]
DETECT -->|entropy analysis| ENTROPY["High Entropy Detection\nrandom-looking strings"]
PATTERNS --> RESULT{"Secret found?"}
ENTROPY --> RESULT
RESULT -->|yes| BLOCK["Block Commit\nshow file:line"]
RESULT -->|no| COMMIT["Create Commit"]
BLOCK -->|developer fixes| CODE
COMMIT -->|pushes to| REPO["Repository"]
Step-by-Step Guide / Deep Dive
Pre-commit Framework
The pre-commit framework is the industry standard for managing Git hooks:
# Install pre-commit
pip install pre-commit
# Create configuration
cat > .pre-commit-config.yaml << 'EOF'
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
EOF
# Install the hooks
pre-commit install
# Run manually on all files
pre-commit run --all-files
Gitleaks
Gitleaks is a fast, comprehensive secret scanner:
# Install
brew install gitleaks
# Scan current repository
gitleaks detect
# Scan with verbose output
gitleaks detect -v
# Scan specific commit range
gitleaks detect --log-opts="main..HEAD"
# Generate a baseline (allowlist known false positives)
gitleaks detect --baseline .gitleaks-baseline.json
# Custom configuration
cat > .gitleaks.toml << 'EOF'
title = "Custom Gitleaks Config"
[extend]
useDefault = true
[[rules]]
id = "custom-api-key"
description = "Custom API Key"
regex = '''CUSTOM_API_KEY\s*=\s*['"]?[a-zA-Z0-9]{32,}['"]?'''
tags = ["custom", "api-key"]
EOF
Detect-secrets
Yelp’s detect-secrets uses baseline files to reduce false positives:
# Install
pip install detect-secrets
# Create initial baseline
detect-secrets scan > .secrets.baseline
# Audit the baseline (review each finding)
detect-secrets audit .secrets.baseline
# Check against baseline in pre-commit
detect-secrets-hook --baseline .secrets.baseline $(git diff --cached --name-only)
# Update baseline after adding allowlisted secrets
detect-secrets scan --baseline .secrets.baseline
Git-secrets (AWS)
AWS’s git-secrets focuses on AWS credentials but supports custom patterns:
# Install
brew install git-secrets
# Install hooks in repository
git secrets --install
# Add AWS patterns
git secrets --register-aws
# Add custom patterns
git secrets --add 'password\s*=\s*.+'
# Scan entire history
git secrets --scan-history
# Scan staged changes
git secrets --scan
Environment Variable Patterns
Never commit secrets — use environment variables and .env files:
# .env (NEVER commit this)
DATABASE_URL=postgresql://user:password@localhost/db
API_KEY=sk-1234567890abcdef
SECRET_KEY=your-secret-key-here
# .env.example (SAFE to commit - template only)
DATABASE_URL=postgresql://user:password@localhost/db
API_KEY=your-api-key-here
SECRET_KEY=your-secret-key-here
# .gitignore
.env
.env.local
.env.*.local
!.env.example
Production Failure Scenarios + Mitigations
| Scenario | Symptoms | Mitigation |
|---|---|---|
| Secret committed | Credential exposed in history | 1. Rotate immediately 2. Rewrite history 3. Scan for usage |
| False positives block commits | Legitimate code blocked | Add to baseline/allowlist; refine patterns |
| Pre-commit hook bypassed | git commit --no-verify | Server-side scanning as backup |
| Secret in CI/CD logs | Logs contain credentials | Use secret masking; rotate exposed secrets |
| Shared development credentials | Multiple devs using same key | Use per-developer credentials; vault systems |
Trade-offs
| Aspect | Advantage | Disadvantage |
|---|---|---|
| Pre-commit hooks | Catches secrets before commit | Can be bypassed with --no-verify |
| Server-side scanning | Cannot be bypassed | Secret already in history |
| Entropy detection | Finds unknown secret types | Higher false positive rate |
| Pattern matching | Low false positives | Misses unknown secret formats |
| Baseline files | Reduces noise over time | Requires maintenance |
Implementation Snippets
# Custom pre-commit hook for secret detection
cat > .git/hooks/pre-commit << 'HOOK'
#!/bin/bash
# Check for common secret patterns
if git diff --cached -U0 | grep -E \
'(password|api_key|secret|token)\s*[:=]\s*["\x27][^"\x27]{8,}'; then
echo "ERROR: Potential secret detected in staged changes."
echo "Use environment variables or a secrets manager instead."
exit 1
fi
HOOK
chmod +x .git/hooks/pre-commit
# Scan for specific secret types
grep -rn "AKIA[0-9A-Z]{16}" . # AWS Access Keys
grep -rn "ghp_[a-zA-Z0-9]{36}" . # GitHub Personal Access Tokens
grep -rn "sk-[a-zA-Z0-9]{48}" . # OpenAI API Keys
# Rotate and clean up
# 1. Rotate the secret in the service
# 2. Remove from history (see Removing Sensitive Data post)
# 3. Update all references
Observability Checklist
- Monitor: Secret detection alerts from pre-commit and server-side scanners
- Track: Number of false positives (tune patterns to reduce)
- Alert: Any secret detected in repository history
- Verify: All team members have pre-commit hooks installed
- Audit: Periodic full-history scans with gitleaks
Security/Compliance Notes
- Pre-commit hooks are local — each developer must install them
- Server-side scanning (GitHub Secret Scanning, GitLab Secret Detection) provides backup
- Always rotate compromised secrets — removing from history is not enough
- Use secret management systems (HashiCorp Vault, AWS Secrets Manager) for production
- See Removing Sensitive Data from History for cleanup
Common Pitfalls / Anti-Patterns
- Relying only on
.gitignore— it’s not a security control - Not rotating leaked secrets — removing from history doesn’t invalidate them
- Ignoring false positives — leads to hook fatigue and bypassing
- Committing
.envfiles — even with fake values, the pattern encourages mistakes - Not scanning history — old commits may contain secrets added before hooks were installed
Quick Recap Checklist
- Install pre-commit hooks with gitleaks or detect-secrets
- Configure server-side secret scanning on your Git platform
- Use
.env.exampletemplates, never commit.envfiles - Rotate any secret that was ever committed
- Maintain baseline files to reduce false positives
- Scan full history periodically, not just new commits
- Use secret management systems for production credentials
Interview Q&A
Git stores every version of every file. Deleting a secret in a new commit doesn't remove it from the old commit where it was introduced. Anyone with access to the repository can still read the old commit. You must rewrite history using tools like git filter-repo or BFG Repo-Cleaner, then force-push and have all collaborators re-clone.
Pattern-based detection uses regex to match known secret formats (AWS keys start with AKIA, GitHub tokens start with ghp_). It has low false positives but misses unknown formats. Entropy-based detection flags high-randomness strings that look like secrets regardless of format. It catches unknown secret types but produces more false positives. Best practice: use both.
Use baseline files (detect-secrets) or allowlists (gitleaks) to mark known false positives. For example, test fixtures containing fake API keys should be allowlisted. The key is to review each finding initially, then maintain the baseline as code evolves. Never disable scanning entirely.
No. Developers can bypass with git commit --no-verify, and hooks only run locally. That's why you need defense in depth: pre-commit hooks catch most mistakes, server-side scanning catches bypasses, and monitoring detects any secrets that reach production. No single layer is sufficient.
Production Failure: Secret in History + Hook Bypass
Scenario: Secret leaked, developer bypassed hook
# What happened:
# 1. Developer ran: git commit --no-verify -m "Add config"
# 2. Committed .env file with AWS credentials
# 3. Pushed to public repository
# 4. Secret was scanned by bots within minutes
# Symptoms (detected later):
$ gitleaks detect --log-opts="--all"
Found 3 leaks
File: .env, Rule: AWS Access Key, Commit: abc123
# Immediate response:
# 1. ROTATE THE SECRET (do this FIRST, before anything else)
# Go to AWS IAM → Security Credentials → deactivate old key
# Generate new access key
# Update all services using the old key
# 2. Remove from history (see Removing Sensitive Data post)
git clone --mirror https://github.com/user/repo.git
cd repo.git
git filter-repo --path .env --invert-paths
git push --force --mirror
# 3. Notify team
echo "URGENT: AWS key was exposed. Old key rotated. Re-clone repo."
# 4. Prevent recurrence — enforce hooks on CI
# Add to CI pipeline:
# - name: Secret scan
# run: gitleaks detect --log-opts="--all"
# === Hook Bypass Prevention ===
# Developers can bypass local hooks with --no-verify
# Defense in depth:
# Layer 1: Local pre-commit hooks (convenience)
pre-commit install
# Layer 2: CI secret scanning (enforcement)
# GitHub Secret Scanning, GitLab Secret Detection
# Layer 3: Server-side pre-receive hooks (hard block)
# Requires self-hosted Git server
# Layer 4: Monitoring (detection)
# Cloud provider alerts for unusual API usage
Trade-offs: Pre-commit vs CI Scanning
| Aspect | Pre-commit Hooks | CI Scanning |
|---|---|---|
| Speed | Instant (local, < 1s) | Minutes (pipeline queue + run) |
| Coverage | Staged files only | Full repository history |
| Enforcement | Can be bypassed (--no-verify) | Cannot be bypassed (blocks merge) |
| Feedback | Immediate, before commit | After push, during PR |
| Setup | Per-developer (each must install) | Central (configured once) |
| False positives | Blocks developer workflow | Blocks PR merge |
| Secret types | Known patterns + entropy | Known patterns + entropy + cloud provider APIs |
| Best for | Catching mistakes early | Catching what slips through |
Recommendation: Use both. Pre-commit hooks catch 95% of mistakes instantly. CI scanning catches the 5% that bypass local hooks and scans full history.
Implementation: Secret Detection Tool Configuration
# === Gitleaks Configuration ===
# .gitleaks.toml
title = "Gitleaks Custom Config"
[extend]
useDefault = true # Use built-in rules
# Custom rules for your organization
[[rules]]
id = "company-api-key"
description = "Company API Key"
regex = '''COMPANY_API_KEY\s*=\s*['"]?[a-zA-Z0-9]{32,}['"]?'''
tags = ["company", "api-key"]
keywords = ["COMPANY_API_KEY"]
# Allowlist known false positives
[allowlist]
paths = ['''test/fixtures/''', '''mocks/''']
regexes = ['''example\.com''', '''placeholder''']
# === Detect-secrets Configuration ===
# Create baseline
detect-secrets scan > .secrets.baseline
# Audit each finding (mark real secrets vs false positives)
detect-secrets audit .secrets.baseline
# Interactive: mark each finding as "Secret" or "False Positive"
# .secrets.baseline format (auto-generated):
# {
# "generated_at": "2026-03-31T12:00:00Z",
# "results": {
# "config.py": [
# {
# "type": "Secret Keyword",
# "hashed_secret": "abc123...",
# "is_verified": false,
# "line_number": 5
# }
# ]
# }
# }
# === TruffleHog Configuration ===
# Install
# brew install trufflehog
# Scan repository
trufflehog git file://. --only-verified
# Scan with custom rules
# .trufflehog.yaml
rules:
company_secret:
description: "Company Secret Pattern"
regex: 'COMPANY_SECRET_[A-Z0-9]{20,}'
keywords:
- "COMPANY_SECRET_"
# Scan specific branch range
trufflehog git https://github.com/user/repo.git \
--branch=main \
--since-commit=HEAD~50 \
--only-verified
# === Pre-commit Integration ===
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
args: ["--baseline", ".gitleaks-baseline.json"]
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ["--baseline", ".secrets.baseline"]
- repo: local
hooks:
- id: trufflehog
name: TruffleHog
entry: trufflehog filesystem --only-verified
language: system
pass_filenames: true
Resources
Category
Related Posts
Removing Sensitive Data from Git History
Using git filter-repo, BFG Repo-Cleaner, and git filter-branch to scrub secrets, passwords, and credentials from Git history. Step-by-step remediation guide.
Signed Commits (GPG/SSH)
Complete guide to Git commit signing with GPG and SSH keys. Setup, verification, trust chains, and why signed commits matter for supply chain security.
Centralized vs Distributed VCS: Architecture, Trade-offs, and When to Use Each
Compare centralized (SVN, CVS) vs distributed (Git, Mercurial) version control systems — their architectures, trade-offs, and when to use each approach.