#!/usr/bin/env bash # net-kingdom pre-commit hook — blocks plaintext secrets from entering git. # # Enabled by: make hooks (sets core.hooksPath = .githooks) # Tested by: make hooks-test # # BLOCKS: # 1. Any file under secrets/ without a SOPS marker # 2. Any *.env file outside sso-mfa/bootstrap/ (env files contain raw secrets) # 3. Any staged file containing known plaintext secret patterns # # WARNS: # - Bundle archives (*-bundle*.tar.age) — these belong offsite, not in git set -euo pipefail fail=0 # ── Helper ──────────────────────────────────────────────────────────────────── staged_files() { git diff --cached --name-only --diff-filter=ACMR } file_content() { git show ":$1" 2>/dev/null || true } # ── Check 1: secrets/ files must be SOPS-encrypted ─────────────────────────── secrets_files=$(staged_files | grep -E '(^|/)secrets/' || true) if [[ -n "$secrets_files" ]]; then while IFS= read -r f; do [[ -z "$f" ]] && continue content="$(file_content "$f")" [[ -z "$content" ]] && continue # SOPS-encrypted files contain a top-level 'sops:' or '"sops":' key if echo "$content" | grep -qE '^[[:space:]]*sops:[[:space:]]*$|"sops"[[:space:]]*:'; then continue fi # Also allow binary age-encrypted or gpg-encrypted blobs case "$f" in *.age|*.gpg) continue ;; esac echo " [secrets/] not SOPS-encrypted: $f" fail=1 done <<< "$secrets_files" fi # ── Check 2: *.env files outside sso-mfa/bootstrap/ ───────────────────────── env_files=$(staged_files | grep -E '\.env$' | grep -v '^sso-mfa/bootstrap/' || true) if [[ -n "$env_files" ]]; then while IFS= read -r f; do [[ -z "$f" ]] && continue echo " [*.env outside bootstrap/] $f" fail=1 done <<< "$env_files" fi # ── Check 3: known plaintext secret patterns in any staged file ─────────────── # These patterns appear only in generated secrets files — never in code. FORBIDDEN_PATTERNS=( 'PI_SECRET_KEY=' 'PI_PEPPER=' 'LLDAP_JWT_SECRET=' 'AUTHELIA_JWT_SECRET=' 'AUTHELIA_SESSION_SECRET=' 'AUTHELIA_STORAGE_ENCRYPTION_KEY=' 'AUTHELIA_OIDC_HMAC_SECRET=' 'AUTHELIA_KEYCAPE_CLIENT_SECRET=' 'BREAKGLASS_PASSWORD=' 'PG_ROOT_PASSWORD=' ) while IFS= read -r f; do [[ -z "$f" ]] && continue content="$(file_content "$f")" [[ -z "$content" ]] && continue for pat in "${FORBIDDEN_PATTERNS[@]}"; do if echo "$content" | grep -qF "$pat"; then echo " [secret pattern '$pat'] $f" fail=1 fi done done <<< "$(staged_files)" # ── Warning: bundle archives in git ────────────────────────────────────────── bundle_files=$(staged_files | grep -E '\-bundle.*\.tar\.age$' || true) if [[ -n "$bundle_files" ]]; then echo "" echo "WARN: ops bundle archives detected — these belong offsite, not in git:" while IFS= read -r f; do echo " $f" done <<< "$bundle_files" echo "" fi # ── Result ──────────────────────────────────────────────────────────────────── if [[ "$fail" -ne 0 ]]; then echo "" echo "Commit blocked: plaintext secret(s) detected (see above)." echo "" echo "To encrypt a file with SOPS: sops --encrypt --in-place " echo "To edit an encrypted file: sops " echo "To bypass (never in prod): git commit --no-verify" exit 1 fi exit 0