GitHub Advisoryadvisory · PyPI

GHSA-5xxx-qhh7-9287 GitPython: Incomplete unsafe_git_revision_options denylist omits --contents/-S, enabling arbitrary file read via Repo.blame()

MEDIUM Published 2026-09-08 Modified 2026-09-08 Official page ↗
Aliases
CVE-2026-78678
Ecosystem
PyPI
Packages
GitPython
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Weaknesses
CWE-200, CWE-88
Reviewed
2026-09-08

Affected packages

• PyPI GitPython: < 3.1.59 → fixed in 3.1.59

Description

Summary `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 ` and `-S `, 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=` 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). Root Cause `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. Impact Arbitrary 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`. Proof of Concept ```python result = repo.blame("--contents=/etc/passwd", "a.txt") result rows carry the victim file's line text ``` Attack Chain 1. Entry: app calls `repo.blame(rev, file)` with attacker `rev="--contents=/etc/passwd"` (or kwarg `contents="/etc/passwd"`, or `-S`). 2. 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. 3. Sink: `self.git.blame(rev, "--", file, p=True, ...)`. argv (observed): `['git','blame','-p','--contents=','HEAD','--','a.txt']`. 4. Impact: blame result rows carry the victim file's line text. Bypass Evidence Independently reproduced (independent test harness, default `allow_unsafe_options=False`): `blame('--contents=','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. Affected Versions `GitPython <= 3.1.58` (denylist present verbatim on the latest release tag). Suggested Fix Prefer 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". --- Reported by zx (Jace) — GitHub: @manus-use

Related

Alias · CVE (1)

  • CVE-2026-78678 GitPython versions before 3.1.59 contain an incomplete denylist in the unsafe_git_revision_options guard that omits --contents and -S… vulnerability

Weakness · CWE (2)

  • CWE-200 Exposure of Sensitive Information to an Unauthorized Actor weakness
  • CWE-88 Improper Neutralization of Argument Delimiters in a Command weakness

References