diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 93ff2ed..f93b68d 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -69,6 +69,21 @@ the outside and only one of them is evidence. A hook or a CI step that treats a `2` as success has quietly turned the check off. Each script states its codes at the top; read them there. +**And a `2` is not automatically a failure either — it is a question.** The +pre-commit hook learned this the hard way: `secrets.sh` exits `2` on a commit +that only *deletes* files, because a deletion has no added lines to scan, and +that is the correct answer. The hook treated every non-zero as a refusal and +printed *"possible credential in the staged changes"* over a plain `git rm`, so +deletion-only commits were impossible and the reason given was a security +problem that did not exist. + +The rule that came out of it: **on a `2`, ask whether the check could have had +anything to look at.** The hook now refuses a `2` whenever the staged diff adds +lines — because then the scanner did have input and checking nothing is exactly +the failure `2` reports — and accepts it, saying so, when the diff only removes +content. Both directions are proved with `prove-guard.sh`-style injections +before either is believed. + The hooks are the other place work gets stopped: [`architecture/githooks/README.md`](architecture/githooks/README.md) has the one install command and the table of what each hook runs. Note that **`post-commit` diff --git a/docs/architecture/githooks/README.md b/docs/architecture/githooks/README.md index 3ae8e3b..8c5cb69 100644 --- a/docs/architecture/githooks/README.md +++ b/docs/architecture/githooks/README.md @@ -68,6 +68,12 @@ exactly the failure it claimed to catch. It exits `2` for "nothing was checked", and the hook treats `2` as a refusal. A missing schema directory is not a clean schema. +**The secret scan's `2` is handled differently, and deliberately.** A commit that +only deletes files gives it nothing to scan, which is the right answer rather +than a fault — so the hook accepts a `2` when the staged diff adds no lines, and +refuses it whenever the diff adds any. Before that distinction existed, `git rm` +was impossible and the refusal claimed a credential had been found. + **`post-commit` pushes.** That is the intent — the commit that first added a pre-commit hook to the project this came from sat unpushed for a day, guarded and invisible — but it is a surprise if you were not expecting it. It never