Summary
create_pr stores the PR's source_branch and target_branch from the request body without validating them as git refs. Both are later interpolated into a single git argv element, so a branch name beginning with - is parsed by git as an option rather than a revision. A target_branch of --output=<path> turns the PR diff endpoint into an arbitrary file write with the node process's privileges.
Impact
GET /api/v1/repos/{owner}/{repo}/pulls/{n}/diff builds git diff {target}...{source} as one argument (branch_diff_names / branch_diff in crates/gitlawb-node/src/git/store.rs). With target_branch = --output=/tmp/x, git runs git diff --output=/tmp/x...{source} and writes to that path. The same shape reaches git worktree add _merge_worktree {target} and git merge --no-ff {source} in merge_branch.
Reachability: opening the PR needs read access (create_pr gates on authorize_repo_read), but the diff endpoint takes an optional identity, so on a public repo the trigger is unauthenticated. The withhold check that builds the diff runs before the per-path visibility gate, so the write happens even when the request ends in a denial or a 500.
Confirmed by reproduction against the real handlers: an anonymous GET .../pulls/1/diff on a public repo, after a reader opened a PR with an --output= target, created the attacker-named file on disk.
Fix
Validate branch refs at the create_pr storage boundary (the single point both sink families read from) against git check-ref-format rules, rejecting a leading - as the security-critical case. A -- delimiter is not the fix: {target}...{source} is a revision argument, and -- there reinterprets the revision as a pathspec.
Summary
create_prstores the PR'ssource_branchandtarget_branchfrom the request body without validating them as git refs. Both are later interpolated into a singlegitargv element, so a branch name beginning with-is parsed by git as an option rather than a revision. Atarget_branchof--output=<path>turns the PR diff endpoint into an arbitrary file write with the node process's privileges.Impact
GET /api/v1/repos/{owner}/{repo}/pulls/{n}/diffbuildsgit diff {target}...{source}as one argument (branch_diff_names/branch_diffincrates/gitlawb-node/src/git/store.rs). Withtarget_branch = --output=/tmp/x, git runsgit diff --output=/tmp/x...{source}and writes to that path. The same shape reachesgit worktree add _merge_worktree {target}andgit merge --no-ff {source}inmerge_branch.Reachability: opening the PR needs read access (
create_prgates onauthorize_repo_read), but the diff endpoint takes an optional identity, so on a public repo the trigger is unauthenticated. The withhold check that builds the diff runs before the per-path visibility gate, so the write happens even when the request ends in a denial or a 500.Confirmed by reproduction against the real handlers: an anonymous
GET .../pulls/1/diffon a public repo, after a reader opened a PR with an--output=target, created the attacker-named file on disk.Fix
Validate branch refs at the
create_prstorage boundary (the single point both sink families read from) againstgit check-ref-formatrules, rejecting a leading-as the security-critical case. A--delimiter is not the fix:{target}...{source}is a revision argument, and--there reinterprets the revision as a pathspec.