2 Commits

Author SHA1 Message Date
joakimp 50065e894f hooks: call the shared freshness notice
Lint / docs-check (push) Successful in 7s
Lint / hadolint (push) Successful in 10s
Lint / actionlint (push) Successful in 23s
git runs whatever hooks/ holds today, so a clone that ran ./setup-hooks.sh once
and never pulled runs an old gate while looking perfectly configured. Measured on
tor-ms22 (logstream seq 172/173): core.hooksPath correct, .git/hooks clean, and
no pre-push gate at all because the clone predated the commit that added it.

Implementation: myconfigs b7acaa0, common/hooks/hook-freshness.sh — one copy for
all six tracked-hook repos, not five vendored copies of a drift detector. SOFT
dependency here: this repo has no myconfigs locator, so the call tries
$MYCONFIGS_DIR, the sibling, then $HOME/myconfigs, and stays SILENT if none
exist. It always exits 0.

Verified: stale remote -> notice naming the commit; in sync -> silent; rc
identical across both, so the gitleaks verdict below is untouched.
2026-09-21 13:38:57 +02:00
joakimp ebabdcef01 hooks: track the pre-commit hook instead of generating a copy of it
Lint / hadolint (push) Successful in 11s
Lint / docs-check (push) Successful in 9s
Lint / actionlint (push) Successful in 17s
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.
2026-09-20 00:27:50 +02:00