fix(hooks): file_write_branch clears only one exact-case key and a plain branch name
Round-2 review of skills#52 committed to main through gitea-mcp with
{"branch":"feat/y8","Branch":"main"}: Go's encoding/json matches keys
case-insensitively and the last one wins, while the hook read only the
lowercase key. Same trick on content emptied a file.
The guard now asks unless exactly one key case-folds to "branch" and
one to "content", both spelled exactly, both strings. The branch is
cleared by shape ([A-Za-z0-9][A-Za-z0-9._/-]*, no "..", no refs/ prefix,
no trailing slash) instead of a blocklist, since refs/heads/main created
a second ref that wedged every API write to main. Whitespace-only
content asks.
11 new cases; 6 were red before the change, 5 pinned untested-but-correct
behaviour the reviewer's flips showed nothing covered.
Refs mathias/infra#508
Bump-Type: minor
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,25 @@ check ask "$(fwb feat/y '')" "file_write_branch emptying a file asks"
|
||||
check ask '{"tool_name":"mcp__plugin_skills_gitea__file_write_branch","tool_input":{"content":"x"}}' "file_write_branch without branch asks"
|
||||
check NONE "$(fwb feat/y x)" "file_write_branch to a feature branch passes"
|
||||
|
||||
# Go's encoding/json matches keys case-insensitively, last one wins: gitea-mcp
|
||||
# wrote to main from {"branch":"feat/y8","Branch":"main"} while the hook read
|
||||
# only `branch` (gatekeeper round 2, verified against a real Gitea). So the
|
||||
# guard clears a write only when exactly one key case-folds to branch/content.
|
||||
FWB=mcp__plugin_skills_gitea__file_write_branch
|
||||
check ask "{\"tool_name\":\"$FWB\",\"tool_input\":{\"branch\":\"feat/y8\",\"Branch\":\"main\",\"content\":\"x\"}}" "case-variant Branch key asks"
|
||||
check ask "{\"tool_name\":\"$FWB\",\"tool_input\":{\"branch\":\"feat/y\",\"content\":\"x\",\"Content\":\"\"}}" "case-variant Content key asks"
|
||||
check ask "{\"tool_name\":\"$FWB\",\"tool_input\":{\"BRANCH\":\"feat/y\",\"content\":\"x\"}}" "only an uppercase BRANCH key asks"
|
||||
check ask "$(fwb Main x)" "file_write_branch to Main asks"
|
||||
check ask "{\"tool_name\":\"$FWB\",\"tool_input\":{\"branch\":\"feat/y\"}}" "missing content key asks"
|
||||
check ask "$(fwb feat/y ' ')" "whitespace-only content asks"
|
||||
check ask "{\"tool_name\":\"$FWB\",\"tool_input\":\"branch=feat/y\"}" "non-object tool_input asks"
|
||||
# Branch names are cleared by shape, not by blocklist: refs/heads/main created a
|
||||
# second ref that wedged every API write to main (gatekeeper side finding).
|
||||
check ask "$(fwb refs/heads/main x)" "refs/ prefixed branch asks"
|
||||
check ask "$(fwb ' main' x)" "branch with whitespace asks"
|
||||
check ask "$(fwb '../main' x)" "branch with .. asks"
|
||||
check NONE "$(fwb release/v1.2_x x)" "ordinary branch name passes"
|
||||
|
||||
# Reversible / read verbs pass through.
|
||||
for v in issue_close issue_create file_read repo_list pr_create; do
|
||||
check NONE "$(tool "mcp__plugin_skills_gitea__$v")" "$v passes through"
|
||||
|
||||
Reference in New Issue
Block a user