#!/usr/bin/env bash # install-hooks.sh — install the Sea Haven security-review git hooks + skill assets. # # Two modes: # install-hooks.sh --global Lay the hooks down once for EVERY repo on this machine: # writes ~/.config/git/hooks/{pre-commit,pre-push}, sets # git config --global core.hooksPath, and links the skill # prompt + finding.schema.json into ~/.claude (Path A). # install-hooks.sh /path/to/repo Per-repo install: copy the hooks into /.git/hooks # (use when a repo sets its own local core.hooksPath, e.g. # husky, which would otherwise shadow the global hook). # install-hooks.sh --help # # The hooks run review.sh --scanners-only (fast, deterministic). The full agentic review is the # on-demand /sh-security-review skill; the nightly VM sweep is the backstop. Idempotent + re-runnable. set -euo pipefail SRC="$(cd "$(dirname "$0")" && pwd)" GLOBAL_HOOKS="$HOME/.config/git/hooks" CLAUDE_DIR="$HOME/.claude" usage() { grep '^#' "$0" | sed 's/^# \{0,1\}//'; } install_one() { # install_one local src="$1" dest="$2" cp "$src" "$dest" chmod +x "$dest" } link_asset() { # link_asset (symlink so the repo stays source of truth) local src="$1" dest="$2" mkdir -p "$(dirname "$dest")" ln -sfn "$src" "$dest" echo " linked $dest -> $src" } case "${1:-}" in -h|--help|"") usage; exit 0;; --global) echo "== Installing Sea Haven security-review hooks globally ==" mkdir -p "$GLOBAL_HOOKS" # Warn (don't clobber silently) if a different global hooksPath is already set. CURRENT="$(git config --global --get core.hooksPath || true)" if [ -n "$CURRENT" ] && [ "$CURRENT" != "$GLOBAL_HOOKS" ]; then echo " WARNING: git config --global core.hooksPath is already '$CURRENT'." >&2 echo " Overwriting it with '$GLOBAL_HOOKS'. Re-point manually if that was intentional." >&2 fi install_one "$SRC/hooks/pre-commit" "$GLOBAL_HOOKS/pre-commit" install_one "$SRC/hooks/pre-push" "$GLOBAL_HOOKS/pre-push" git config --global core.hooksPath "$GLOBAL_HOOKS" echo " installed pre-commit + pre-push -> $GLOBAL_HOOKS" echo " set git config --global core.hooksPath = $GLOBAL_HOOKS" # Path A assets: link the on-demand skill prompt + finding schema into ~/.claude. link_asset "$SRC/skill/sh-security-review.md" "$CLAUDE_DIR/commands/sh-security-review.md" link_asset "$SRC/finding.schema.json" "$CLAUDE_DIR/security-review/finding.schema.json" echo echo "Done. Every repo on this machine is now gated by review.sh --scanners-only before push." echo "Caveats: a repo that sets its OWN local core.hooksPath (e.g. husky) overrides this global hook" echo " — run 'install-hooks.sh ' to gate it per-repo. Skip a repo with a" echo " .security-review-skip file at its root; bypass once with 'git push --no-verify'." ;; --*) echo "unknown option: $1" >&2; usage; exit 2;; *) REPO="$1" [ -d "$REPO/.git" ] || { echo "not a git repo: $REPO" >&2; exit 1; } echo "== Installing security-review hooks into $REPO/.git/hooks ==" for h in pre-commit pre-push; do DEST="$REPO/.git/hooks/$h" [ -f "$DEST" ] && echo " warning: existing $h hook at $DEST will be overwritten" >&2 install_one "$SRC/hooks/$h" "$DEST" echo " installed $h -> $DEST" done echo "note: hooks run deterministic scanners only; full review is /sh-security-review (on demand)." ;; esac