DEV Community

A 2-minute pre-commit hook that stops you from committing API keys

Leaked credentials almost never happen because someone decided to commit a key. They happen because a .env, a config file, or a debug snippet slipped into a staged change and nobody noticed until a bot on the internet did.

The fix is to make the mistake impossible to commit — a gate that runs before every commit and fails loudly if anything credential-shaped is staged.

The shape of the problem

Most leaked secrets have recognizable prefixes:

Provider Looks like
OpenAI sk-...
Anthropic sk-ant-...
AWS AKIA...
GitHub PAT ghp_... / github_pat_...
Stripe sk_live_...
Google AIza...
Private keys -----BEGIN ... PRIVATE KEY-----

A scanner that knows these can catch the overwhelming majority of accidental leaks with almost no false positives.

The 2-minute setup

You don't need a heavyweight platform. A single command in a git hook does it. Here's one using ctxpack (a zero-dependency CLI), but the pattern works with any scanner that exits non-zero on a hit:

Create .git/hooks/pre-commit:

#!/bin/sh
npx github:trongtruong110-ux/ctxpack . --check -i "test/**" -i "**/*.example" || {
  echo "Commit blocked: a possible secret was found above."
  exit 1
}
Enter fullscreen mode Exit fullscreen mode
chmod +x .git/hooks/pre-commit
Enter fullscreen mode Exit fullscreen mode

Now every commit is scanned. When something slips in, you get:

ctxpack --check: 1 potential secret(s) in 34 files:

  src/config.js:12  ANTHROPIC_KEY

✗ failing — remove or ignore these before committing.
Enter fullscreen mode Exit fullscreen mode

Note it reports the location and type, never the secret value itself — so the finding is safe to show in CI logs.

Two details that make it stick

  1. Ignore your fixtures. Test suites are full of deliberately-fake keys. Use an ignore glob (-i "test/**") so the gate doesn't cry wolf — a scanner that fires on fixtures gets disabled within a week.
  2. Run it in CI too. Local hooks can be skipped with --no-verify. Add the same --check line as a CI step so nothing merges with a live credential in it.

Why bother if you have secret scanning on the host?

GitHub and others scan after the push — by then the secret is in history (and history is forever unless you rewrite it). A pre-commit gate stops it before it exists in a commit at all. Defense in depth: keep the host scanner, but don't let it be your first line.


ctxpack is MIT-licensed and free: https://github.com/trongtruong110-ux/ctxpack. What's your current setup for catching secrets before they land — pre-commit, CI, host-side, or a mix?

Top comments (0)