GitPython Argument Injection via Diffable.diff Enables Arbitrary File Overwrite
GitPython's diff method passes user-controlled arguments directly to git with no safety checks, letting an attacker overwrite any file the process can reach by injecting a --output option.

The problem
The Diffable.diff() method, mixed into Commit, Tree, IndexFile, and Submodule, forwards all **kwargs straight into the git subprocess with no call to check_unsafe_options.
The other ref is inserted into the argv list before the -- separator (diff.py:265). That means passing a string like --output=/path as the other value causes git to parse it as a real option, opening and truncating the target file and writing diff content into it.
An attacker who controls either a kwarg key or the other ref string can overwrite any file accessible to the process: SSH authorized_keys, config files, lock files, etc.
Proof of concept
A working proof-of-concept for this issue in GitPython, with the exact payload below.
# Key-control: caller controls a kwarg name
commit.diff(other_commit, output='/home/app/.ssh/authorized_keys')
# => git diff <sha1> <sha2> --output=/home/app/.ssh/authorized_keys --abbrev=40 ...
# Value-control: caller controls only the ref string (e.g. user-supplied branch name)
commit.diff(other='--output=/home/app/.ssh/authorized_keys')
# => git diff-tree <sha> --output=/home/app/.ssh/authorized_keys -r ...
# Both verified to truncate and overwrite the target file with real diff-tree bytes.Two distinct injection paths exist. In the key-control path, any kwarg whose name is an unsafe git option (e.g. output) is forwarded verbatim because check_unsafe_options is never called in Diffable.diff. In the value-control path, diff.py:265 does args.insert(0, other), placing the caller-supplied string before --, so git parses it as an option rather than a ref.
The git diff --output=<file> option tells git to open, truncate, and write the patch to that path. Content is real diff bytes, attacker-influenced by the repos being compared. CWE-88 (Argument Injection) applies directly.
The patch (commit 1d51b891, PR #2180) adds check_unsafe_options validation to Diffable.diff and inserts --end-of-options before the other ref, so option-like strings are treated as literal refs, mirroring the existing guard in iter_commits and archive.
The fix
Upgrade GitPython to 3.1.54. The fix in commit 1d51b891 adds check_unsafe_options to Diffable.diff and places --end-of-options before the other ref argument so git cannot interpret user-supplied strings as options.
Reported by zx (Jace).
Related research
- high · 8.8GitPython: OS Command Injection via Single-Character Kwarg Value Token Smuggling
- high · 8.4GitPython Command Injection and Arbitrary File Overwrite via Unguarded Git Options
- high · 7.5GitPython: OS Command Injection via --template in clone_from
- highGitPython unsafe clone option gate bypass via joined short options