fix(tools): reject case-variant arg keys and ref-shaped branch names
parseArgs now refuses, before any request: two keys equal under
strings.EqualFold in any object (exact duplicates included), and a
top-level key that differs from a schema field only in case. Without
this, encoding/json's case-insensitive last-wins match let
{"branch":"feat/x","Branch":"main"} write to main while a client-side
hook read feat/x.
file_write_branch (branch) and tbd_ship (branch and base) validate the
branch name: ^[A-Za-z0-9][A-Za-z0-9._/-]*$ plus no refs/ prefix, HEAD,
"..", "//", "/.", trailing "/" or ".", or ".lock". refs/heads/main
otherwise created refs/heads/refs/heads/main and wedged API writes to
main. tbd_ship's base is the protection-probe key, so a refs/-prefixed
base would also have read as unprotected.
Closes #65
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -160,6 +160,13 @@ func (t *TBDShip) Call(ctx context.Context, raw json.RawMessage) (json.RawMessag
|
||||
if branch == "" {
|
||||
branch = deriveBranch(args.Message, args.Path, args.Content)
|
||||
}
|
||||
// Both names reach a ref: base is also the protection-probe key, so a
|
||||
// `refs/heads/main` base would read as unprotected (#65).
|
||||
for _, b := range []string{base, branch} {
|
||||
if err := ValidateBranchName(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
deleteBranch := true
|
||||
if args.DeleteBranch != nil {
|
||||
deleteBranch = *args.DeleteBranch
|
||||
|
||||
Reference in New Issue
Block a user