From ebabdcef018494fd22940ba82568f9eb102c924f Mon Sep 17 00:00:00 2001 From: Joakim Persson Date: Sun, 20 Sep 2026 00:27:50 +0200 Subject: [PATCH] hooks: track the pre-commit hook instead of generating a copy of it setup-hooks.sh used to WRITE .git/hooks/pre-commit from a heredoc. That makes the running hook a copy of the versioned intent, and a copy drifts. It already had: the hook installed on this machine was an older revision than this script emits, having lost the Linux gitleaks install hint (`uname -s` line) that the heredoc has. Nothing reported that, because a stale hook still prints a reassuring banner. The hook body is now a tracked file at hooks/pre-commit -- extracted byte-for-byte from the heredoc, so this commit changes where the hook lives, not what it does -- wired via `git config core.hooksPath hooks`. git then executes the tracked file itself, so the hook that runs and the hook in history cannot disagree. This is the convention docker-compose-repo already uses. setup-hooks.sh keeps its name (README references it) and now activates rather than generates. It also warns if .git/hooks/pre-commit still exists, because after core.hooksPath is set that file is INERT and silently shadowed -- a decoy that looks like protection. The leftover copy on this clone was removed. VERIFIED, and the first attempt was a false pass worth recording: a planted `AKIAIOSFODNN7EXAMPLE` was NOT blocked and the test commit went through. The gate was fine -- gitleaks ALLOWLISTS that string, since it is AWS's own documentation example. A control built from a well-known example credential proves nothing. That commit was reset (HEAD back to 69fc80a = origin/main) and the control rebuilt from a synthetic RSA private key block, first confirmed detectable by two independent routes (`gitleaks detect --no-git` on the file, then `gitleaks protect --staged` in-repo) BEFORE being trusted as a control. Through the hook: rc=1, "commit blocked", HEAD unchanged. Also confirmed core.hooksPath=hooks is set and shellcheck is clean on both files. Test artefacts deleted; nothing leaked into history. --- README.md | 2 +- hooks/pre-commit | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100755 hooks/pre-commit diff --git a/README.md b/README.md index aa6f1e1..aecd5b4 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ cd opencode-devbox cp .env.example .env # Edit .env with your provider, API key, workspace path, git config -# Install git hooks (secret scanning) before committing +# Activate the tracked git hooks (secret scanning) before committing brew install gitleaks # macOS / Linuxbrew ./setup-hooks.sh diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 0000000..5f8163a --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,26 @@ +#!/bin/bash +# Pre-commit hook — scans staged files for secrets using gitleaks + +if ! command -v gitleaks >/dev/null 2>&1; then + echo "" + echo "⚠️ gitleaks is not installed — skipping secret scan" + echo " Install: brew install gitleaks (macOS)" + echo " Or: curl -sSL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_\$(uname -s)_\$(uname -m).tar.gz | sudo tar -xz -C /usr/local/bin gitleaks" + echo "" + exit 0 +fi + +echo "🔒 Scanning for secrets..." + +if gitleaks protect --staged --no-banner 2>/dev/null; then + echo "✅ No secrets detected" + exit 0 +else + echo "" + echo "❌ Secrets detected in staged changes — commit blocked" + echo "" + echo " Details: gitleaks protect --staged --verbose" + echo " Bypass: git commit --no-verify" + echo "" + exit 1 +fi