bureaucat / .git-hooks /pre-commit
ravinsingh15's picture
Bureaucat β€” Build Small Hackathon submission (Qwen3-VL-8B, ZeroGPU, gr.Server)
6b5e47d
Raw
History Blame Contribute Delete
3.6 kB
#!/usr/bin/env bash
# Pre-commit hook: block staging of text files containing Swedish personnummer patterns.
# Installed via: git config core.hooksPath .git-hooks
#
# WHAT IT SCANS: text files with these extensions staged for commit:
# .py .txt .md .json .csv .yaml .yml .toml .sh
#
# WHAT IT DOES NOT SCAN: image files (PNG, JPG, etc.).
# Image redaction is a MANUAL VISUAL gate β€” verify PNG/JPG files by eye before committing.
#
# Pattern covers:
# 10-digit: YYMMDD-XXXX (e.g., 901215-1234)
# 12-digit: YYYYMMDD-XXXX (e.g., 19901215-1234)
# Plus-separator (100+ years): YYMMDD+XXXX
# Samordningsnummer: same formats; day-part 61-91 is not distinguished (any match blocked)
set -euo pipefail
# Patterns (regex alternation):
# [0-9]{6}[+-][0-9]{4} β€” 10-digit form with hyphen or plus
# [0-9]{8}-[0-9]{4} β€” 12-digit full-year form with hyphen
PATTERN='[0-9]{6}[+-][0-9]{4}|[0-9]{8}-[0-9]{4}'
# Get staged text files (added, copied, modified, renamed β€” not deleted)
staged_files=$(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null | \
grep -E '\.(py|txt|md|json|csv|yaml|yml|toml|sh)$' || true)
if [ -z "$staged_files" ]; then
exit 0
fi
# Determine scan backend: prefer grep -P (PCRE); fall back to python3 if unavailable.
# macOS BSD grep may not support -P; the fallback ensures portability.
#
# The probe MUST actually exercise a digit-dash-digit match, or it silently
# always-fails and never selects grep. We build the probe digits at runtime via
# printf so this hook file never contains a literal personnummer (which would
# otherwise self-trip the hook when the hook itself is committed).
_grep_supports_P() {
local six="123456" four="7890"
printf '%s-%s\n' "$six" "$four" | grep -qP '[0-9]{6}-[0-9]{4}' 2>/dev/null
}
# Scan convention (MUST match _scan_file_python): exit 0 = CLEAN (no PII),
# exit non-zero = PII FOUND. `grep -qP` exits 0 when it FINDS a match, so it is
# negated here to align the two backends β€” otherwise the loop guard below would
# be inverted for the grep path (the CR-01 bug).
_scan_file_grep() {
local f="$1"
! grep -qP "$PATTERN" "$f" 2>/dev/null
}
_scan_file_python() {
local f="$1"
python3 - "$f" <<'PYEOF'
import re, sys
pattern = re.compile(r'[0-9]{6}[+-][0-9]{4}|[0-9]{8}-[0-9]{4}')
with open(sys.argv[1], errors='replace') as fh:
sys.exit(0 if not pattern.search(fh.read()) else 1)
PYEOF
}
_get_matches_grep() {
local f="$1"
grep -nP "$PATTERN" "$f" 2>/dev/null | head -3 || true
}
_get_matches_python() {
local f="$1"
python3 - "$f" <<'PYEOF'
import re, sys
pattern = re.compile(r'[0-9]{6}[+-][0-9]{4}|[0-9]{8}-[0-9]{4}')
with open(sys.argv[1], errors='replace') as fh:
for i, line in enumerate(fh, 1):
if pattern.search(line):
print(f"{i}:{line.rstrip()}")
PYEOF
}
# Select backend once
if _grep_supports_P; then
SCAN_CMD=_scan_file_grep
MATCH_CMD=_get_matches_grep
else
SCAN_CMD=_scan_file_python
MATCH_CMD=_get_matches_python
fi
found=0
while IFS= read -r file; do
if [ -f "$file" ] && ! $SCAN_CMD "$file"; then
echo "ERROR: Possible personnummer found in: $file"
$MATCH_CMD "$file"
found=1
fi
done <<< "$staged_files"
if [ "$found" -eq 1 ]; then
echo ""
echo "COMMIT BLOCKED: Remove or redact personnummer patterns before committing."
echo "Pattern matched: YYMMDD-XXXX, YYYYMMDD-XXXX, or YYMMDD+XXXX"
echo ""
echo "NOTE: Image files (PNG/JPG) are NOT scanned β€” verify PNG/JPG redaction visually."
echo " Real letters must live in data/letters/private/ (gitignored), never in public/."
exit 1
fi
exit 0