Skip to content

Use --end-of-options before user-controlled refs in git invocations#290

Open
lohengrin332 wants to merge 1 commit intoopenai:mainfrom
lohengrin332:harden-git-end-of-options
Open

Use --end-of-options before user-controlled refs in git invocations#290
lohengrin332 wants to merge 1 commit intoopenai:mainfrom
lohengrin332:harden-git-end-of-options

Conversation

@lohengrin332
Copy link
Copy Markdown

This is a defense-in-depth hardening PR, not a CVE. There is no remotely exploitable bug here in normal use of the plugin.

The behavior being changed: git.mjs passes the user-supplied --base <ref> value (and values derived from it) into git argv positions without --end-of-options. None of the affected git subcommands have known option-injection paths to arbitrary command execution, but the boundary is easy to harden.

Why it's worth tightening: under prompt-injection-adjacent threat models, a hostile --base "--output=/tmp/x" argument reaches git as a positional. The set of dangerous git options the current subcommands accept may grow over time (or may already include something not yet audited). --end-of-options is the documented git idiom for "treat the next argument as a value regardless of leading dashes" and has been supported since 2.24 (November 2019).

A note on -- vs --end-of-options: -- is a path-spec separator in git log <range> and git diff <range>, which would silently change the command's semantics. --end-of-options is the correct construct.

Changes

  • lib/git.mjs: insert --end-of-options before user-input positionals in merge-base, show-ref, and downstream diff/log commands that take ranges derived from user input. Also adds a _gitCheckedImpl injection point to buildBranchComparison (threaded from collectReviewContext) to support structural testing.
  • tests/git.test.mjs: three new tests:
  • Behavioral (3a): hostile --base value of the form --output=<sentinel> errors out as a bad ref and fails to create the sentinel file.
  • Behavioral (3c): refs with embedded dashes (feature/has-dashes-in-name) still work, guarding against any future regex-based "is this a flag?" check.
  • Structural (3d): spy on the injected git runner and assert --end-of-options appears before baseRef in the merge-base argv.

Minimum git version: 2.24 (Nov 2019), which introduced --end-of-options. CI runs ubuntu-latest so this is well below the floor in practice. Happy to add an explicit note in package.json engines or the README if you'd like.

🤖 Generated with Claude Code

User-supplied --base values flow through resolveReviewTarget and
buildBranchComparison into git invocations as positional arguments.
A flag-shaped value like `--output=/tmp/x` could in principle be
parsed by git as an option rather than a ref. The current set of
git subcommands the plugin uses (merge-base, show-ref, diff, log)
do not have known option-injection paths to arbitrary command
execution, but defending the input boundary is cheaper than
relying on that staying true.

Insert --end-of-options before any user-input-derived positional.
This is the documented git idiom (since 2.24, Nov 2019) for "treat
the next argument as a value, not a flag." Note: -- is the wrong
construct here because in `git log <range>` and `git diff <range>`,
-- is a path-spec separator that would silently change semantics.

Tested with a hostile --base value of the form --output=<sentinel>;
the call now errors as a bad ref instead of either succeeding or
creating the sentinel file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lohengrin332 lohengrin332 requested a review from a team May 2, 2026 21:03
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