cf2tf · GitPython: clone_from()/clone() omit --separate-git-dir from unsafe_git_clone_options, enabling arbitrary
Affects cf2tfSource GCVE Database
Not known to be exploited · CVSS 8.7 High : upgrade to 0.9.2_9 soon.
Data as of 26 Oct 2026, 17:16 UTC
- EPSS
- 0.6% chance of exploitation in 30 days – higher than 50% of CVEsExploit Prediction Scoring System: the likelihood attackers will use this flaw soon.
- Patch
- Available – fixed in 0.9.2_9
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:NExploitation evidence
Disclosed: evidence found.
CVE published 25 Aug 2026
Detectable: no evidence on record.
No public nuclei template indexed
Weaponised: no evidence on record.
No public exploit code indexed in Metasploit or Exploit-DB
Exploited in the wildExploited: no evidence on record.
Not in CISA KEV and no source reports exploitation
Observed by OffSeq MirageSeen by 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_9.
Affected software
| Product | Affected | Fixed in |
|---|---|---|
Homebrewcf2tfpkg:brew/cf2tfOther threats | Affected
| Fixed in
|
Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.
Description
- **CWE:** CWE-73 (External Control of File Name or Path) / CWE-22 (Path Traversal, in the "escapes intended base directory" sense) - **Affected component:** `git/repo/base.py`, `Repo.unsafe_git_clone_options` (class attribute, lines 153-165) and `Repo._clone()` (lines 1477-1520), reached via the public `Repo.clone_from()` (line 1626) and `Repo.clone()` (line 1567) APIs. - **Affected version:** GitPython at HEAD (`9729ed3b948f2bde09f1f188c5311e172212b67e`, 2026-08-05, VERSION `3.1.58`) ## Reachability `Repo.clone_from(url, to_path, **kwargs)` (and `Repo.clone()`) forward arbitrary keyword arguments to the underlying `git clone` invocation. Before forwarding, GitPython builds a candidate option list from the kwargs (`Git._option_candidates`) and checks it against a denylist, `Repo.unsafe_git_clone_options`, via `Git.check_unsafe_options()` — *unless* the caller passes `allow_unsafe_options=True`. This denylist mechanism is exactly the guard that the last ~16 published GHSAs against this repo (2026-07-12 → 2026-08-05) have repeatedly found incomplete or bypassable for other options (`--template`, `--upload-pack`, `--config`, `--exec`, `--output`, `--index-output`, `--pathspec-from-file`, etc.). `git clone` also accepts `--separate-git-dir=<path>`, which redirects the repository's entire `.git` metadata directory to an **arbitrary, caller-controlled filesystem path**, leaving only a gitlink text file (`gitdir: <path>`) at the intended destination. This is the exact same primitive already recognized as unsafe by GitPython's own code: `Repo.unsafe_git_init_options` (line 145-150) blocks `--separate-git-dir` for `Repo.init()`, with the comment *"Redirects the repository metadata to a caller-controlled path"*. The `Repo._clone()`/`clone()`/`clone_from()` docstring (line 1450-1452) is even more explicit: ``` :param allow_unsafe_options: Allow unsafe options to be used, such as ``--template`` and ``--separate-git-dir``. ``` i.e. the maintainers' own documentation states that `allow_unsafe_options=False` (the default) is supposed to block `--separate-git-dir` for clone. But **`Repo.unsafe_git_clone_options` does not contain it**: ```python unsafe_git_clone_options = [ "--upload-pack", "-u", "--config", "-c", "--template", "--bundle-uri", ] ``` So any application that forwards a `separate_git_dir` (or `separate-git-dir`) kwarg into `Repo.clone_from()` / `Repo.clone()` — e.g. a CI/build service, a Git-hosting proxy, or any tool that exposes a subset of clone options to a client, the exact threat model already accepted for the sibling `--template`/`--upload-pack`/`--config` entries in this same list — gets **no protection at all** for `--separate-git-dir`, even with the default `allow_unsafe_options=False`. ## Root cause Parity gap between two sibling denylists that guard the same underlying primitive (arbitrary redirection of git metadata storage): `unsafe_git_init_options` correctly lists `--separate-git-dir`; `unsafe_git_clone_options`, covering the same option on a different git subcommand that also accepts it, does not — despite the function's own docstring claiming otherwise. This is the same "denylist omits an equally-dangerous sibling option" pattern already responsible for `GHSA-539m-9xh6-q6rr` (`archive` denylist missing `--add-file`/`--add-virtual-file`) and `GHSA-6p8h-3wgx-97gf` (`clone` denylist missing `--template`, since fixed). ## Exploit path 1. Attacker-controlled input reaches a `separate_git_dir=...` (or equivalently `"separate-git-dir"`) keyword argument passed into `Repo.clone_from()` / `Repo.clone()` by the host application, with `allow_unsafe_options` left at its default `False`. 2. `Git._option_candidates()` renders this as `--separate-git-dir` and `Git.check_unsafe_options()` checks it against `Repo.unsafe_git_clone_options` — no match, no `UnsafeOptionError` raised. 3. `Git.transform_kwargs()` renders the same kwarg into the real command line as `--separate-git-dir=<attacker path>` and GitPython executes `git clone -v --separate-git-dir=<attacker path> -- <url> <dest>` via `subprocess` (no shell). 4. `git` itself creates the full repository metadata tree (`config`, `description`, `HEAD`, `hooks/`, `index`, `objects/`, `refs/`, `packed-refs`, `logs/`) at the attacker-specified path — which can be **any path outside the intended clone destination** that the process has permission to create — and leaves a gitlink file at the intended destination pointing to it. ## Impact Arbitrary directory/file creation at a path fully controlled by the attacker (bounded only by filesystem permissions of the process running GitPython), matching the impact class of the already-published, High-severity `GHSA-hmq2-w58f-27jc` ("Arbitrary Git Repository Creation Outside the Working Tree", CVSS 8.2). Concretely: - Planting a git repository structure (including a `hooks/` directory) at an attacker-chosen location outside the sandboxed clone destination the calling application in
References
- github.com/gitpython-developers/GitPython/security/advisories/GHSA-8mcc-hrx5-hvxc (opens in a new tab)
- nvd.nist.gov/vuln/detail/CVE-2026-78677 (opens in a new tab)
- github.com/gitpython-developers/GitPython/pull/2210 (opens in a new tab)
- github.com/gitpython-developers/GitPython/commit/b68afff45af0f49e79a3e2d2162018986b37ad5d (opens in a new tab)
- github.com/gitpython-developers/GitPython (opens in a new tab)
- github.com/gitpython-developers/GitPython/releases/tag/3.1.59 (opens in a new tab)
- github.com/pypa/advisory-database/tree/main/vulns/gitpython/PYSEC-2026-3787.yaml (opens in a new tab)
- vulncheck.com/advisories/gitpython-before-path-traversal-via-separate-git-dir (opens in a new tab)