sec(hooks): the claim gate's deny text interpolates peer-written values into prose a model then acts on (BACKLOG #1040) - #547
Merged
Conversation
…deny prose (BACKLOG #1040) The claim gate's deny text is read by a model that then does what it says, and every value it interpolates comes from a file another session wrote. Nothing folded them. THE SHARPEST VALUE IS `note`. It is free text any peer supplies with `claim.ps1 -Take <n> -Note "<what>"`, it runs to several hundred characters in this repo routinely, and nothing constrains its content. `worktree` and `branch` are folded too -- a refname is not inert either, since `git check-ref-format` accepts `;`, `$`, `|`, `"` and `'`, which is #1040's own instance one. THE DEFECT IS ON RECORD RATHER THAN IMAGINED. #1040 instance two: a `Write` whose `file_path` carried embedded newlines produced a worktree_gate.ps1 reason with TWO `Do this instead:` blocks, the forged one FIRST, so a model reading top-down reaches the injected command before the real remedy. It needed nothing on disk -- only the JSON field -- so no other gate saw it. The PowerShell side was closed then; this is the Python residual the re-score names, at claim_check.py's ANOTHER-worktree block. A LOCAL HELPER, NOT A SHARED MODULE, and the reason is mechanical. install-git-hooks.ps1 COPIES this file into the git hooks directory and runs it from there (`exec "$PY" "$HOOK_DIR/claim_check.py"`), so an import from scripts/hooks/ resolves at development time and fails when the gate actually runs. collision_gate.ps1 took a local copy of its PowerShell equivalent for exactly this reason, recorded on #1040. CONTROL CHARACTERS, NOT ONLY NEWLINES. A lone \x1b can rewrite a rendered line and \x08 can erase what precedes it, so a value containing no newline is not therefore inert. THE TESTS ASSERT THE PROPERTY, NOT THE PRESENCE OF THE HELPER. A test that the fold function exists cannot tell a working fold from one that is never called -- the "control that cannot fire" shape #1313 found in the sdist leak gate. So they assert that a hostile note CANNOT INTRODUCE A LINE, with a positive control proving the payload really contains the lines being folded, plus a wiring check that reads the deny block's source and fails if any `claim.get(...)` reaches an f-string unfolded. That last one is what would catch a future field added without the fold. MUTATION-CHECKED, AND MY FIRST MUTANT WAS THE WRONG ONE. Neutering the whitespace-collapse line left all six tests green, because newlines are actually folded by the control-character pass above it -- I had removed a line that was not doing the work. A true passthrough mutant fails 4 of 6; the two survivors are the readability and wiring checks, which correctly do not depend on the fold's internals. NOT DONE HERE, and named so it is not mistaken for covered: ledger_check.py:262 interpolates an ADR filename and push_guard.py:224 interpolates a remote ref. The refname one is genuinely influenceable by #1040's own instance-one reasoning. Both are single sites in files that are not this item's named residual, and folding them belongs with a measurement of their own.
…(BACKLOG #1040) test_every_non_engine_test_is_classified refuses a tests/*.py that imports no engine module and appears in neither tests/tooling_manifest.txt nor _STAYS_WITHOUT_IMPORTING. tests/test_hook_prose_folding.py imports stdlib and pytest only, so it needs the entry. MANIFEST RATHER THAN _STAYS_WITHOUT_IMPORTING, and the manifest's own header decides it: the tier is defined by SUBJECT -- "the development harness (worktree gate, coordination claims, session mail, ledger, announce)". This test's subject is scripts/hooks/claim_check.py, which is the coordination claim gate. _STAYS_WITHOUT_IMPORTING is for files that LOOK like harness and are not, which this is not. PLACED BESIDE tests/test_claim_check.py, THE SIBLING TEST FOR THE SAME FILE, and that is a deliberate second choice. THE LIST IS NOT SORTED -- three alphabetical insertion heuristics each landed it somewhere different, because "first entry greater than mine" lands wherever the local ordering happens to be broken. Chasing a sort order that does not exist produced three wrong placements; the semantic neighbour is unambiguous and reviewable. THIS IS THE THIRD BRANCH TONIGHT TO HIT THIS GATE (542, 544, and this one). The lander's observation is the useful part: the registration is INVISIBLE TO A CONTENT REVIEW and only surfaces if you happen to run that one suite locally. Nothing in the diff of a new test file suggests it needs registering somewhere else. 36 passed across test_tooling_partition.py, test_ci_tooling_gate.py and the test itself.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three files, 142 insertions:
scripts/hooks/claim_check.py, a newtests/test_hook_prose_folding.py, and its manifest entry. No engine source.The defect is on record, not hypothesised
The claim gate's deny text is read by a model that then acts on it, and every value it interpolates comes from a file another session wrote. Nothing folded them.
noteis the sharpest: free text any peer supplies viaclaim.ps1 -Take <n> -Note "<what>", routinely hundreds of characters, with no constraint on content.#1040 instance two is the proof this is not theoretical: a
Writewhosefile_pathcarried embedded newlines produced aworktree_gate.ps1reason with two "Do this instead:" blocks, the forged one first. It needed nothing on disk — only the JSON field — so no other gate saw it. The PowerShell side was closed then; this is the Python residual the re-score names.The wiring test is the part that outlives the fix
It reads the deny block's source and fails if any
claim.get(...)reaches an f-string unfolded. A future field added without the fold goes red rather than shipping.That is a gate on the class rather than the instance — the same shape as the sdist leak-gate defect #1313 found, pointed the right way.
A local helper, not a shared module, for a mechanical reason
install-git-hooks.ps1copies this file into the git hooks directory and runs it from there, so an import fromscripts/hooks/resolves in development and fails when the gate actually runs.collision_gate.ps1took a local PowerShell copy for exactly this reason, recorded on #1040 itself.Two things the author caught in their own work
The first mutant was the wrong one and all six tests stayed green. They neutered the whitespace-collapse line, but newlines are folded by the control-character pass above it — so they had removed a line that was not doing the work. They nearly recorded "the tests do not discriminate" when the mutation was the broken instrument.
The corrected mutation: a passthrough mutant fails 4 of 6. The two survivors are the readability and wiring checks, which correctly do not depend on the fold's internals.
Named as NOT done, so it is not read as covered
ledger_check.py:262interpolates an ADR filename andpush_guard.py:224interpolates a remote ref. The refname one is genuinely influenceable by this item's own instance-one reasoning —git check-ref-formataccepts;,$,|,"and'. Both are single sites in files that are not this item's named residual, and folding them belongs with a measurement of their own.Verification
6 passed;
ruff formatandcheckclean; all pre-commit hooks passed including bandit. Independently screened here over all 142 added lines with controls fired first: 0 secrets, 0 non-ASCII, and the manifest entry present with a bogus-name control returning 0.Not run: the full suite on this branch. CI is its first execution.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
🤖 Generated with Claude Code