CVE-2026-78678: GitPython before 3.1.59 Arbitrary File Read via Repo.blame()

Published Aug 25, 2026
·
Updated

Summary Repo.blame() / Repo.blameincremental() guard forwarded revision options against unsafegitrevisionoptions, 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 unsafegitrevisionoptions = ["--output","-o"] (git/repo/base.py:188). The rev string is passed to optioncandidates([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 allowunsafeoptions=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.checkunsafeoptions(optioncandidates([rev,...], kwargs), unsafegitrevisionoptions) @ 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=<secret>','HEAD','--','a.txt']. 4. Impact: blame result rows carry the victim file's line text.

Bypass Evidence Independently reproduced (independent test harness, default allowunsafeoptions=False): blame('--contents=<secret>','a.txt') → guard PASSED; result rows = ['GATESECRETLINEA','GATESECRETLINEB']. 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 unsafegitrevisionoptions, and make the membership rule "the option takes a filesystem path" rather than "the option writes output".

--- Reported by zx (Jace) — GitHub: @manus-use

Other sources

GitPython versions before 3.1.59 contain an incomplete denylist in the unsafegitrevisionoptions 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.

— MITRE

Affected Software

3 affected componentsFixes available
GitPython GitPython<3.1.59
Gitpython Project Gitpython Python<3.1.59
pip/GitPython<=3.1.58
3.1.59

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/GitPython to a version that resolves this vulnerability.

    Fixed in 3.1.59
  2. Upgrade

    Upgrade GitPython to a version that resolves this vulnerability.

    Fixed in 3.1.59
  3. Configuration

    Modify GitPython's `unsafe_git_revision_options` denylist used by `Git.check_unsafe_options(_option_candidates(...))` (base.py:841) so that `--contents` (canonical `contents`) and `-S` are treated as unsafe (path-taking) options; the current denylist only includes `--output` and `-o`.

    GitPython Repo.blame() / blame_incremental() unsafe_git_revision_options guard unsafe_git_revision_options = Include --contents and -S (and any other path-taking blame options); update membership rule to match options that take a filesystem path rather than options that write output
  4. Compensating control

    If the application passes attacker-influenced revision strings into `repo.blame(rev, file)` (e.g., `rev="--contents=/etc/passwd"` or `contents="/etc/passwd"`), ensure only a safe, fixed revision format is accepted (no leading `--`/option-style revision strings).

Event History

Aug 25, 2026
CVE Published
via MITRE·01:30 AM
Data Sourced
via MITRE·01:30 AM
DescriptionSeverityWeakness
Data Sourced
via NVD·02:16 AM
DescriptionSeverityWeaknessAffected Software
Sep 8, 2026
Advisory Published
via GitHub·06:41 PM
Data Sourced
via GitHub·06:41 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

What application behavior creates exposure to this issue?

An application is exposed when it passes attacker-controlled revision values to GitPython's Repo.blame() method. The vulnerable guard can be bypassed with revision options including --contents or -S.

2

What level of access does an attacker need?

The CVSS vector indicates low privileges are required. The attacker must be able to provide a value that is used as the revision argument to Repo.blame().

3

What is the practical impact of successful exploitation?

An attacker can use a value such as --contents=/etc/passwd to cause file contents to be returned through the blame result. The stated impact is disclosure of arbitrary readable files, with no integrity or availability impact indicated.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203