#!/bin/bash # Harness commit guard. Inspects STAGED content for footguns before a commit. # # Rollout posture: WARN-ONLY by default (logs + prints, never blocks). This is # deliberate (Task 4): a guard that fails closed can brick every machine's /save. It is # promoted to blocking only after a clean warn window across the fleet. # - default -> warn only, exit 0 # - HARNESS_GUARD_FATAL=1 -> exit 1 on any issue (caller decides to abort) # - SKIP_HARNESS_GUARD=1 -> bypass entirely (logged) # Detects: conflict markers, unencrypted SOPS / private-key material, and a staged # submodule gitlink change (informational). set -uo pipefail ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0 cd "$ROOT" LOG="$ROOT/.claude/harness/guard.log" mkdir -p "$(dirname "$LOG")" 2>/dev/null || true ts() { date '+%Y-%m-%dT%H:%M:%S' 2>/dev/null || echo "?"; } warn() { echo "[harness-guard][WARN] $1"; echo "$(ts) WARN $1" >> "$LOG" 2>/dev/null || true; } if [ "${SKIP_HARNESS_GUARD:-0}" = "1" ]; then echo "[harness-guard] bypassed (SKIP_HARNESS_GUARD=1)" echo "$(ts) BYPASS SKIP_HARNESS_GUARD=1" >> "$LOG" 2>/dev/null || true exit 0 fi ISSUES=0 mapfile -t STAGED < <(git diff --cached --name-only --diff-filter=ACM 2>/dev/null) for f in "${STAGED[@]}"; do [ -n "$f" ] || continue blob=$(git show ":$f" 2>/dev/null) || continue # 1. Conflict markers — require a REAL hunk: both an open (<<<<<<<) AND a close # (>>>>>>>) marker at line start. A lone '=======' line is a markdown setext # underline or a divider, not a conflict, so flagging it alone is a false positive # with no detection value (git always writes all three markers). Requiring the pair # eliminates that vector (verified by test-harness-guard.sh) before FATAL promotion. if printf '%s\n' "$blob" | grep -qE '^<<<<<<< ' && printf '%s\n' "$blob" | grep -qE '^>>>>>>> '; then warn "conflict markers in staged file: $f"; ISSUES=$((ISSUES + 1)) fi # 2. Unencrypted SOPS vault file case "$f" in *.sops.yaml|*.sops.json|*.sops.env) if ! printf '%s\n' "$blob" | grep -qE 'ENC\[|^sops:'; then warn "possible UNENCRYPTED sops file staged: $f"; ISSUES=$((ISSUES + 1)) fi ;; esac # 3. Private key material if printf '%s\n' "$blob" | grep -qE -- '-----BEGIN [A-Z ]*PRIVATE KEY-----'; then warn "private-key material in staged file: $f"; ISSUES=$((ISSUES + 1)) fi done # 4. Submodule gitlink staged (informational — should only happen with --with-submodules) if git diff --cached --submodule=short 2>/dev/null | grep -q '^Submodule '; then warn "submodule gitlink change is staged (intentional only via --with-submodules)" fi if [ "$ISSUES" -gt 0 ]; then echo "[harness-guard] $ISSUES issue(s) found." if [ "${HARNESS_GUARD_FATAL:-0}" = "1" ]; then echo "[harness-guard] FATAL mode -> signalling block." exit 1 fi echo "[harness-guard] WARN-ONLY mode -> not blocking." fi exit 0