diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 1e3112d..f1133dc 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,13 +1,13 @@ #!/usr/bin/env bash # seal — pre-commit secret scanner # Blocks commits that stage secrets, private env files, or high-risk key patterns. +# Requires gitleaks: https://github.com/gitleaks/gitleaks set -euo pipefail ROOT="$(git rev-parse --show-toplevel)" HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RED='\033[0;31m' -YELLOW='\033[1;33m' NC='\033[0m' fail() { @@ -15,9 +15,12 @@ fail() { exit 1 } -warn() { - echo -e "${YELLOW}seal: warning — $1${NC}" >&2 -} +# --- 0. gitleaks is required --- +if ! command -v gitleaks >/dev/null 2>&1; then + fail "gitleaks is not installed. Install it first: + brew install gitleaks + https://github.com/gitleaks/gitleaks#installing" +fi # --- 1. Block staging of files that should never be committed --- BLOCKED_FILES=( @@ -50,66 +53,14 @@ for file in $STAGED; do done done -# --- 2. Prefer gitleaks when installed (best coverage) --- -if command -v gitleaks >/dev/null 2>&1; then - CONFIG="$HOOK_DIR/gitleaks.toml" - if [[ ! -f "$CONFIG" ]]; then - CONFIG="$ROOT/gitleaks.toml" - fi - if [[ ! -f "$CONFIG" ]]; then - CONFIG="" - fi - if [[ -n "$CONFIG" ]]; then - gitleaks protect --staged --redact --verbose --config "$CONFIG" --source "$ROOT" - else - gitleaks protect --staged --redact --verbose --source "$ROOT" - fi - echo "seal: gitleaks scan passed" - exit 0 +# --- 2. Scan staged changes with gitleaks --- +CONFIG="$HOOK_DIR/gitleaks.toml" +if [[ ! -f "$CONFIG" ]]; then + CONFIG="$ROOT/gitleaks.toml" fi - -# --- 3. Fallback: pattern scan on staged content (no gitleaks required) --- -PATTERNS=( - 'sk_live_[0-9a-zA-Z]{10,}' - 'sk_test_[0-9a-zA-Z]{10,}' - 'rk_live_[0-9a-zA-Z]{10,}' - 'rk_test_[0-9a-zA-Z]{10,}' - 'whsec_[0-9a-zA-Z]{10,}' - 'sk-[a-zA-Z0-9]{20,}' - 'ghp_[0-9a-zA-Z]{20,}' - 'gho_[0-9a-zA-Z]{20,}' - 'github_pat_[0-9a-zA-Z_]{20,}' - 'AKIA[0-9A-Z]{16}' - 'xox[baprs]-[0-9A-Za-z\-]{10,}' -) - -FOUND=0 -for file in $STAGED; do - # Skip allowlisted paths - case "$file" in - *.env.example|*.env.sample|README.md|SECURITY.md|gitleaks.toml) continue ;; - esac - - if [[ ! -f "$ROOT/$file" ]]; then - continue - fi - - # Skip binary files - if file -b --mime-type "$ROOT/$file" 2>/dev/null | grep -qE '^(image|video|audio|application/(zip|gzip|pdf|octet-stream))'; then - continue - fi - - for pattern in "${PATTERNS[@]}"; do - if grep -qE "$pattern" "$ROOT/$file" 2>/dev/null; then - echo -e "${RED}seal: possible secret in $file (matched: $pattern)${NC}" >&2 - FOUND=1 - fi - done -done - -if [[ "$FOUND" -eq 1 ]]; then - fail "secret-like value detected in staged files. Install gitleaks for deeper scanning: brew install gitleaks" +if [[ ! -f "$CONFIG" ]]; then + fail "gitleaks.toml not found next to the hook or in the repo root" fi -warn "gitleaks not installed — using basic pattern scan only. Run: brew install gitleaks" -echo "seal: basic scan passed" +gitleaks protect --staged --redact --verbose --config "$CONFIG" --source "$ROOT" +echo "seal: gitleaks scan passed"