#!/usr/bin/env bash # ============================================================================= # .git/hooks/pre-commit # Fail-safe security hook: prevents plaintext commits of encrypted files. # Reads encryption requirements dynamically from .gitattributes via # git check-attr — no hardcoded paths, inherits all future rules automatically. # ============================================================================= set -euo pipefail FAILED=0 # Verify git-crypt is initialized if ! git-crypt status >/dev/null 2>&1; then printf "\n[CRITICAL] git-crypt not initialized.\n" exit 1 fi # Get staged files (additions, copies, modifications — no deletions) STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true) [[ -z "$STAGED_FILES" ]] && exit 0 while IFS= read -r file; do # Dynamically check if this file requires git-crypt encryption filter=$(git check-attr filter -- "$file" 2>/dev/null | sed 's/.*: //') [[ "$filter" != "git-crypt" ]] && continue # File is required to be encrypted — verify it actually is status=$(git-crypt status "$file" 2>/dev/null || printf "error") if printf '%s\n' "$status" | grep -q "not encrypted"; then printf "\n\033[0;31m[SECURITY ALERT] PLAINTEXT LEAK DETECTED\033[0m\n" printf -- "-----------------------------------------------------------\n" printf "File: %s\n" "$file" printf "Status: Marked for git-crypt in .gitattributes but NOT encrypted.\n" printf "Action: Verify .gitattributes rules and re-run 'git-crypt init'.\n" printf -- "-----------------------------------------------------------\n" FAILED=1 fi done <<< "$STAGED_FILES" if [[ "$FAILED" -ne 0 ]]; then printf "\n\033[0;31mCommit blocked: security policy violation.\033[0m\n\n" exit 1 fi exit 0