Quick Answer
Pre-commit secret scanning runs a scanner on your staged changes every time you run git commit, and blocks the commit if it finds an API key, token or password. The quickest setup is Gitleaks through the pre-commit framework: add the gitleaks hook to .pre-commit-config.yaml and run pre-commit install. Back it up with a scan in CI, because anyone can skip a local hook.
Why Stop Secrets Before the Commit
A secret that reaches Git stays in the history even after you delete the file. Anyone who cloned the repository, and every fork, keeps a copy. GitGuardian's State of Secrets Sprawl 2025 counted 23,770,171 new hardcoded secrets added to public GitHub repositories in 2024, 25% more than the year before, and found that 70% of the secrets leaked in 2022 were still valid.
Cleaning up means rotating the credential everywhere it is used, then rewriting history. A pre-commit hook avoids both by refusing the commit while the secret is still only on your machine. If secrets are already in your history, start with scanning and removing leaked secrets from Git history, then add the hook.
Pre-commit Scanning vs CI, Push Protection and History Scans
Each layer catches secrets at a different moment. None of them covers everything on its own:
| Layer | What it scans | When | Can it be skipped? |
|---|---|---|---|
| Pre-commit hook | Your staged changes | Before the commit exists | Yes: git commit --no-verify or SKIP=gitleaks |
| CI scan | The commits in a push or pull request | After the push, so the secret is already on the remote branch | Not by the developer |
| GitHub push protection | Pushes to GitHub | At push time, before the commits land | Depends on your GitHub settings |
| Full history scan | Every commit ever made | On demand | Not applicable: it finds what already leaked |
GitHub's push protection for users is on by default and stops you pushing secrets to public repositories from your personal account. Repository-level push protection is off by default and has to be enabled. A pre-commit hook works earlier than either, with any Git host.
Option 1: Gitleaks With the pre-commit Framework (Any Project)
The pre-commit framework installs and versions hooks for you, whatever language your project uses. Create .pre-commit-config.yaml at the root of the repository:
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaks
Then install it:
pip install pre-commit
pre-commit install
pre-commit autoupdate # moves rev to the newest Gitleaks release
The gitleaks hook runs gitleaks git --pre-commit --redact --staged --verbose, so it checks only what you are about to commit and hides the secret value in its output. If you would rather not build Gitleaks through Go, use the gitleaks-docker hook ID instead, or gitleaks-system to call a Gitleaks binary you installed yourself.
Option 2: Gitleaks With Husky (npm Projects)
JavaScript projects often manage hooks with Husky. Install Husky and the Gitleaks binary (for example brew install gitleaks):
npm install --save-dev husky
npx husky init
husky init creates .husky/pre-commit and adds a prepare script, so the hook installs on every npm install. Replace the file's contents with:
gitleaks git --pre-commit --redact --staged --verbose
In Husky v9 the hook is a plain shell script. If you copied an older guide, remove the . "$(dirname -- "$0")/_/husky.sh" line. If a guide uses gitleaks protect --staged, that command still works but was deprecated in Gitleaks 8.19 in favour of gitleaks git.
Other Scanners With a pre-commit Hook
| Scanner | Hook ID | How it decides |
|---|---|---|
| Gitleaks | gitleaks | Pattern rules for known key formats, run on staged changes |
| TruffleHog | trufflehog | Its hook reports only verified results, meaning credentials it confirmed are live |
| detect-secrets | detect-secrets | Looks for high-entropy strings and compares them with a baseline file of known findings |
Pattern rules catch known formats even when the key is revoked. Verification cuts noise but can miss a key it cannot check. For a single hook on a small project, Gitleaks is the simplest place to start.
Handling False Positives
Test fixtures and documentation examples sometimes look like real keys. Gitleaks gives you two ways to allow them:
- On the line: add a
gitleaks:allowcomment to a line you are knowingly committing, such as a fake key in a test. - In a file: copy the
Fingerprintvalue from the Gitleaks output into.gitleaksignoreat the root of the repository, one per line. A fingerprint names the commit, file, rule and line, for examplecd52267...:config/test.go:generic-api-key:23.
Allow a finding only when you are sure it is not a real credential. A public key that is meant to ship, such as a Supabase anon key, is fine; a service-role key never is.
Make It Stick on a Team
- Commit the config.
.pre-commit-config.yamlor the.huskyfolder lives in the repository, so everyone gets the same hook. - Scan in CI as well. A hook can be skipped with
--no-verify, and it never runs for a developer who has not installed it. - Scan the history once. Before you add the hook, run
gitleaks git -von the repository and rotate anything it finds. - Make skipping deliberate.
SKIP=gitleaks git commitskips only this hook, which is easier to spot in review than--no-verify.
VibeDoctor's Vibe Check runs Gitleaks across every file in your repository as part of each scan, so it also finds keys that were committed before the hook existed. It is free to sign up.
FAQ
What is pre-commit secret scanning?
It is a Git hook that runs a secret scanner on your staged changes every time you commit. If the scanner finds an API key, token or password, the commit is refused, so the secret never enters your Git history.
How do I add Gitleaks as a pre-commit hook?
Add the gitleaks repository to .pre-commit-config.yaml with the hook ID gitleaks, then run pip install pre-commit and pre-commit install. In an npm project you can use Husky instead and put gitleaks git --pre-commit --redact --staged --verbose in .husky/pre-commit.
Can pre-commit hooks be bypassed?
Yes. git commit --no-verify skips every hook, and SKIP=gitleaks skips the Gitleaks hook in the pre-commit framework. That is why you should also run a secret scan in CI, which developers cannot skip.
Is pre-commit scanning the same as scanning the repository history?
No. A pre-commit hook checks only the changes you are about to commit. A history scan checks every commit ever made and finds secrets that leaked before the hook existed. Run a history scan once, rotate what it finds, then rely on the hook and CI.
Is Gitleaks better than GitHub push protection?
They work at different moments. Push protection blocks secrets when you push to GitHub. A pre-commit hook blocks them before the commit exists and works with any Git host. Using both gives you two chances to catch a key.
Does it work when Cursor or Claude Code makes the commit?
Yes. The hook is part of Git, not the editor, so it runs whenever git commit runs, including when an AI agent commits for you. That matters because AI tools sometimes write keys straight into source files.