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.

published: reading time: 11 min read updated: March 31, 2026

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

ScenarioSymptomsMitigation
Secret committedCredential exposed in history1. Rotate immediately 2. Rewrite history 3. Scan for usage
False positives block commitsLegitimate code blockedAdd to baseline/allowlist; refine patterns
Pre-commit hook bypassedgit commit --no-verifyServer-side scanning as backup
Secret in CI/CD logsLogs contain credentialsUse secret masking; rotate exposed secrets
Shared development credentialsMultiple devs using same keyUse per-developer credentials; vault systems

Trade-offs

AspectAdvantageDisadvantage
Pre-commit hooksCatches secrets before commitCan be bypassed with --no-verify
Server-side scanningCannot be bypassedSecret already in history
Entropy detectionFinds unknown secret typesHigher false positive rate
Pattern matchingLow false positivesMisses unknown secret formats
Baseline filesReduces noise over timeRequires 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 .env files — 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.example templates, never commit .env files
  • 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

Why can't you just delete a secret from Git history with a normal commit?

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.

What's the difference between pattern-based and entropy-based secret detection?

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.

How do you handle false positives in secret scanning?

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.

Can pre-commit hooks prevent all secret commits?

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

AspectPre-commit HooksCI Scanning
SpeedInstant (local, < 1s)Minutes (pipeline queue + run)
CoverageStaged files onlyFull repository history
EnforcementCan be bypassed (--no-verify)Cannot be bypassed (blocks merge)
FeedbackImmediate, before commitAfter push, during PR
SetupPer-developer (each must install)Central (configured once)
False positivesBlocks developer workflowBlocks PR merge
Secret typesKnown patterns + entropyKnown patterns + entropy + cloud provider APIs
Best forCatching mistakes earlyCatching 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.

#git #version-control #secrets

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.

#git #version-control #gpg

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.

#git #version-control #svn