Catch secrets before they reach git: a pre-commit check for Node repos
GitGuardian's State of Secrets Sprawl 2026 counted 28,649,024 new secrets in public GitHub commits during 2025, 34% more than the year before. Commits co-authored by Claude Code leaked secrets at 3.2%, against 1.5% for all public commits (SC Media's coverage has the breakdown). The report also found 24,008 unique secrets in MCP configuration files on public GitHub, and 2,117 of those were verified as still valid.
An assistant that writes a config file for you will paste in whatever token is sitting in your shell or your chat history. A hook that reads the staged diff before the commit exists will catch most of that. Setting one up takes about ten minutes.
Set up the hook
This guide uses gitleaks. Install it (brew install gitleaks, or grab a binary from the releases page), then pick one of the three ways to run it.
If your repo already uses the pre-commit framework, add this to .pre-commit-config.yaml:
repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.24.2 hooks: - id: gitleaks Run pre-commit autoupdate to move rev to the current release.
If the team uses husky, add the command to .husky/pre-commit:
gitleaks git --pre-commit --redact --staged --verbose With no tooling at all, put the same line in .git/hooks/pre-commit and run chmod +x .git/hooks/pre-commit. That file isn't committed, so each developer has to add it themselves, which is a good reason to prefer husky or pre-commit.
The command is the same in all three: --staged scans only what you're about to commit, so it stays fast on a large repo, and --redact keeps the secret out of your terminal scrollback. Older guides use gitleaks protect --staged. That command was deprecated in v8.19.0 and is now hidden from --help.
Test it before you trust it. Stage a file containing a fake AWS-style key and try to commit:
echo 'AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE' > leak-test.txt git add leak-test.txt git commit -m "test" # should fail with a finding git reset leak-test.txt && rm leak-test.txt For a real false positive, add a gitleaks:allow comment on that line, or put the finding's fingerprint in a .gitleaksignore file.
Decide which files count as secret-bearing
The hook catches tokens. Your .gitignore keeps whole files out:
.env .env.* !.env.example .mcp.json Commit a .env.example with every variable name and a dummy value, so new developers know what to set without anyone pasting real values into Slack.
Treat agent configuration the same way. .mcp.json, and any folder where your editor or coding assistant keeps server definitions, often holds API keys inline. Those are the files behind GitGuardian's 24,008 MCP findings. If a file has to be shared, reference environment variables in it instead of literal tokens, and check what your tool supports before you assume it does.
Run the same scan in CI
Hooks live on each laptop. A new clone doesn't have one until someone installs it, and git commit --no-verify skips it entirely. Run the scan again on every push and pull request so the check doesn't depend on anyone's local setup:
name: gitleaks on: [push, pull_request] jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: gitleaks/gitleaks-action@v3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} fetch-depth: 0 gives the action the full history to scan. Repos owned by a GitHub organization need a free GITLEAKS_LICENSE key from gitleaks.io; personal repos don't. Use @v3: v2 runs on Node 20, which GitHub-hosted runners dropped in September 2026.
When one slips through anyway
Rotate the credential first. Revoke it at the provider, issue a new one, deploy it, and only then think about the git history. Once a commit is pushed to a public repo, assume it has been copied: forks, local clones and caches keep it after you rewrite your own history. GitGuardian tracked valid secrets it detected in 2022 and found 64% still active four years later.
After rotating, check what the old key did while it was exposed. Your provider's audit log covers its side. For keys that call your own API, look at the requests: which endpoints the key hit, from which IPs, and when it started. If your current logs can't show that, add per-request logging with the caller's identity before the next leak.
See what's hitting your API: apitraffic.io