TL;DR
- The best place to stop a secret leak is before the commit object is created at all.
- A pre-commit hook scans only staged changes, so it is fast enough for day-to-day use and specific enough to be actionable.
- GitHub push protection is useful, but it runs later, after the secret has already entered local history.
- Teams that combine pre-commit, CI scanning, and git-history audits catch both new mistakes and old ones.
Why pre-commit beats catching secrets later
Most secret leak tooling is deployed too late in the workflow. A CI scan catches the problem after the branch is pushed. GitHub push protection catches it after the commit exists locally. A quarterly audit catches it after the credential may have been sitting in history for months.
A pre-commit hook is different because it runs at the exact moment the mistake is still cheap to fix. The developer still has the file open. The change is small. The secret has not reached the remote. No incident process is required. They delete the value, use a placeholder or proper secret reference, and continue working.
Good security controls reduce cleanup work. Great controls stop the cleanup from ever being necessary.
How the hook actually works
Git hooks are local scripts that run during the git lifecycle. A pre-commit hook runs after files are staged and before git writes the final commit. That timing matters because the scanner can focus on the exact content about to enter history.
$ slickenv git protect
✓ Pre-commit hook installed at .git/hooks/pre-commit
✓ Scans staged files only
✓ Blocks 53 secret patterns before commitA useful hook should do three things well:
- Scan only staged files so the feedback matches what the developer is committing.
- Return clear, low-noise output with the file and pattern that triggered the block.
- Provide a safe remediation path such as placeholders, secret references, or runtime injection.
What a blocked commit should look like
$ git commit -m "add billing integration"
[SlickEnv Pre-commit Hook]
→ Scanning staged files...
✗ STRIPE_SECRET_KEY found in src/lib/billing.ts:18
sk_live_4xKabcdefghijklmnopqrstuvwxyz
Commit blocked.
Tip: replace the raw value with STRIPE_SECRET_KEY=slickenv://STRIPE_SECRET_KEYWhat a good hook should catch
The obvious cases are easy: raw API keys, private keys, database URLs with embedded passwords, and committed .env files. But the real value of a hook is catching the patterns developers do not notice when moving fast.
- cloud credentials such as AWS access keys and Azure connection strings
- provider-specific tokens for Stripe, GitHub, OpenAI, Anthropic, Slack, and other SaaS APIs
- JWT signing keys, PEM private keys, service-account JSON, and SSH material
- connection strings in source files, tests, fixtures, shell scripts, and CI configs
- sensitive files that should never be committed directly, including
.envand some MCP-related config files
Secret leaks rarely happen in the obvious config directory only. They happen in test snapshots, temporary scripts, copied curl examples, and "just for local debugging" commits.
Rolling it out across a team
The weakness of local hooks is adoption drift. One engineer installs it. Two never do. Someone clones a fresh repo on Friday and skips the setup step. The technical fix is easy; the operational fix is to make hook installation part of the normal bootstrap path.
- Add
slickenv git protectto your onboarding or repo setup script. - Document the expected block message so developers know the hook is working, not broken.
- Backstop it with CI secret scans because not every local machine is configured correctly.
- Run periodic git-history scans to catch older leaks created before the hook existed.
If you want the team version of this workflow, pair it with the patterns in The Practical Guide to Environment Variable Management for Teams.
Pre-commit vs GitHub push protection
This is not an either-or choice. They solve different points in the timeline.
- Pre-commit hook: stops the secret before it becomes part of local git history.
- GitHub push protection: stops known patterns when the developer pushes to GitHub.
- CI secret scan: catches committed or generated secrets before deployment.
- Git history scan: catches secrets that predate your current controls.
Use all four layers if the repository matters. GitHub's own secret scanning and push protection docs are worth reading here: GitHub secret scanning documentation.
The later the control runs, the more cleanup work it creates.
Practical setup that developers will keep enabled
Developers disable security tooling when it is slow, noisy, or unclear. So the winning setup is the one that is fast on staged diffs, avoids false positives where possible, and tells the engineer exactly how to fix the issue.
# Recommended local baseline
slickenv git protect
slickenv ai protect
slickenv scan
# Recommended CI baseline
slickenv scan --ci
slickenv git scanThe hook is prevention. The rest of the stack is verification. If you already know you have old leaked credentials, read We Found 3 Production Stripe Keys in Our Git History and then clean up the repository before you congratulate yourself on new guardrails.