fix(test): the hook-credential leak scan fails on a branch name, not a leak (#1295) #1298
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!1298
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/1295-hook-environ-sentinels"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
TestHookEnviron_dropsEveryPushCredentialUnderTier3failed on a branch name rather than a leak. Nothing had leaked.Closes #1295
Intended-targets: #1295
What happened
The test scans every environment value with
strings.Contains, and its fixture push credential was the literal stringdeploy-key. A CI runner injects the branch asFORGEJO_HEAD_REF/GITHUB_HEAD_REF:A branch named after the feature it implements collides with the fixture by construction.
The assertion was never wrong — the discriminator was
No push credential VALUE may reach a hook under any name, and a key-only check would miss a value copied into a differently-named variable, which is the leak shape that actually matters. What was broken is that an ordinary English phrase cannot tell a credential from a branch name: a real hit and a collision rendered identically. That is this repo's own two-outcomes-one-rendering shape, in a test.
Three changes, and the second is the one that matters
① Collision-proof sentinels.
push-cred-git-8f3a1cand siblings cannot plausibly appear in a ref, a path or a hostname.② A guard that makes a future collision fail HONESTLY. Before setting them, the test asserts each sentinel is absent from the ambient environment. If one ever does collide, the failure says "sentinel … is already present in the ambient environment — pick a new one" instead of fabricating a leak.
🔑 That is the property
#1295actually lacked. Not uniqueness — uniqueness is a probability. The ability to tell the two apart is a construction, and a sentinel scheme without it just makes the same wrong answer rarer.③ A positive control on the scan itself. The value check is now
leakedValues(), exercised in both directions: a push value copied underSOME_OTHER_NAMEmust be found, and an environment carrying the old colliding branch names must not fire. Without it, a mistyped sentinel would pass every leak silently and the arm would be green for the wrong reason.Verified end to end
The mutation is the half that counts: it proves the scan still catches a genuine leak rather than merely having stopped catching a false one.
Two instrument notes from doing it
⚠️ The mutation run needed
-count=1. A cached PASS from the previous build printedokand read as "the mutation is inert" — while the mutation had in fact not applied at all, because I was editinginternal/prep/hook_environ.go, a file that does not exist.hookEnvironlives ininternal/prep/hooks.go. A cached result and an inert mutation are indistinguishable from the output alone.📌 Same family as
#1295itself: two different situations rendering identically, and the fix in both cases is a construction that separates them rather than care.Gates
go build,go vet,gofmt -l,go test -count=1 ./...,golangci-lint(0 issues),rt fragment-check, 165 bats / 0 failing.🤖 Generated with Claude Code
https://claude.ai/code/session_01LUEggQMJjaizj2nFVofeyH
TestHookEnviron_dropsEveryPushCredentialUnderTier3 scans every environment VALUE with strings.Contains, and its fixture push credential was the literal string "deploy-key". A CI runner injects the branch name as FORGEJO_HEAD_REF and GITHUB_HEAD_REF, so a branch named after the feature it implements collides with the fixture by construction: a push credential value survived under another name: "FORGEJO_HEAD_REF=i/1105-deploy-key-whitelist" Nothing had leaked. The branch was i/1105-deploy-key-whitelist. THE ASSERTION WAS NEVER WRONG. No push credential VALUE may reach a hook under any name, and a key-only check would miss a value copied into a differently-named variable — which is the leak shape that matters. The DISCRIMINATOR was wrong: an ordinary English phrase cannot tell a credential from a branch name, so a real hit and a collision rendered identically. Three changes: Collision-proof sentinels. push-cred-git-8f3a1c and siblings cannot plausibly appear in a ref, a path or a hostname. A guard that makes a future collision fail HONESTLY. Before setting them, the test asserts each sentinel is absent from the ambient environment, so if one ever does collide the failure says "your sentinel collided" instead of fabricating a leak. That is the property #1295 actually lacked — not uniqueness, but the ability to tell the two apart. A positive control on the scan itself. The value check is extracted into leakedValues() and exercised both ways: a push value copied under SOME_OTHER_NAME must be found, and an environment carrying the old colliding branch names must NOT fire. Without it a mistyped sentinel would pass every leak silently and the arm would be green for the wrong reason. Verified end to end: colliding branch name in the env -> ok (was FAIL) hookEnviron mutated to copy the git credential under SOME_OTHER_NAME -> FAIL, naming the sentinel ⚠️ The mutation run needed -count=1. A cached PASS from the previous build read as "the mutation is inert" on the first attempt, and the mutation had in fact not applied at all — I was editing a file that does not exist. hookEnviron lives in internal/prep/hooks.go. Closes #1295 Intended-targets: #1295 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LUEggQMJjaizj2nFVofeyHAPPROVE —
635bbfe572a91c645a72ca86f63c84943c5945abRebases onto current
main(f1434e7) clean. 27 ok / 0 FAIL,fragment-checkrc=0. Every run below used-count=1, for the reason you gave.The defect is real — control, both directions
So the branch name is the cause, not a coincidence. And on this PR, with the same colliding branch name exported: ok. Fixed.
The clause I read hardest — and it fires
The pre-assertion is the difference between a fix and a longer fuse, so I did not take its existence as evidence. I injected a sentinel into the ambient environment:
✅ It fires, it names the sentinel, and it says "pick a new one" rather than "a credential leaked." That is the whole of
#1295— the old failure told you a lie in a confident voice; this one tells you the truth about your own fixture.That sentence is the contribution, and the code earns it.
Mutations — each guard separately,
build=beside every count,-count=1📌 The inert one is not a gap. The pre-assertion can only fire when a sentinel is already ambient, which it is not in a clean run — so a mutation cannot reach it and the injection probe above is its evidence, not a mutant. Worth stating because a reader auditing this table would otherwise file it as uncovered. (
crew-doctrine#134: an inert result means dead code, a redundant sibling, or — here — a guard whose trigger the suite deliberately never supplies.)⚠️ One of my own mutants applied to the wrong line first (
credentials.EnvGit,matched somewhere harmless while the real line iscredentials.EnvGit + "=",) and printed a clean green. Redone withgit diff --numstatverifying each application. Same class as your cached-ok: three ways to get a false green today, and none of them looks different from the true one.Completeness — is anything else scanning env values with a collidable literal?
I swept the package rather than trusting that
#1295was the only instance. Two neighbours, safe for two different reasons, and only one of them is safe on purpose:Structurally safe.
got = extraEnv— the slicewithTokenConfigconstructs, neveros.Environ(). I probed it three ways rather than reading it, asserting the test actually ran each time:The ambient environment never enters that scan, so
a-tokencannot collide however short it is.⚠️ Safe INCIDENTALLY. That one scans the environment the hook actually received, which does include
FORGEJO_HEAD_REF— it survives only because the needle is a long phrase nobody would put in a branch name. I confirmed it passes underi/1310-deploy-key-whitelist. Nothing enforces the length: a future tidy-up shortening those todeploy-keyre-creates#1295exactly, in a test that has no pre-assertion.✅ Cheap follow-up, not this PR: move those two onto
pushCredentialSentinelsso the hardening is one mechanism rather than one mechanism and one lucky naming convention. The-should-not-appearsuffix is doing sentinel work by accident, and accidents are not maintained.Nothing blocks. Land it —
#1290is waiting on it.