How to scan for secrets before you push (gitleaks pre-commit hook)
A .gitignore entry stops a .env file from being committed. It does nothing about an API key pasted into config.js, a test fixture, or a README. A secret scanner in a pre-commit hook reads the lines you are about to commit and refuses the commit if one of them looks like a credential. That is the cheapest point to catch it: nothing has left your Mac, so there is nothing to rotate.
1. Install gitleaks
brew install gitleaks
gitleaks version
Check the version before you copy commands from older blog posts. Gitleaks v8.19.0 deprecated the detect and protect commands in favour of git, dir and stdin. The old names still run but are hidden from --help. The translation that matters for a hook is from the old form (before v8.19.0):
gitleaks protect --staged
to the current one:
gitleaks git --pre-commit --staged
2. Add the hook to one repository
Create .git/hooks/pre-commit in the repository:
#!/bin/sh
gitleaks git --pre-commit --staged --redact --verbose
chmod +x .git/hooks/pre-commit
What each flag does:
--pre-commitscans the output ofgit diffinstead of walking commit history.--stagedscans the staged changes, which is exactly what the commit will contain. Without it,--pre-commitlooks at unstaged changes instead.--redactprintsREDACTEDin place of the secret, so the value does not end up in your terminal scrollback or a CI log.--verboseprints each finding: file, line, rule and a fingerprint.
Gitleaks exits with code 1 when it finds something, and a non-zero exit from a pre-commit hook aborts the commit. A blocked commit looks like this:
Finding: STRIPE_SECRET_KEY=REDACTED
Secret: REDACTED
RuleID: stripe-access-token
Entropy: 5.348277
File: config.env
Line: 1
Fingerprint: config.env:stripe-access-token:1
WRN leaks found: 1
If the hook works in Terminal but reports gitleaks: command not found from a GUI git client, the client is running hooks with a shorter PATH. Use the full path from which gitleaks in the script, /opt/homebrew/bin/gitleaks on Apple silicon.
3. Or add it to every repository at once
.git/hooks is not versioned, so the hook above lives only in that one clone. To cover every repository on your Mac, point git at a global hooks directory:
mkdir -p ~/.git-hooks
cp .git/hooks/pre-commit ~/.git-hooks/pre-commit
git config --global core.hooksPath ~/.git-hooks
The catch: once core.hooksPath is set, git stops running the scripts in each repository's own .git/hooks. If some of your projects have their own hooks, a repository-level core.hooksPath (which is what tools like Husky set) still wins over the global one, but plain .git/hooks scripts are skipped.
4. Or share it with the team through pre-commit
If the project already uses the pre-commit framework, gitleaks publishes a hook for it. Add this to .pre-commit-config.yaml and commit the file:
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaks
brew install pre-commit
pre-commit autoupdate
pre-commit install
The published hook runs the same gitleaks git --pre-commit --redact --staged --verbose command. Use the hook id gitleaks-system instead of gitleaks to run the copy you installed with Homebrew. Each teammate still has to run pre-commit install once per clone; the config file is shared, the hook is not.
5. What gitleaks catches, and what it misses
We ran gitleaks 8.30.1 with its default rules against a handful of staged lines. It blocked:
- A Stripe live secret key (
sk_live_...) - A GitHub personal access token (
ghp_...) - An AWS access key ID (
AKIA...) - A long random value assigned to a name like
api_key
It let these through:
DB_PASSWORD=hunter2. Short, human-chosen passwords do not look random enough to flag.- A password inside a
postgres://user:password@host/dbconnection string. - A passphrase such as
correct-horse-battery-staple. - AWS's documented example key
AKIAIOSFODNN7EXAMPLE, which is allowlisted on purpose.
The general shape: provider tokens with a recognisable prefix are caught reliably, generic high-entropy strings usually are, and ordinary passwords usually are not. Treat a clean scan as "no known token formats", not "no secrets".
The hook also has structural gaps:
- It is skippable.
git commit --no-verifyskips pre-commit hooks entirely. Pair the hook with a server-side check such as GitHub push protection, covered in what to do when you leak an API key. - It only sees new changes. A key committed last year is not in the staged diff. Scan the whole history once with
gitleaks git -v, and the working tree including untracked files withgitleaks dir -v. If history turns up a real key, see how to remove an API key from git history. - It does not stop files by name. A
.envfull of low-entropy values can pass. Ignore env files globally as well; see how to prevent committing .env files to git.
6. Handling false positives
For a test fixture you are knowingly committing, add a gitleaks:allow comment to the end of that line and gitleaks skips it. To ignore one specific finding without touching the file, copy its Fingerprint line from the output into a .gitleaksignore file at the repository root. For a real secret, do not reach for either: move the value into an environment variable instead.
7. Alternatives: TruffleHog and git-secrets
TruffleHog (brew install trufflehog) has a large detector set and can verify a finding by trying it against the provider's API. Its documented pre-commit hook is:
#!/bin/sh
export TRUFFLEHOG_PRE_COMMIT=1
trufflehog git file://.
Two trade-offs. Verification means candidate credentials are sent to the matching service to test them, so the hook makes network calls. And its pre-commit settings report only verified and unknown results, so a detected key that TruffleHog could not confirm as live does not block the commit. That keeps noise low, at the cost of letting through a real key it failed to confirm.
git-secrets from AWS Labs (brew install git-secrets) is the older, narrower option. Run git secrets --install in each repository and git secrets --register-aws to add its patterns. Out of the box it knows AWS credentials only; anything else is a regex you add with git secrets --add. It also installs commit-msg and prepare-commit-msg hooks, so it checks commit messages too.
If you are picking one, gitleaks is the easiest to reason about: no network, fast, and a single command in the hook.