Skip to main content
CVE-2026-73620VulnerabilityPublished Updated

IndexFile.checkout() · GitPython: Unguarded git option forwarding

Affects cf2tfSource GCVE Database

0
Authoritative record:CVE-2026-73620· High 7.2

Not known to be exploited · CVSS 7.2 High : upgrade to 0.9.2_7 soon.

Data as of 26 Oct 2026, 17:16 UTC

CVSS 4.0 base score
7.2High
How severe the flaw is on the 0–10 CVSS scale.
EPSS
0.5% chance of exploitation in 30 days – higher than 47% of CVEsExploit Prediction Scoring System: the likelihood attackers will use this flaw soon.
Patch
Available – fixed in 0.9.2_7
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 13 Aug 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponised: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.

What to do

Upgrade cf2tf to 0.9.2_7.

Affected software

ProductAffectedFixed in
Homebrewcf2tfpkg:brew/cf2tfOther threatsAffected
  • >=0.6.1 <0.9.2_7
Fixed in
  • 0.9.2_7

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

**Target:** gitpython-developers/GitPython **Tested:** HEAD `07e80555` (2026-07-25), latest release 3.1.55, `git version 2.50.1` **Reported instances:** 2 exploitable, from a sweep of 14 unguarded call sites ## Summary GitPython blocks dangerous git options through `Git.check_unsafe_options()`, gated per method by an `allow_unsafe_options` parameter. That guard is applied **per call site**, so any API that forwards `**kwargs` into a git command without calling it passes caller-controlled options straight to git. A mechanical sweep of every method that forwards `**kwargs` into a `.git.<command>(...)` call found **14 sites with no guard**. Two reach a git option that takes a filesystem path: | # | Call site | git option | Impact | |---|---|---|---| | 1 | `IndexFile.checkout()` → `git checkout-index` | `--prefix=<path>` | arbitrary file **overwrite** with repository-controlled content | | 2 | `TagReference.create()` → `git tag` | `-F <file>` / `--file=<file>` | arbitrary file **read**, returned in-band | This is the same defect class already fixed in `Commit.count()` (GHSA-p538-c434-8v24), `Repo.archive()` and `Git.ls_remote()` (GHSA-956x-8gvw-wg5v). Both instances below are still present at HEAD. --- ## Instance 1 — `IndexFile.checkout()`: arbitrary file overwrite `git/index/base.py:1210` accepts `**kwargs` and forwards them with no guard: ```python def checkout(self, paths=None, force=False, fprogress=lambda *args: None, **kwargs): ... proc = self.repo.git.checkout_index(*args, **kwargs) # line 1331 ... proc = self.repo.git.checkout_index(args, **kwargs) # line 1349 ``` There is no `allow_unsafe_options` parameter and no `check_unsafe_options()` call in the method. `git checkout-index` accepts `--prefix=<string>`, prepended to every output path. It is not confined to the working tree, so an absolute prefix writes tracked file contents anywhere the process can write, and `-f` overwrites what is already there. ### Reproduction ```python from git import Repo Repo("/path/to/repo").index.checkout(prefix="/tmp/target_dir/", a=True, f=True) ``` Observed (`poc/poc_checkout_index.py`) — no exception raised, files land outside the repository: ``` [ALLOWED] no UnsafeOptionError raised files written outside the repo: ['f.txt'] f.txt: 'hi\n' ``` Overwrite of a pre-existing file (`poc/poc_ci_overwrite.py`) — the victim file held `ORIGINAL-DO-NOT-CLOBBER\n` before the call: ``` [ALLOWED] no exception victim content now: 'hi\n' OVERWRITTEN: True ``` ### Why this rates High Both halves of the write are attacker-influenced: - **Destination** — the `prefix` kwarg. - **Content** — the bytes written are repository blobs, so anyone who can land a file in the repository (a pull-request branch, a mirrored or untrusted repository, an agent-cloned repository) controls exactly what is written. Commit a file named `authorized_keys`, `.bashrc`, `config` or `post-checkout`, choose the matching prefix (`~/.ssh/`, `~/`, `.git/hooks/`), and the write becomes code execution as the service account. For comparison within this project: GHSA-fjr4-x663-mwxc (arbitrary file overwrite via `git diff --output`) is rated High, and GHSA-p538-c434-8v24 (arbitrary file *truncation* via `git rev-list --output`) is rated Medium. `--prefix` supplies full content control, so it sits at or above the former. --- ## Instance 2 — `TagReference.create()`: arbitrary file read `git/refs/tag.py:88` forwards `**kwargs` into `git tag` with no guard, and the signature advertises the passthrough: ```python def create(cls, repo, path, reference="HEAD", logmsg=None, force=False, **kwargs): """... :param kwargs: Additional keyword arguments to be passed to :manpage:`git-tag(1)`. """ ``` `git tag` accepts `-F <file>` / `--file=<file>`, which reads the tag message from an arbitrary path. The annotated tag object stores that content and GitPython returns it to the caller via `TagReference.tag.message`, so the file contents come back in-band. ### Reproduction ```python from git import Repo from git.refs.tag import TagReference t = TagReference.create(Repo("/path/to/repo"), "x", force=True, a=True, F="/etc/passwd") print(t.tag.message) ``` Observed (`poc/poc_tag_F.py`), reading a canary file outside the repository: ``` [ALLOWED] no UnsafeOptionError raised >>> tag message recovered from arbitrary path: 'TAG-READ-CANARY-98765\nsecond-line-secret' ``` Impact is a read at the privileges of the process. I am not claiming code execution for this instance. The signing options (`-s`, `-u`/`--local-user`) do invoke gpg from the same unguarded kwargs, but I did not develop that into command execution and make no claim about it. --- ## Sweep results — the other 12 sites Reported so the fix can be scoped once rather than per report. `poc/sweep.py` reproduces this list. | Call site | git command | Assessment | |---|---|---| | `IndexFile.from_tree()` | `read-tree` | `--index-output=<path>` looked reachable but i

References

Community reviews

Loading reviews…