Skip to content

fix(git): run git through argv instead of a shell string - #10

Merged
fiddur merged 1 commit into
developfrom
argv-git
Aug 21, 2026
Merged

fix(git): run git through argv instead of a shell string#10
fiddur merged 1 commit into
developfrom
argv-git

Conversation

@fiddur

@fiddur fiddur commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Audit finding P1-1. exec/execLarge/execWithStdin hand a string to execSync, which runs
/bin/sh -c, and every caller built that string by interpolating a ref, a path or a search term.
All of them are reachable unauthenticated over HTTP:

Request Reached the shell as
GET /api/diff?ref=… git merge-base <ref> HEAD
GET /api/file/<path>?ref=… git show <ref>:<path> — both halves
GET /api/commits?search=… git log --grep=<search>
POST /api/revert-file rm "<filePath>"

Double quotes were no protection: "$(…)" expands.

There is also a trigger that needs no request at all. The diff parser feeds every changed file name
back into git show <ref>:<path> for its line count, so opening a diff whose pull request renames
a file to evil$(touch PWNED).txt executed it
. git permits that file name; the regression test
builds a repository containing exactly it.

  • everything in @diffity/git now goes through argv-form git(), gitLines(), gitWithStdin()
  • revertFile uses git clean --force -- for untracked files instead of rm, so git itself refuses paths outside the repository — no containment check to get wrong
  • getRecentCommits drops the literal quotes it wrapped --format in, which were being parsed into the first and last field

Tests cover the hostile file name through getFileContent, getFileLineCount, getDiff,
getDiffFiles and getDiffStat, a $(…) ref through getMergeBase/normalizeRef, and a $(…)
search term through getRecentCommits — each asserting no side effect happened. Note that git
rejects branch names containing parentheses, so the ref vector is the API parameter, not a branch.

Remaining from the same finding, for a follow-up: packages/github still interpolates owner/repo
into gh api … strings (audit P2-1; the delivery path there is a submodule's .gitmodules URL).

Stacked on #9.

🤖 Generated with Claude Code

https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs

exec/execLarge/execWithStdin pass a string to execSync, which runs /bin/sh -c, and every
caller built that string by interpolating a ref, a path or a search term. All of them are
reachable unauthenticated over HTTP: /api/diff?ref=, /api/file/<path>?ref=,
/api/commits?search=, /api/revert-file. Quoting did not help - "$(…)" expands too.

There is also a no-network trigger: the diff parser feeds every changed file name back into
`git show <ref>:<path>` for its line count, so opening a diff whose PR renames a file to
`evil$(touch PWNED).txt` was enough to execute.

Everything in the package now goes through argv-form git(), gitLines() and gitWithStdin().
revertFile uses `git clean --force --` for untracked files rather than `rm`, so git refuses
paths outside the repository. getRecentCommits also drops the literal quotes it wrapped
--format in, which were being parsed as part of the first and last field.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
@fiddur
fiddur changed the base branch from pin-pr-ref to develop August 21, 2026 13:34
@fiddur
fiddur marked this pull request as ready for review August 21, 2026 13:34
@fiddur
fiddur merged commit 1d14528 into develop Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant