{"id":"GHSA-fjr4-x663-mwxc","summary":"GitPython: Arbitrary file overwrite via git diff --output argument injection in Diffable.diff (key- and value-controlled)","details":"## Summary\n`Diffable.diff()` forwards `**kwargs` straight into `diff`/`diff_tree` with **no** `check_unsafe_options` guard. `Diffable` is mixed into `Commit`, `Tree`, `IndexFile`, and `Submodule`, giving a broad surface. `git diff --output=\u003cpath\u003e` writes real patch content to an attacker-chosen path, enabling arbitrary file overwrite.\n\n## Root Cause\n`diff.py:188-283` builds and runs the diff command with no `check_unsafe_options` anywhere in the method (grep-confirmed). Additionally `diff.py:265` does `args.insert(0, other)`, placing the caller-supplied `other` ref BEFORE the `--` separator, so a value of `--output=/path` is parsed by git as an option — a value-only control path requiring no kwarg key.\n\n## Impact\nOverwrite/corrupt any file at process privilege with attacker-chosen path (e.g. `~/.ssh/authorized_keys`, configs, lockfiles). Content is real diff/patch bytes (attacker-influenced). Per the skill's rule, controlling WHICH file is overwritten = I:H regardless of content constraints.\n\n## Proof of Concept\n```python\n# Key-control:\ncommit.diff(other_commit, output='/home/app/.ssh/authorized_keys')   # victim overwritten with diff (105 bytes verified)\n# Value-control (attacker controls only the ref string):\ncommit.diff(other='--output=/home/app/.ssh/authorized_keys')          # 14-byte victim -\u003e 146 bytes of diff-tree output\n```\n\n## Attack Chain\n1. Entry (value-control): `commit.diff(other=\u003cuser ref\u003e)` with `other = \"--output=/home/app/.ssh/authorized_keys\"`. Guard: none in `Diffable.diff`. Bypass proof: no `check_unsafe_options` in the method body (grep); `other` inserted pre-`--` at diff.py:265.\n2. Sink: `git diff-tree \u003csha\u003e --output=/home/app/.ssh/authorized_keys -r ...` -\u003e git opens+truncates the target then writes diff content. Impact: overwrite/corrupt any file at process privilege (attacker chooses the path). Verified argv and victim overwrite live.\n\n## Bypass Evidence\nLive-verified on HEAD (tag 3.1.53): both key-control (`output=`) and value-control (`other='--output=...'`) overwrote a victim file with real diff-tree content; argv confirmed `['git','diff-tree','\u003csha\u003e','--output=/victim','-r',...]`. This is the same value-control model GHSA-956x deemed fix-worthy for `iter_commits(rev='--output=')` — but `diff` is a distinct, unguarded sink NOT touched by that fix.\n\n## Affected Versions\n`\u003c= 3.1.53`\n\n## Suggested Fix\nAdd `check_unsafe_options` to `Diffable.diff` (mirroring `iter_commits`/`archive`), and/or place `--end-of-options` before the `other` ref so it cannot be parsed as an option.\n\n---\nReported by **zx (Jace)** — GitHub: @manus-use","aliases":["CVE-2026-73624","PYSEC-2026-3995"],"modified":"2026-09-29T09:10:33.783639162Z","published":"2026-07-24T16:41:20Z","database_specific":{"cwe_ids":["CWE-88"],"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-07-24T16:41:20Z","nvd_published_at":null},"references":[{"type":"WEB","url":"https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-fjr4-x663-mwxc"},{"type":"WEB","url":"https://github.com/gitpython-developers/GitPython/pull/2180"},{"type":"WEB","url":"https://github.com/gitpython-developers/GitPython/commit/1d51b891d7f236044a6aa17498ec682b63dad6e6"},{"type":"PACKAGE","url":"https://github.com/gitpython-developers/GitPython"},{"type":"WEB","url":"https://github.com/gitpython-developers/GitPython/releases/tag/3.1.54"}],"affected":[{"package":{"name":"gitpython","ecosystem":"PyPI","purl":"pkg:pypi/gitpython"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.1.54"}]}],"versions":["0.1.7","0.2.0-beta1","0.3.0-beta1","0.3.0-beta2","0.3.1-beta2","0.3.2","0.3.2.1","0.3.2.RC1","0.3.3","0.3.4","0.3.5","0.3.6","0.3.7","1.0.0","1.0.1","1.0.2","2.0.0","2.0.1","2.0.2","2.0.3","2.0.4","2.0.5","2.0.6","2.0.7","2.0.8","2.0.9","2.0.9.dev0","2.0.9.dev1","2.1.0","2.1.1","2.1.10","2.1.11","2.1.12","2.1.13","2.1.14","2.1.15","2.1.2","2.1.3","2.1.4","2.1.5","2.1.6","2.1.7","2.1.8","2.1.9","3.0.0","3.0.1","3.0.2","3.0.3","3.0.4","3.0.5","3.0.6","3.0.7","3.0.8","3.0.9","3.1.0","3.1.1","3.1.10","3.1.11","3.1.12","3.1.13","3.1.14","3.1.15","3.1.16","3.1.17","3.1.18","3.1.19","3.1.2","3.1.20","3.1.22","3.1.23","3.1.24","3.1.25","3.1.26","3.1.27","3.1.28","3.1.29","3.1.3","3.1.30","3.1.31","3.1.32","3.1.33","3.1.34","3.1.35","3.1.36","3.1.37","3.1.38","3.1.4","3.1.40","3.1.41","3.1.42","3.1.43","3.1.44","3.1.45","3.1.46","3.1.47","3.1.48","3.1.49","3.1.5","3.1.50","3.1.51","3.1.52","3.1.53","3.1.6","3.1.7","3.1.8","3.1.9"],"database_specific":{"last_known_affected_version_range":"\u003c= 3.1.53","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-fjr4-x663-mwxc/GHSA-fjr4-x663-mwxc.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H"}]}