Production-grade pre-commit hook utility for zero-latency git diff token scanning
| #!/usr/bin/env python3 | |
| """ | |
| Fast Diff Token Scanner. | |
| Reads unified git diff from STDIN, checks only added lines (+), | |
| and flags API keys, private keys, high-entropy tokens. | |
| """ | |
| import sys | |
| import re | |
| import math | |
| from typing import List, Tuple | |
| # Pre-compiled high-confidence secret regex patterns | |
| SECRET_PATTERNS = [ | |
| ("AWS Access Key ID", re.compile(r"\b(AKIA[0-9A-Z]{16})\b")), | |
| ("GitHub Personal Access Token", re.compile(r"\b(gh[pousr]_[A-Za-z0-9_]{36,255})\b")), | |
| ("Generic Bearer Token", re.compile(r"(?i)bearer\s+([a-zA-Z0-9_\-\.]{24,})")), | |
| ("RSA/ECDSA Private Key Header", re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----")), | |
| ("Slack API Token", re.compile(r"\b(xox[p|b|o|a]-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*)\b")), | |
| ("Stripe Secret Key", re.compile(r"\b(sk_live_[0-9a-zA-Z]{24})\b")), | |
| ] | |
| def shannon_entropy(text: str) -> float: | |
| """Calculate Shannon entropy bits per character.""" | |
| if not text: | |
| return 0.0 | |
| length = len(text) | |
| counts = [0] * 256 | |
| for b in text.encode("utf-8", errors="ignore"): | |
| counts[b] += 1 | |
| entropy = 0.0 | |
| for count in counts: | |
| if count > 0: | |
| p = count / length | |
| entropy -= p * math.log2(p) | |
| return entropy | |
| def scan_diff(stream) -> List[Tuple[str, str, str]]: | |
| findings = [] | |
| current_file = "unknown" | |
| for line in stream: | |
| line_clean = line.rstrip("\r\n") | |
| # Track file name | |
| if line_clean.startswith("+++ b/"): | |
| current_file = line_clean[6:] | |
| continue | |
| elif line_clean.startswith("--- ") or line_clean.startswith("diff --git"): | |
| continue | |
| # Only inspect additions | |
| if not line_clean.startswith("+") or line_clean.startswith("+++"): | |
| continue | |
| content = line_clean[1:] # strip leading '+' | |
| # 1. Regex pattern checks | |
| for pat_name, regex in SECRET_PATTERNS: | |
| match = regex.search(content) | |
| if match: | |
| snippet = match.group(0)[:12] + "..." | |
| findings.append((current_file, pat_name, snippet)) | |
| # 2. Shannon entropy check for alphanumeric tokens > 24 chars | |
| words = re.findall(r"[A-Za-z0-9+/=_\-]{24,}", content) | |
| for w in words: | |
| ent = shannon_entropy(w) | |
| if ent >= 4.4: | |
| snippet = w[:10] + "..." + w[-4:] | |
| findings.append((current_file, f"High Entropy Token ({ent:.2f} bits)", snippet)) | |
| return findings | |
| def main(): | |
| findings = scan_diff(sys.stdin) | |
| if not findings: | |
| sys.exit(0) | |
| print("\n🚨 Potential credentials found in git diff:") | |
| for file_path, rule, snippet in findings: | |
| print(f" - [{rule}] in {file_path}: {snippet}") | |
| print() | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| main() |
Zero-Latency Git Diff Secret Guard Installation
Protect your repositories from accidental secret commits by running this pre-commit scanner locally.
Method 1: Local .git/hooks Installation
- Copy the scripts into your repository's hook directory:
cp pre-commit-secret-guard.sh .git/hooks/pre-commit cp fast_diff_check.py .git/hooks/fast_diff_check.py chmod +x .git/hooks/pre-commit .git/hooks/fast_diff_check.py
- Test the hook by staging a dummy high-entropy secret:
echo "API_KEY=AKIAIOSFODNN7EXAMPLE" >> test.env git add test.env git commit -m "Test commit" # Hook will abort commit and alert you!
Method 2: Global Git Template
To enforce this check across all newly cloned repositories:
git config --global init.templateDir ~/.git-templates mkdir -p ~/.git-templates/hooks cp pre-commit-secret-guard.sh ~/.git-templates/hooks/pre-commit cp fast_diff_check.py ~/.git-templates/hooks/fast_diff_check.py chmod +x ~/.git-templates/hooks/pre-commit ~/.git-templates/hooks/fast_diff_check.py
| #!/usr/bin/env bash | |
| # | |
| # Git pre-commit hook to prevent accidental secret leakage. | |
| # Scans only staged changes for zero-latency commits. | |
| set -e | |
| # Redirect output to stderr | |
| exec 1>&2 | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| SCANNER="$SCRIPT_DIR/fast_diff_check.py" | |
| # If python scanner is not found in hooks dir, search PATH | |
| if [ ! -f "$SCANNER" ]; then | |
| SCANNER="fast_diff_check.py" | |
| fi | |
| echo "🔍 [Secret Guard] Scanning staged changes for credentials..." | |
| # Run secret scanner on staged git diff | |
| if git rev-parse --verify HEAD >/dev/null 2>&1; then | |
| AGAINST=HEAD | |
| else | |
| # Initial commit: diff against empty tree object | |
| AGAINST="$(git hash-object -t tree /dev/null)" | |
| fi | |
| git diff --cached --unified=0 "$AGAINST" | python3 "$SCANNER" | |
| STATUS=$? | |
| if [ $STATUS -ne 0 ]; then | |
| echo "❌ [Secret Guard] Commit aborted due to detected high-entropy token or secret pattern!" | |
| echo "💡 If this is an intentional commit or false positive, use git commit --no-verify" | |
| exit 1 | |
| fi | |
| echo "✅ [Secret Guard] Staged changes verified clean." | |
| exit 0 |
评论
?
参与讨论