Pre-commit hook to block committed secrets
The cheapest secret leak to prevent is the one a pre-commit hook catches before git commit finishes. The catch: a local hook can be bypassed, so it needs a CI backstop. This page sets up both, extending CI/CD Config Validation.
Problem 1: relying on memory
# ANTI-PATTERN: "I'll remember not to commit .env" — until you don't
# git add . && git commit -m "wip" # .env with live keys is now in history
A single git add . is all it takes; rotating the leaked credential is the only fix once it is in history.
Problem 2: a hook with no CI backstop
# ANTI-PATTERN: local hook only — trivially skipped
git commit --no-verify # bypasses every local pre-commit hook
Local hooks are advisory; --no-verify skips them. CI must enforce the same scan.
Secure implementation
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks # scans staged changes for secrets
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ["--baseline", ".secrets.baseline"]
# one-time setup
pip install pre-commit detect-secrets
detect-secrets scan > .secrets.baseline # record known-safe matches
pre-commit install # activate the local hook
# .github/workflows/secrets.yml — the backstop that cannot be skipped
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- run: gitleaks detect --no-banner # fails the build on any finding
The local hook gives instant feedback; the CI job is the gate that --no-verify cannot bypass. The baseline records intentional matches (test fixtures) so they do not block every commit.
Gotchas & version-specific behaviour
- A leaked secret must be rotated, not just removed — history is forever.
detect-secretsneeds a baseline; regenerate it when adding legitimate fixtures.gitleaks detectwithfetch-depth: 0scans full history in CI; the pre-commit hook scans staged changes only.- Keep
.envin.gitignoreas the first line of defence; the scanner is the second.
Production parity checklist
pre-commit installis run by every developer (document it in the README).- The same scanner runs in CI with full history.
.envand*.pemare gitignored.- A
.secrets.baselineis committed and kept current. - Any real hit triggers immediate credential rotation.
Conclusion
Pair a local pre-commit scanner with a CI job that cannot be skipped, and committed secrets are caught before they become permanent. This is the local half of CI/CD Config Validation.