{"id":"CVE-2026-78678","aliases":["GHSA-5xxx-qhh7-9287","PYSEC-2026-3788"],"url":"https://o3.security/vulnerability/CVE-2026-78678","summary":"GitPython before 3.1.59 Arbitrary File Read via Repo.blame()","details":"## Summary\n`Repo.blame()` / `Repo.blame_incremental()` guard forwarded revision options against `unsafe_git_revision_options`, but that denylist only contains the file-WRITE options `--output`/`-o`. `git blame` also honors `--contents <file>` and `-S <file>`, which cause the file's lines to be echoed into the blame result — an arbitrary file READ. Neither option is in the denylist, so a caller-influenced revision value of `--contents=<path>` passes the guard and leaks file contents. This is a distinct sink-option and impact class (READ) from GHSA-956x-8gvw-wg5v (which addressed the blame `--output` WRITE), directly analogous to GHSA-539m-9xh6-q6rr (archive READ gap accepted separately from the archive write/exec advisory).\n\n## Root Cause\n`unsafe_git_revision_options = [\"--output\",\"-o\"]` (`git/repo/base.py:188`). The `rev` string is passed to `_option_candidates([rev], kwargs)` and placed BEFORE the `--` separator (base.py:841). The canonical name of `--contents=...` is `contents`, which is not on the denylist, so no `UnsafeOptionError` is raised. The trailing `--` protects only the pathspec, not the option before the revision.\n\n## Impact\nArbitrary local file read at the privileges of the host process; the file's line contents appear in the blame result returned to the caller. Pure VALUE control (the caller forwards a user-influenced revision string). Default `allow_unsafe_options=False`.\n\n## Proof of Concept\n```python\nresult = repo.blame(\"--contents=/etc/passwd\", \"a.txt\")\n# result rows carry the victim file's line text\n```\n\n## Attack Chain\n1. Entry: app calls `repo.blame(rev, file)` with attacker `rev=\"--contents=/etc/passwd\"` (or kwarg `contents=\"/etc/passwd\"`, or `-S`).\n2. Check: `Git.check_unsafe_options(_option_candidates([rev,...], kwargs), unsafe_git_revision_options)` @ base.py:841. Guard: denylist = `[\"--output\",\"-o\"]` only. Bypass proof: canonical name `contents` ∉ denylist → no error.\n3. Sink: `self.git.blame(rev, \"--\", file, p=True, ...)`. argv (observed): `['git','blame','-p','--contents=<secret>','HEAD','--','a.txt']`.\n4. Impact: blame result rows carry the victim file's line text.\n\n## Bypass Evidence\nIndependently reproduced (independent test harness, default `allow_unsafe_options=False`): `blame('--contents=<secret>','a.txt')` → guard PASSED; result rows = `['GATE_SECRET_LINE_A','GATE_SECRET_LINE_B']`. Control: `blame('--output=…')` still BLOCKED (guard active on this path). `-S` kwarg argv also reaches git unguarded.\n\n## Affected Versions\n`GitPython <= 3.1.58` (denylist present verbatim on the latest release tag).\n\n## Suggested Fix\nPrefer an allowlist of blame options; at minimum add `--contents`/`-S` (and any other path-taking blame options) to `unsafe_git_revision_options`, and make the membership rule \"the option takes a filesystem path\" rather than \"the option writes output\".\n\n---\nReported by **zx (Jace)** — GitHub: @manus-use","published":"2026-08-25T01:30:35.582Z","modified":"2026-09-25T18:26:56.147391449Z","cvss":null,"epss":{"score":0.0024,"percentile":0.15333,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"PyPI","name":"gitpython","fixedVersion":"3.1.59"}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/78xxx/CVE-2026-78678.json"},{"type":"ADVISORY","url":"https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-5xxx-qhh7-9287"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-78678"},{"type":"ADVISORY","url":"https://www.vulncheck.com/advisories/gitpython-before-arbitrary-file-read-via-repo-blame"},{"type":"PACKAGE","url":"https://github.com/gitpython-developers/GitPython"},{"type":"WEB","url":"https://github.com/pypa/advisory-database/tree/main/vulns/gitpython/PYSEC-2026-3788.yaml"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-09-25T18:26:56.147391449Z"}}