Описание
GitPython: Incomplete unsafe_git_revision_options denylist omits --contents/-S, enabling arbitrary file read via Repo.blame()
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 <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).
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
Attack Chain
- Entry: app calls
repo.blame(rev, file)with attackerrev="--contents=/etc/passwd"(or kwargcontents="/etc/passwd", or-S). - Check:
Git.check_unsafe_options(_option_candidates([rev,...], kwargs), unsafe_git_revision_options)@ base.py:841. Guard: denylist =["--output","-o"]only. Bypass proof: canonical namecontents∉ denylist → no error. - Sink:
self.git.blame(rev, "--", file, p=True, ...). argv (observed):['git','blame','-p','--contents=<secret>','HEAD','--','a.txt']. - 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=<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.
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
Ссылки
- https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-5xxx-qhh7-9287
- https://nvd.nist.gov/vuln/detail/CVE-2026-78678
- https://github.com/pypa/advisory-database/tree/main/vulns/gitpython/PYSEC-2026-3788.yaml
- https://www.vulncheck.com/advisories/gitpython-before-arbitrary-file-read-via-repo-blame
Пакеты
GitPython
<= 3.1.58
3.1.59
Связанные уязвимости
GitPython versions before 3.1.59 contain an incomplete denylist in the unsafe_git_revision_options guard that omits --contents and -S options, allowing attackers to read arbitrary files by passing these options to Repo.blame(). Attackers can supply revision values like --contents=/etc/passwd to leak file contents through the blame result returned to the caller.
GitPython versions before 3.1.59 contain an incomplete denylist in the unsafe_git_revision_options guard that omits --contents and -S options, allowing attackers to read arbitrary files by passing these options to Repo.blame(). Attackers can supply revision values like --contents=/etc/passwd to leak file contents through the blame result returned to the caller.
GitPython versions before 3.1.59 contain an incomplete denylist in the unsafe_git_revision_options guard that omits --contents and -S options, allowing attackers to read arbitrary files by passing these options to Repo.blame(). Attackers can supply revision values like --contents=/etc/passwd to leak file contents through the blame result returned to the caller.
GitPython versions before 3.1.59 contain an incomplete denylist in the ...
Уязвимость функций Repo.blame() и Repo.blame_incremental() механизма защиты unsafe_git_revision_options библиотеки Python для взаимодействия с git-репозиториями GitPython, позволяющая нарушителю получить несанкционированный доступ к защищаемой информации