DEV Community

ke jia
ke jia

Posted on

Your Pre-Commit Hook Can Catch Secrets in 30 Lines

CI is a great place to catch a leaked secret — unless the secret has already been committed. By the time the pipeline fails, the value is in git history, and "just delete the file and push again" does not un-ring that bell.

The earlier line of defense is the pre-commit hook. It runs on your machine, before the commit exists, and a 30-line hook can stop the most common leak class dead.

Here's the whole setup.

The hook

npx @wuchunjie/dotguard scans every .env* file in a directory and exits 1 if it finds anything that looks like a real credential. That exit code is the entire integration:

#!/bin/sh
# .git/hooks/pre-commit

# 1. Did the user stage a .env file? If so, it must be .env.example.
git diff --cached --name-only | grep -E '(^|/)\.env($|\.)' | grep -v '\.env\.example$' > /dev/null && {
  echo "BLOCKED: you staged a .env file. Use .env.example instead."
  echo "  If you really need to commit it (you don't), run: git commit --no-verify"
  exit 1
}

# 2. Scan the working tree for env files that contain real values.
npx @wuchunjie/dotguard . > /dev/null 2>&1 || {
  echo "BLOCKED: dotguard found potential secrets in .env files."
  echo "  Run 'npx @wuchunjie/dotguard .' to see details."
  exit 1
}

exit 0
Enter fullscreen mode Exit fullscreen mode

That's the whole thing. Two checks, both fast, both local:

  1. Staged-file check — the classic "I accidentally added .env" case. Blocked before the object ever enters the repo.
  2. Content check — the .env sitting in your working directory (correctly untracked, correctly in .gitignore) still gets scanned, because it's the file you're about to think is safe. If it contains a real key, you'll know before you push a branch, open a PR, or copy-paste it into a config file elsewhere.

Why not just CI?

Because the value of catching a secret is a function of how far back in history it goes:

Caught by Damage already done
Pre-commit hook None. The value never entered git.
CI on push It's in the remote history. Rotate, then scrub.
A dependency audit, weeks later It's in forks, caches, and possibly a public mirror. Rotate, scrub, assume compromise.

CI still deserves a scanner — it's the last line, and it catches what the hook can't (secrets in non-env files, secrets in PRs from forks). But the hook is the one that keeps your own history clean, and that's the one teams skip because "I'm careful."

Careful is not a control. The hook is.

Making the team actually use it

Two practical notes from rolling this out:

Install it in the repo, not the laptop. A hook that lives in ~/.git-hooks only exists on the person who set it up. Instead, commit a hooks/ directory and a one-liner setup:

git config core.hooksPath .githooks
Enter fullscreen mode Exit fullscreen mode

Now the hook travels with the repo, shows up in the onboarding doc, and can't silently rot.

Make the failure message teach. The hook above tells you why it blocked you and how to fix it (or how to override with --no-verify, which leaves an audit trail in the commit message if you document it). A hook that just says error gets bypassed by the third week.

The tradeoff, honestly

Pre-commit hooks are per-machine, so a fork without the hook setup has no protection — CI is still mandatory. And npx means the first run downloads the package (it's a single zero-dependency file, but the first run takes a few seconds). Cache it with a local npm install -D if the latency bothers you.

Worth it? The hook has stopped real commits on my machines more times than I can count — usually a .env.local with a staging key, usually two seconds from a git add .. Each stop is a five-minute incident that never happened.

git config core.hooksPath .githooks
npx @wuchunjie/dotguard
Enter fullscreen mode Exit fullscreen mode

More Tools

Tool What it does Command
scaffoldx-cli Production-ready project templates in seconds npx scaffoldx-cli
dotguard Scan .env files for exposed secrets npx @wuchunjie/dotguard
gitpulse Git repo analytics in your terminal npx @wuchunjie/gitpulse
snippetx Terminal code snippet manager npx @wuchunjie/snippetx

If these save you time, consider buying me a coffee. All tools are MIT-licensed, zero-dependency, and run fully offline.

Top comments (0)