Stop AI Coding Agents From Leaking Secrets

Why agents commit .env files and keys — and how to catch it automatically

An AI coding agent with shell access will, eventually, try to commit something it shouldn't. Not maliciously — it runs git add -A to "save progress," and an untracked .env goes in with everything else. The commit message says chore: sync. The next git push sends your database password to a remote you can't fully un-send it from.

Why this happens specifically with agents

Humans develop a reflex: you glance at git status before staging, and an unexpected .env jumps out. Agents don't have that reflex unless you give it to them. They pattern-match "stage the work" to git add -A or git add ., both of which sweep up every untracked file in the tree — secrets, local configs, key material, and (in repos with worktrees) gitlinks to other sessions.

Why "just add it to .gitignore" isn't enough

.gitignore only protects files that were never tracked. The dangerous cases slip through anyway: a .env.local variant not in your ignore patterns, a key pasted into a normally-tracked config file, or a secret in a file the agent created this session. You need a check on the content of the outgoing commits, not just filenames.

The fix: scan outgoing commits before every push

The right place to catch a secret is the moment before it leaves your machine — at git push, against the commits you're actually about to send (@{u}..HEAD), not the whole history and not just the working tree.

upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null)
range=${upstream:+$upstream..HEAD}

# names: catch .env and key material in outgoing commits
git diff --name-only "$range" | grep -qE '(^|/)(\.env|.*\.pem|id_rsa|id_ed25519)$' \
  && { echo "sensitive file in outgoing commits"; exit 2; }

# content: catch credential-shaped strings in the added lines
patterns='(AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|sk-[A-Za-z0-9_-]{20,}|sk-ant-[A-Za-z0-9_-]{20,}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----)'
git diff "$range" | grep -qE "^\+.*$patterns" \
  && { echo "credential-shaped string in outgoing diff"; exit 2; }

Wire that into a Claude Code PreToolUse hook matched on git push, and use gitleaks as the primary scanner with the regex layer as a portable fallback. The hook exits 2 to block, and the agent gets told why — so it stops instead of retrying.

If a secret already pushed

Treat it as compromised the instant it reaches a remote, even a private one. Rotate the credential first; rewriting history (git filter-repo) is cleanup, not containment. This is exactly why a pre-push gate is worth the five minutes to set up — recovery is always more expensive.

Want this as a one-command install?
CC Powerpack ships a pre-push secret scanner (gitleaks + regex fallback + forbidden-file check) as a native Claude Code hook, plus dangerous-command and worktree gates. Free and MIT-licensed. Get it on GitHub →

← Back to CC Powerpack · How Claude Code hooks work →