e888560175
Blocks commits containing:
- Private key file headers (RSA/EC/DSA/OPENSSH)
- Plaintext passwords/tokens ≥16 chars
- Gitea/GitHub-style hex tokens
- Files with private key path suffixes (_ed25519, _rsa, etc.)
Allowlisted: Ansible vault variable references ({{ vault_* }}),
encrypted vault blocks ($ANSIBLE_VAULT), and comment lines.
Run scripts/install-hooks.sh after cloning to activate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
2.7 KiB
Bash
Executable File
104 lines
2.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
FAIL=0
|
|
errors=()
|
|
|
|
# Files that must never be staged (matched against path)
|
|
FORBIDDEN_PATHS=(
|
|
'vault_pass$'
|
|
'\.secret$'
|
|
'\.vault$'
|
|
'_ed25519$'
|
|
'_rsa$'
|
|
'_dsa$'
|
|
'_ecdsa$'
|
|
'user_credentials\.json$'
|
|
'\.env$'
|
|
)
|
|
|
|
# Regex patterns that indicate secrets in file content.
|
|
# Each is matched with: grep -qiE -e "$pattern"
|
|
SECRET_PATTERNS=(
|
|
'BEGIN (RSA|EC|DSA|OPENSSH) PRIVATE KEY'
|
|
'password[[:space:]]*[:=][[:space:]]*["\x27]?[A-Za-z0-9+/!@#$%^&*]{16,}'
|
|
'secret_key[[:space:]]*[:=][[:space:]]*["\x27][A-Za-z0-9+/]{24,}'
|
|
'internal_token[[:space:]]*[:=][[:space:]]*["\x27][A-Za-z0-9+/]{24,}'
|
|
'token[[:space:]]*[:=][[:space:]]*["\x27][a-f0-9]{40}'
|
|
'api[_-]?key[[:space:]]*[:=][[:space:]]*["\x27][A-Za-z0-9]{24,}'
|
|
)
|
|
|
|
# Lines matching these are skipped before pattern matching (false-positive suppression)
|
|
SAFE_PATTERNS=(
|
|
'\$ANSIBLE_VAULT'
|
|
'\{\{.*\}\}'
|
|
'^[[:space:]]*#'
|
|
'vault_password_file'
|
|
'^vault_[a-z_]*:'
|
|
'grafana_admin_password: admin'
|
|
'_password:.*vault_'
|
|
'Example\|example\|placeholder\|CHANGEME'
|
|
)
|
|
|
|
staged=$(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null)
|
|
[[ -z "$staged" ]] && exit 0
|
|
|
|
# --- Check forbidden file paths ---
|
|
for f in $staged; do
|
|
for pattern in "${FORBIDDEN_PATHS[@]}"; do
|
|
if echo "$f" | grep -qE -e "$pattern"; then
|
|
errors+=("FORBIDDEN FILE: $f (matches: $pattern)")
|
|
FAIL=1
|
|
fi
|
|
done
|
|
done
|
|
|
|
# --- Check staged content for secret patterns ---
|
|
for f in $staged; do
|
|
# Skip binary files
|
|
if git diff --cached -- "$f" | grep -qP '^\+.*[\x00-\x08\x0b-\x1f\x7f-\xff]' 2>/dev/null; then
|
|
continue
|
|
fi
|
|
|
|
while IFS= read -r line; do
|
|
# Strip leading '+' from diff hunk line
|
|
content="${line:1}"
|
|
|
|
# Skip safe/allowlisted lines
|
|
skip=0
|
|
for safe in "${SAFE_PATTERNS[@]}"; do
|
|
if echo "$content" | grep -qE -e "$safe"; then
|
|
skip=1
|
|
break
|
|
fi
|
|
done
|
|
[[ $skip -eq 1 ]] && continue
|
|
|
|
# Check against secret patterns
|
|
for pattern in "${SECRET_PATTERNS[@]}"; do
|
|
if echo "$content" | grep -qiE -e "$pattern"; then
|
|
# Redact the actual value in the error message
|
|
redacted=$(echo "$content" | sed -E 's/([=:][[:space:]]*["\x27]?)[A-Za-z0-9+\/!@#$%^&*]{8,}/\1**REDACTED**/g')
|
|
errors+=("SECRET in $f: ${redacted:0:120}")
|
|
FAIL=1
|
|
break
|
|
fi
|
|
done
|
|
done < <(git diff --cached -- "$f" | grep -E '^\+[^+]')
|
|
done
|
|
|
|
# --- Report ---
|
|
if [[ ${FAIL} -eq 1 ]]; then
|
|
echo "" >&2
|
|
echo "pre-commit: potential secrets detected — commit blocked" >&2
|
|
echo "" >&2
|
|
for e in "${errors[@]}"; do
|
|
echo " ✗ $e" >&2
|
|
done
|
|
echo "" >&2
|
|
echo " To bypass (only if certain this is a false positive):" >&2
|
|
echo " git commit --no-verify" >&2
|
|
echo "" >&2
|
|
exit 1
|
|
fi
|