GitPython command wrappers omit unsafe protocol and option checksGHSA-w8jc-g24h-crhw
HIGHFix: gitpython-developers/GitPython#2251GHSA-w8jc-g24h-crhw is a high-severity (CVSS 7.5) CWE-1287 vulnerability in gitpython. A fix is available for gitpython — see the affected versions and patch details below.
Exploitation Status
No confirmed exploitation observed yet
- A successful exploit gives an attacker total control of the affected component, not partial access.
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for GHSA-w8jc-g24h-crhw.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
GHSA-w8jc-g24h-crhw by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.
Where this sits among everything scored
Of 396,257 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.
Real-World Exposure
gitpythonReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
Summary
GitPython has spent 2022-2026 rolling out two reusable guards method-by-method: Git.check_unsafe_protocols(url) (raises UnsafeProtocolError for <transport>:: remote-helper URLs - CVE-2022-24439) and Git.check_unsafe_options(...) (raises UnsafeOptionError for denylisted option spellings - CVE-2023-40267, CVE-2026-42215, CVE-2026-67323). Three wrapper methods that build a git argv from caller-controlled values still have neither:
Git.ls_remote(git/cmd.py:1128-1143) - checks options (:1139-1141) but forwards the URL positional unchecked; noallow_unsafe_protocolsparameter exists. The same URL throughclone_fromraisesUnsafeProtocolError.Repo.merge_base(git/repo/base.py:895-936) - forwards**kwargs/*revtoself.git.merge_basewith no guard; guarded siblingiter_commitssits 12 lines up. Its exception handler additionally swallows every non-128GitCommandErrorinto[], masking the injection.IndexFile.move(git/index/base.py:1092-1168) - forwards**kwargstogit mvtwice (:1147, :1167); guarded siblingremove()is 23 lines above.
Verified unfixed on main @ a54d159 (2026-09-21), post-dating latest release 3.1.62 (2026-09-07) - all three sites re-verified today.
Impact
Site 1 (ls_remote) - RCE conditional on the victim git transport policy. The common 'probe first, then clone' pattern (Git(...).ls_remote(attacker_url)) with an ext::sh -c ... URL executes arbitrary commands when the victim git permits the ext transport: dynamically verified with protocol.ext.allow=always (marker executed). git >= 2.38.1 stock config refuses the transport (fatal: transport 'ext' not allowed, rc 128) - GitPython adds no defense-in-depth and surfaces a bare GitCommandError. Environments on git < 2.38.1 are exposed with stock config - the exact population CVE-2022-24439 was scored against.
Sites 2/3 - latent argument-injection surface: caller kwargs flow 1:1 into the git argv before operands and -- (cmd.py:1850 opt_args + ext_args), and execute_kwargs (incl. shell=True/env - the CVE-2023-40267 Windows mechanism) passes through unguarded. Today git merge-base/git mv expose no exec option, so we claim the missing-guard class (vendor-settled by CVE-2026-67323, which bundled archive/ls_remote/iter_commits/blame in one advisory), not direct RCE.
Dynamic verification (today; GitPython main @ a54d159, git 2.55.0, Linux)
| # | Call | Result |
|---|---|---|
| T1 | repo.git.ls_remote('ext::/tmp/gp_touch.sh'), stock config | git refuses (rc 128); no UnsafeProtocolError from GitPython |
| T1b | same + victim config protocol.ext.allow=always | marker created - arbitrary command executed |
| T2 | repo.git.ls_remote('.', upload_pack=...) | blocked by cmd.py:1141 (options-side fixed - protocol-side is the residual gap) |
| T3 | repo.merge_base('main','side', upload_pack=...) | not blocked; argv git merge-base --upload-pack=... main side; rc 129 silently converted to [] |
| T4 | repo.index.move(['f1','f2'], output=...) | not blocked; option reaches git mv (rc 129) |
Coverage matrix (main @ a54d159)
check_unsafe_protocols: 7 call sites (_clone, archive, set_url, create, fetch, pull, push) - none on ls_remote.
check_unsafe_options: 24 call sites incl. ls_remote, iter_commits, IndexFile.remove - absent on merge_base and move.
Precedents (same repo, same guard family)
- CVE-2022-24439 / GHSA-hcpj-qp55-gfph (critical):
check_unsafe_protocolson the clone path - Site 1 is the sameext::primitive on a sibling API. - CVE-2023-40267 / GHSA-pr76-5cm5-w9cj (critical, 'incomplete fix for CVE-2022-24439'):
check_unsafe_options- unguarded kwargs to argv treated as RCE (shell=Trueon Windows). - CVE-2026-42215 / GHSA-rpm5-65cw-6hj4 (high, fixed 3.1.47): underscored-spelling bypass - rollout still active in 2026.
- CVE-2026-67323 / GHSA-956x-8gvw-wg5v (high, 8.4, fixed 3.1.51): added
check_unsafe_optionstoarchive/ls_remote/iter_commits/blame; its PoC is literallyrepo.git.ls_remote('.', upload_pack=...). Establishes that a wrapper missing the check is itself a vulnerability. This report is the residual gap of that rollout:ls_remotegot options but not protocols;merge_base/movegot neither.
Recommended fix (existing helpers, 1-3 lines per site)
Git.ls_remote:if not allow_unsafe_protocols: self.check_unsafe_protocols(args[0])+ newallow_unsafe_protocols: bool = False(the_clonepattern, base.py:1536-1538).Repo.merge_base:check_unsafe_options(Git._option_candidates(list(rev), kwargs), unsafe_options=self.unsafe_git_revision_options)(theiter_commitspattern); stop masking non-128 statuses.IndexFile.move: theremove()pattern withunsafe_git_pathspec_from_file_options.
One advisory, three affected methods, severity anchored on Site 1 - mirroring CVE-2026-67323's bundling.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | gitpython | all versions | 3.2.0pip install --upgrade 'gitpython==3.2.0' |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for gitpython, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update gitpython to 3.2.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-w8jc-g24h-crhw is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
Frequently Asked Questions
Is GHSA-w8jc-g24h-crhw in your dependencies?
Find it across PyPI, including transitive dependencies.