From 101deb954640b72527211a38729de2c9ddd979ba Mon Sep 17 00:00:00 2001 From: cesnimda Date: Wed, 1 Jul 2026 10:14:37 +0200 Subject: [PATCH] chore: add version-controlled git hooks + installer pre-commit runs fast checks (block committed .env/secrets, verify dotnet format); pre-push mirrors CI (Release build + tests + frontend build) to catch failures before they leave the machine. install-hooks.ps1 wires core.hooksPath so the hooks are shared and reviewable rather than living in un-tracked .git/hooks. Bypass with --no-verify; CI still enforces the gate server-side. Co-Authored-By: Claude Opus 4.8 --- scripts/git-hooks/pre-commit | 43 ++++++++++++++++++++++++++++++++++++ scripts/git-hooks/pre-push | 21 ++++++++++++++++++ scripts/install-hooks.ps1 | 24 ++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100755 scripts/git-hooks/pre-commit create mode 100755 scripts/git-hooks/pre-push create mode 100644 scripts/install-hooks.ps1 diff --git a/scripts/git-hooks/pre-commit b/scripts/git-hooks/pre-commit new file mode 100755 index 0000000..4e8ca9e --- /dev/null +++ b/scripts/git-hooks/pre-commit @@ -0,0 +1,43 @@ +#!/usr/bin/env sh +# InboxIntel pre-commit hook — fast checks only (keep it under a few seconds). +# Heavier build/test verification runs in pre-push. Bypass in an emergency with: +# git commit --no-verify +set -eu + +echo "[pre-commit] running fast checks..." + +# 1) Block obvious secrets from being committed. Scans only staged, added lines. +STAGED=$(git diff --cached --name-only --diff-filter=ACM) +if [ -n "$STAGED" ]; then + # Never allow a real .env (only *.example templates are tracked). + echo "$STAGED" | grep -E '(^|/)\.env($|\.)' | grep -v '\.example$' >/tmp/ii_env_hits 2>/dev/null || true + if [ -s /tmp/ii_env_hits ]; then + echo "[pre-commit] BLOCKED: attempting to commit an env/secret file:" + cat /tmp/ii_env_hits + echo " -> add it to .gitignore or commit .env.example instead." + exit 1 + fi + + # Heuristic secret scan on added lines (private keys, obvious credential assigns). + if git diff --cached --unified=0 -- $STAGED \ + | grep -E '^\+' \ + | grep -Ei 'BEGIN (RSA|OPENSSH|EC|PGP) PRIVATE KEY|(client_secret|password|api[_-]?key|secret)\s*[:=]\s*["'"'"']?[A-Za-z0-9/_+=-]{16,}' \ + | grep -Evi 'change-me|your-|example|placeholder|\$\{' >/tmp/ii_secret_hits 2>/dev/null; then + echo "[pre-commit] BLOCKED: possible hard-coded secret in staged changes:" + cat /tmp/ii_secret_hits + echo " -> use deploy/.env / user-secrets. Override with 'git commit --no-verify' if this is a false positive." + exit 1 + fi +fi + +# 2) Format .NET code (only if the tool + staged .cs files are present). Verify-only, +# non-mutating, so it never rewrites files out from under your staged diff. +if echo "$STAGED" | grep -q '\.cs$'; then + if command -v dotnet >/dev/null 2>&1 && dotnet format --help >/dev/null 2>&1; then + echo "[pre-commit] dotnet format --verify-no-changes" + dotnet format InboxIntel.sln --verify-no-changes --verbosity quiet \ + || { echo " -> run 'dotnet format InboxIntel.sln' and re-stage."; exit 1; } + fi +fi + +echo "[pre-commit] OK" diff --git a/scripts/git-hooks/pre-push b/scripts/git-hooks/pre-push new file mode 100755 index 0000000..0827fc1 --- /dev/null +++ b/scripts/git-hooks/pre-push @@ -0,0 +1,21 @@ +#!/usr/bin/env sh +# InboxIntel pre-push hook — build + test gate. Mirrors what Gitea CI runs so you +# catch failures before they reach the server. Bypass with: git push --no-verify +set -eu + +echo "[pre-push] building solution (Release)..." +dotnet build InboxIntel.sln -c Release --nologo \ + || { echo "[pre-push] BLOCKED: backend build failed."; exit 1; } + +echo "[pre-push] running tests..." +dotnet test InboxIntel.sln -c Release --no-build --nologo \ + || { echo "[pre-push] BLOCKED: tests failed."; exit 1; } + +# Frontend build (only if the app is present and npm is installed). +if [ -f frontend/package.json ] && command -v npm >/dev/null 2>&1; then + echo "[pre-push] frontend build..." + ( cd frontend && npm run build --silent ) \ + || { echo "[pre-push] BLOCKED: frontend build failed."; exit 1; } +fi + +echo "[pre-push] OK — safe to push." diff --git a/scripts/install-hooks.ps1 b/scripts/install-hooks.ps1 new file mode 100644 index 0000000..122b037 --- /dev/null +++ b/scripts/install-hooks.ps1 @@ -0,0 +1,24 @@ +<# +.SYNOPSIS + Point git at the version-controlled hooks in scripts/git-hooks. +.DESCRIPTION + Uses `git config core.hooksPath` so the hooks live in the repo (reviewable, + shared, updatable) instead of the un-tracked .git/hooks directory. Run once + per clone. Git for Windows ships the bash needed to execute the POSIX hooks. +.EXAMPLE + ./scripts/install-hooks.ps1 +#> +$ErrorActionPreference = 'Stop' +$root = Split-Path -Parent $PSScriptRoot +Push-Location $root +try { + git config core.hooksPath scripts/git-hooks + # Best-effort exec bit (matters on Linux/WSL; harmless on Windows). Only applies + # once the hooks are tracked; ignored on a first run before they're committed. + foreach ($h in 'pre-commit','pre-push') { + try { git update-index --chmod=+x "scripts/git-hooks/$h" 2>$null } catch {} + } + Write-Host "Installed git hooks -> scripts/git-hooks (core.hooksPath set)." -ForegroundColor Green + Write-Host "Bypass in an emergency with --no-verify." -ForegroundColor DarkGray +} +finally { Pop-Location }