#!/usr/bin/env bash # .githooks/pre-commit — blocks sensitive files and credential patterns from being committed set -euo pipefail RED='\033[0;31m'; YELLOW='\033[1;33m'; BOLD='\033[1m'; NC='\033[0m' BLOCKED=0 STAGED=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null) if [[ -z "$STAGED" ]]; then exit 0 fi # ── Blocked filenames ────────────────────────────────────────────────────────── BLOCKED_FILES=( ".env" ".env.local" ".env.production" ".env.staging" "*.pem" "*.key" "*.p12" "*.pfx" "id_rsa" "id_ecdsa" "id_ed25519" "id_dsa" "*.ppk" "secrets.yml" "secrets.yaml" "credentials.json" "service-account*.json" "*.keystore" "htpasswd" ".htpasswd" ) while IFS= read -r file; do filename="$(basename "$file")" for pattern in "${BLOCKED_FILES[@]}"; do # shellcheck disable=SC2254 case "$filename" in $pattern) echo -e "${RED}BLOCKED:${NC} ${BOLD}$file${NC} matches blocked filename pattern '${YELLOW}$pattern${NC}'" BLOCKED=1 ;; esac done done <<< "$STAGED" # ── Blocked content patterns ─────────────────────────────────────────────────── declare -A CONTENT_PATTERNS=( ["RSA/EC private key header"]="-----BEGIN (RSA|EC|DSA|OPENSSH) PRIVATE KEY" ["AWS access key"]="AKIA[0-9A-Z]{16}" ["GitHub token"]="ghp_[A-Za-z0-9]{36}" ["Generic API key assignment"]="(api_key|API_KEY|secret_key|SECRET_KEY)\s*=\s*['\"][A-Za-z0-9_\-]{16,}" ["Stripe secret key"]="sk_(live|test)_[A-Za-z0-9]{24,}" ["Forgejo/Gitea token (40 hex chars)"]="[a-f0-9]{40}" ) while IFS= read -r file; do # Skip binary files if git diff --cached -- "$file" | grep -qP "^\+.*\x00"; then continue fi for label in "${!CONTENT_PATTERNS[@]}"; do pattern="${CONTENT_PATTERNS[$label]}" matches=$(git diff --cached -- "$file" | grep "^+" | grep -cP "$pattern" 2>/dev/null || true) if [[ "$matches" -gt 0 ]]; then echo -e "${RED}BLOCKED:${NC} ${BOLD}$file${NC} contains pattern matching '${YELLOW}$label${NC}'" BLOCKED=1 fi done done <<< "$STAGED" # ── Result ───────────────────────────────────────────────────────────────────── if [[ "$BLOCKED" -eq 1 ]]; then echo "" echo -e "${RED}Commit rejected.${NC} Remove sensitive files/content before committing." echo -e "To bypass in an emergency: ${YELLOW}git commit --no-verify${NC} (use with extreme caution)" exit 1 fi exit 0