diff --git a/hooks/commit-msg b/hooks/commit-msg new file mode 100755 index 0000000..87fbf08 --- /dev/null +++ b/hooks/commit-msg @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# commit-msg — enforces conventional commit format +set -euo pipefail + +RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m' + +VALID_TYPES="feat|fix|docs|chore|test|refactor|perf|ci|build|security" +MSG_FILE="$1" +MSG=$(head -1 "$MSG_FILE") + +if [[ -z "${MSG// }" ]]; then + echo -e "${RED}Commit rejected:${NC} Commit message is empty." + exit 1 +fi + +if ! echo "$MSG" | grep -qE "^($VALID_TYPES)(\(.+\))?: .+"; then + echo -e "${RED}Commit rejected:${NC} Message does not follow conventional commit format." + echo "" + echo -e " Required: ${YELLOW}type: description${NC} or ${YELLOW}type(scope): description${NC}" + echo -e " Valid types: ${YELLOW}$VALID_TYPES${NC}" + echo "" + echo -e " Your message: ${YELLOW}$MSG${NC}" + echo "" + echo -e " Examples:" + echo -e " ${YELLOW}feat: add cover letter refinement${NC}" + echo -e " ${YELLOW}fix(wizard): handle missing user.yaml gracefully${NC}" + echo -e " ${YELLOW}security: rotate leaked API token${NC}" + exit 1 +fi +exit 0 diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 0000000..f797292 --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# pre-commit — scan staged diff for secrets + PII via gitleaks +set -euo pipefail + +HOOKS_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BASE_CONFIG="$HOOKS_REPO/gitleaks.toml" +REPO_ROOT="$(git rev-parse --show-toplevel)" +REPO_CONFIG="$REPO_ROOT/.gitleaks.toml" + +if ! command -v gitleaks &>/dev/null; then + echo "ERROR: gitleaks not found. Install with: sudo apt-get install gitleaks" + echo " or: https://github.com/gitleaks/gitleaks#installing" + exit 1 +fi + +CONFIG_ARG="--config=$BASE_CONFIG" +[[ -f "$REPO_CONFIG" ]] && CONFIG_ARG="--config=$REPO_CONFIG" + +if ! gitleaks protect --staged $CONFIG_ARG --redact 2>&1; then + echo "" + echo "Commit blocked: secrets or PII detected in staged changes." + echo "Review above, remove the sensitive value, then re-stage and retry." + echo "If this is a false positive, add an allowlist entry to .gitleaks.toml" + exit 1 +fi diff --git a/hooks/pre-push b/hooks/pre-push new file mode 100755 index 0000000..3cee0db --- /dev/null +++ b/hooks/pre-push @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# pre-push — scan full branch history not yet on remote +# Safety net: catches anything committed with --no-verify or before hooks were wired +set -euo pipefail + +HOOKS_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +BASE_CONFIG="$HOOKS_REPO/gitleaks.toml" +REPO_ROOT="$(git rev-parse --show-toplevel)" +REPO_CONFIG="$REPO_ROOT/.gitleaks.toml" + +if ! command -v gitleaks &>/dev/null; then + echo "ERROR: gitleaks not found. Install with: sudo apt-get install gitleaks" + exit 1 +fi + +CONFIG_ARG="--config=$BASE_CONFIG" +[[ -f "$REPO_CONFIG" ]] && CONFIG_ARG="--config=$REPO_CONFIG" + +if ! gitleaks git $CONFIG_ARG --redact 2>&1; then + echo "" + echo "Push blocked: secrets or PII found in branch history." + echo "Use git-filter-repo to scrub, then force-push." + echo "See: https://github.com/newren/git-filter-repo" + exit 1 +fi