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 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
| 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-off Analysis
| 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 Considerations
- 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
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
Interview Questions
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.
Gitleaks uses regex-based and entropy-based detection out of the box with 150+ built-in rules for common secret patterns like API keys, tokens, and private keys. git-secrets (AWS) uses regex patterns only and requires manual configuration of allowed patterns. Gitleaks can scan entire Git history in one pass with gitleaks detect --source, while git-secrets scans staged changes via pre-commit hooks. Gitleaks is better for CI/CD scanning of historical repos; git-secrets excels at preventing leaks before they happen.
A .gitallowed file defines false-positive exceptions for secret scanning tools like Gitleaks. It specifies patterns or file paths that should be ignored during scanning, such as test credentials, example configuration files, or known placeholder values. Without it, every scan would flag the same non-secret values repeatedly, creating alert fatigue. Gitallowed files should be reviewed regularly to ensure they haven't grown to include actual secrets.
First, rotate the compromised secret immediately — invalidate the API key, token, or password at the service level. Then use git filter-repo or BFG Repo-Cleaner to remove the secret from Git history across all branches and tags. After cleanup, force-push the rewritten history and instruct all collaborators to reclone. Finally, add the secret to your scanning tool's rules and ensure pre-commit hooks prevent re-introduction. Rotation must happen before history rewriting because the rewrite doesn't invalidate cached or copied secrets.
.gitignore can prevent .env files from being tracked, but it only works for untracked files. If a .env file was ever committed and tracked, .gitignore will not stop it from appearing in status — you must use git rm --cached .env to untrack it. The best practice is to commit a .env.example file with placeholder values and document required variables, while adding .env to .gitignore before the first commit.
Scanning staged changes catches secrets before they enter the repository, which is the ideal detection point. Scanning full Git history catches secrets that already exist in the repository from before scanning was implemented. History scanning is essential for legacy repositories and security audits. Tools like Gitleaks and truffleHog can scan the entire commit graph, while pre-commit hooks only see current changes. A defense-in-depth strategy uses both: pre-commit hooks for prevention and CI/CD history scans for detection.
TruffleHog uses entropy-based detection as its primary method, scanning for high-entropy strings (like Base64-encoded secrets) without relying on specific regex patterns. Gitleaks primarily uses regex pattern matching with a comprehensive rule set. TruffleHog excels at finding novel or custom secret formats that don't match known patterns but risks more false positives. Gitleaks has lower false-positive rates for known secret types. Both are open-source and often used together in security-conscious organizations.
Key signals include: unexpected API calls from unknown IPs, billing anomalies in cloud provider accounts, 403/401 error spikes from invalidated credentials, new commits in private repos from unexpected collaborators, and GitHub secret scanning alerts for repos with public access. Organizations should set up automated alerts for these signals and have a runbook for each scenario that covers immediate rotation, history remediation, and post-mortem analysis.
Monorepos require per-team scanning configuration using tool-specific allowlists and path filters. Each team should have its own .gitleaks.toml or equivalent at the team directory level. Use path exclusions to avoid cross-team false positives. Consider sparse checkout and path-based access controls to limit which teams can see which directories. Centralize secret scanning at the CI level with per-team reporting so each team only sees alerts for their code.
Removing a secret from Git history only prevents future discovery through repository access. The secret was already exposed to everyone who had cloned the repository before the cleanup — they have a local copy in their .git directory. Attackers who accessed the repo during the exposure window may have extracted the secret. Additionally, CI/CD logs, cached builds, and third-party tools may have captured the secret. Rotation ensures the exposed credential can no longer be used regardless of where copies exist.
Secrets managers (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager) provide centralized access control, audit logging, and automatic rotation. Environment variables are simpler but lack audit trails and make rotation harder. Secrets managers add latency (network calls to fetch secrets) and operational complexity. For CI/CD pipelines, a hybrid approach works best: reference secrets via environment variables that are populated by the secrets manager at runtime, never storing actual values in repository configuration files.
Use a warning mode (non-blocking) for CI secret scanning that alerts the team while allowing the deployment to proceed. Implement a bypass mechanism with review — developers can override a scan failure by acknowledging the issue in the commit message and scheduling a remediation ticket. After the emergency, the same secret should be rotated and the approach re-evaluated. The goal is to prevent a hard block on secret scanning from causing a workaround that bypasses all security.
SOC 2 and PCI-DSS both require access control and monitoring of sensitive data. Secrets committed to Git represent an access control failure because anyone with repository access — including contractors, CI systems, and former employees with stale access — could view the secret. PCI-DSS Requirement 7 explicitly mandates need-to-know access. Git secret scanning with blocked commits, combined with automated history audits, helps demonstrate compliance. Most auditors will flag any committed secrets in findings if scanning wasn't in place.
Git-crypt is a transparent encryption tool for specific files in a Git repository, while secret scanning tools detect secrets that have already been committed. Git-crypt encrypts files based on .gitattributes rules and decrypts them only for authorized GPG key holders. Scanning tools are detective controls; git-crypt is a preventive control. They complement each other: git-crypt prevents secrets from being stored in plaintext, while scanning catches misconfigured files that git-crypt didn't protect.
Key practices include: developer training on secret hygiene, automated scanning at multiple stages (pre-commit, pre-receive, CI), regular audits of existing repositories using Gitleaks or truffleHog, secret-free templates that scaffold projects with proper .gitignore and .env.example files, pair programming during sensitive changes, and incident response drills for secret leak scenarios. A blameless culture where developers report accidental commits immediately (rather than trying to hide them) significantly reduces overall risk.
Use intentional secret injection tests where a test credential is deliberately committed to a test repository to verify detection fires. Maintain a baseline scan of known false positives and track new findings over time. Monitor detection latency — how long between commit and alert. Track false-positive rate (high rates cause alert fatigue) and true-positive rate. Periodically run a red-team exercise where someone attempts to commit a simulated secret to verify all detection layers fire correctly.
The future includes server-side Git hooks (pre-receive) as the default in code hosting platforms, AI-powered detection that learns project-specific patterns rather than relying on generic regex, native secret scanning built into GitHub/GitLab (already available as a feature), push-based secret masking that automatically redacts secrets in CI logs, and SOPS/age-based encryption becoming the standard for storing encrypted secrets in repos. The trend is shifting from detection-only toward prevention-through-encryption.
Further Reading
- Gitleaks Official Documentation — The most widely used open-source Git secret scanner with 150+ built-in patterns
- pre-commit Framework — Industry-standard framework for managing multi-language pre-commit hooks
- detect-secrets by Yelp — Baseline-based secret detection tool that minimizes false positives
- TruffleHog — Entropy-based secret scanner that excels at finding novel credential formats
- git-crypt — Transparent file encryption in Git repositories for storing secrets securely
Conclusion
Secrets in version control are a time bomb — once pushed, they persist in history forever. Combining pre-commit hooks that scan for credentials with tools like git-secrets or truffleHog creates a defense-in-depth approach that catches leaks before they reach the remote.
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.