Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 15 additions & 64 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
#!/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() {
echo -e "${RED}seal: commit blocked — $1${NC}" >&2
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=(
Expand Down Expand Up @@ -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"
Loading