Skip to content

Instantly share code, notes, and snippets.

@BrennerSpear
Last active March 20, 2026 14:52
Show Gist options
  • Select an option

  • Save BrennerSpear/319611ffebeef0501540ea2f5a4f942f to your computer and use it in GitHub Desktop.

Select an option

Save BrennerSpear/319611ffebeef0501540ea2f5a4f942f to your computer and use it in GitHub Desktop.
Global Git pre-commit hook that blocks API keys, tokens, and .env files from being committed. 30-second install.

git-secret-guard 🔐

A global Git pre-commit hook that blocks commits containing API keys, tokens, and .env files. Works across all repos — no per-project setup needed.

What it catches

Provider Pattern
OpenAI sk-proj-*
Anthropic sk-ant-api*
Google AIzaSy*
AWS AKIA*
GitHub ghp_*, gho_*, github_pat_*
GitLab glpat-*
Stripe sk_live_*, sk_test_*, rk_live_*
Slack xoxb-*, xoxp-*
SendGrid SG.*
npm / PyPI / HuggingFace / Replicate various
JWT tokens eyJ* bearer tokens
.env files any .env* except .env.example

Plus it auto-fixes your .gitignore to exclude .env files when it catches one.

Install (30 seconds)

# 1. Create global hooks directory
mkdir -p ~/.config/git/hooks

# 2. Download the hook
curl -fsSL https://gist.githubusercontent.com/BrennerSpear/319611ffebeef0501540ea2f5a4f942f/raw/pre-commit \
  -o ~/.config/git/hooks/pre-commit

# 3. Make it executable
chmod +x ~/.config/git/hooks/pre-commit

# 4. Tell git to use it globally
git config --global core.hooksPath ~/.config/git/hooks

That's it. Every git commit in every repo now runs through this hook.

How it works

  1. Blocks .env files — auto-unstages them and adds patterns to .gitignore
  2. Scans diffs for secret patterns — only checks newly added lines (not existing code)
  3. Chains to local hooks — if a repo has its own .git/hooks/pre-commit, it runs that too

Override

When you intentionally want to commit something that matches (e.g., test fixtures, documentation):

git commit --no-verify

Uninstall

git config --global --unset core.hooksPath
rm ~/.config/git/hooks/pre-commit
#!/usr/bin/env bash
# git-secret-guard: Global git pre-commit hook that blocks API keys and .env files
# Install: curl -fsSL <this-gist-raw-url> | bash
# Or manually: copy pre-commit to your hooks dir and run the git config line below.
set -euo pipefail
HOOKS_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/git/hooks"
HOOK_FILE="$HOOKS_DIR/pre-commit"
mkdir -p "$HOOKS_DIR"
# Download the hook (or copy from local)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "$SCRIPT_DIR/pre-commit" ]; then
cp "$SCRIPT_DIR/pre-commit" "$HOOK_FILE"
else
echo "Error: pre-commit file not found alongside install.sh"
exit 1
fi
chmod +x "$HOOK_FILE"
# Set global hooks path (preserves any existing git config)
git config --global core.hooksPath "$HOOKS_DIR"
echo ""
echo "✅ git-secret-guard installed!"
echo " Hook: $HOOK_FILE"
echo " Global hooksPath: $HOOKS_DIR"
echo ""
echo "All future commits in ALL repos will be scanned."
echo "Bypass with: git commit --no-verify"
echo ""
echo "⚠️ If a repo has its own .git/hooks/pre-commit, it will still run after this global hook."
#!/usr/bin/env bash
# git-secret-guard — Global pre-commit hook
# Blocks commits containing API keys, tokens, and .env files.
# https://gist.github.com/brennerspear/...
#
# Install globally:
# mkdir -p ~/.config/git/hooks
# cp pre-commit ~/.config/git/hooks/pre-commit
# chmod +x ~/.config/git/hooks/pre-commit
# git config --global core.hooksPath ~/.config/git/hooks
#
# Override for a single commit:
# git commit --no-verify
set -euo pipefail
FAILED=0
# ─── Part 1: Block .env files ────────────────────────────────────────────────
bad_files=$(git diff --cached --name-only --diff-filter=ACR | grep -E '(^|/)\.env($|\..*)' | grep -v '\.env\.example$' || true)
if [ -n "$bad_files" ]; then
echo "🚫 BLOCKED: .env file(s) with potential secrets:"
echo "$bad_files" | sed 's/^/ /'
echo ""
# Auto-fix .gitignore
root=$(git rev-parse --show-toplevel 2>/dev/null || true)
if [ -n "$root" ]; then
touch "$root/.gitignore"
changed=false
for pattern in '.env' '.env.*' '!.env.example'; do
if ! grep -qxF "$pattern" "$root/.gitignore" 2>/dev/null; then
echo "$pattern" >> "$root/.gitignore"
changed=true
fi
done
if [ "$changed" = true ]; then
echo " Auto-added .env patterns to .gitignore"
git add "$root/.gitignore"
fi
# Unstage the bad files
echo "$bad_files" | while read -r f; do
git reset HEAD -- "$f" 2>/dev/null || true
done
echo " .env files unstaged automatically."
echo ""
fi
FAILED=1
fi
# ─── Part 2: Scan diffs for secret patterns ──────────────────────────────────
# Each line: "pattern_regex ## human-readable label"
SECRET_PATTERNS=(
'sk-proj-[a-zA-Z0-9]{20,} ## OpenAI API key'
'sk-or-v1-[a-zA-Z0-9]{20,} ## OpenRouter API key'
'sk-ant-api[a-zA-Z0-9]{20,} ## Anthropic API key'
'AIzaSy[a-zA-Z0-9_-]{33} ## Google API key'
'GOCSPX-[a-zA-Z0-9_-]{20,} ## Google OAuth client secret'
'ghp_[a-zA-Z0-9]{36} ## GitHub personal access token'
'gho_[a-zA-Z0-9]{36} ## GitHub OAuth token'
'github_pat_[a-zA-Z0-9_]{20,} ## GitHub fine-grained PAT'
'glpat-[a-zA-Z0-9_-]{20,} ## GitLab personal access token'
'xoxb-[0-9]{10,} ## Slack bot token'
'xoxp-[0-9]{10,} ## Slack user token'
'xoxs-[0-9]{10,} ## Slack session token'
'AKIA[0-9A-Z]{16} ## AWS access key ID'
'sk_live_[a-zA-Z0-9]{20,} ## Stripe live secret key'
'sk_test_[a-zA-Z0-9]{20,} ## Stripe test secret key'
'rk_live_[a-zA-Z0-9]{20,} ## Stripe restricted key'
'pk_live_[a-zA-Z0-9]{20,} ## Stripe live publishable key'
'SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43} ## SendGrid API key'
'sq0csp-[a-zA-Z0-9_-]{43} ## Square OAuth secret'
'eyJ[a-zA-Z0-9_-]{20,}\.eyJ[a-zA-Z0-9_-]{20,}\. ## JWT token'
'npm_[a-zA-Z0-9]{36} ## npm access token'
'pypi-[a-zA-Z0-9]{50,} ## PyPI API token'
'hf_[a-zA-Z0-9]{34} ## Hugging Face token'
'r8_[a-zA-Z0-9]{38} ## Replicate API token'
'whsec_[a-zA-Z0-9]{20,} ## Webhook signing secret'
)
# Get only added lines from staged changes
added_lines=$(git diff --cached -U0 --diff-filter=ACM 2>/dev/null | grep -E '^\+' | grep -v '^+++' || true)
if [ -n "$added_lines" ]; then
for entry in "${SECRET_PATTERNS[@]}"; do
pattern=$(echo "$entry" | sed 's/ *## .*//')
label=$(echo "$entry" | sed 's/.*## //')
matches=$(echo "$added_lines" | grep -E "$pattern" 2>/dev/null || true)
if [ -n "$matches" ]; then
if [ "$FAILED" -eq 0 ]; then
echo "🚫 BLOCKED: Potential secrets detected in staged changes:"
echo ""
fi
echo " $label"
echo "$matches" | head -3 | sed 's/^/ /'
echo ""
FAILED=1
fi
done
fi
# ─── Result ───────────────────────────────────────────────────────────────────
if [ "$FAILED" -ne 0 ]; then
echo "Commit blocked. Remove the secrets and try again."
echo "Override: git commit --no-verify"
exit 1
fi
# ─── Chain to repo-local hook ────────────────────────────────────────────────
# If this repo has its own pre-commit hook, run it too.
local_hook="$(git rev-parse --show-toplevel 2>/dev/null)/.git/hooks/pre-commit"
if [ -x "$local_hook" ]; then
exec "$local_hook"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment