# GitPython: Arbitrary file overwrite via git diff --output argument injection in Diffable.diff (key- and value-controlled)

GitPython's Diffable.diff passes kwargs and the `other` ref unguarded to git diff, letting `--output=` overwrite arbitrary files at process privilege.

- Payout: Research
- Severity: high
- Program: GitPython
- Found by: manus-use
- Disclosed: 2026-07-24
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-fjr4-x663-mwxc
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-fjr4-x663-mwxc
- Tags: area:oss domain:oss impact:integrity kind:research platform:ghsa sev:high tech:python technique:incomplete-fix technique:source-review vuln:command-injection

**Target:** Python library wrapping git diff/diff-tree subprocess calls
**Root cause:** Diffable.diff() forwards **kwargs into the git diff command with no check_unsafe_options guard, and inserts the caller-supplied `other` ref before the `--` separator so it is parsed as an option.
**Steps:**
1. Identify a sink where user input reaches `commit.diff(other=...)` or kwargs.
2. Key-control: call `commit.diff(other_commit, output='/home/app/.ssh/authorized_keys')`.
3. Value-control: call `commit.diff(other='--output=/home/app/.ssh/authorized_keys')`.
4. Resulting argv: `['git','diff-tree','<sha>','--output=/victim','-r',...]`; git truncates and writes diff bytes to the target path.
**How found:** Noted Diffable.diff lacked check_unsafe_options (grep-confirmed) unlike iter_commits/archive, then spotted diff.py:265 inserting `other` before `--`, giving a value-only control path; verified live on tag 3.1.53.
**Exploitation:** Attacker who controls a ref string or diff kwargs causes git to open and truncate an arbitrary path at process privilege, writing real diff/patch bytes (attacker-influenced content) over files like ~/.ssh/authorized_keys or configs.
**Insight:** When a library patches one subprocess sink (iter_commits) for option injection, enumerate every other method that builds git argv from caller refs/kwargs — mixed-in modules like Diffable widen the surface, and `args.insert(0, other)` before `--` turns a plain ref value into an option.

## Proof of concept (copied exactly from the original report)

**PoC: key- and value-controlled diff output overwrite**

```
# Key-control:
commit.diff(other_commit, output='/home/app/.ssh/authorized_keys')   # victim overwritten with diff (105 bytes verified)
# Value-control (attacker controls only the ref string):
commit.diff(other='--output=/home/app/.ssh/authorized_keys')          # 14-byte victim -> 146 bytes of diff-tree output
```

