GHSA-W8JC-G24H-CRHW
Vulnerability from github – Published: 2026-10-27 15:26 – Updated: 2026-10-27 15:26Summary
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": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.1.62"
},
"package": {
"ecosystem": "PyPI",
"name": "GitPython"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.2.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-1287"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-27T15:26:43Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Summary\n\nGitPython has spent 2022-2026 rolling out two reusable guards method-by-method: `Git.check_unsafe_protocols(url)` (raises `UnsafeProtocolError` for `\u003ctransport\u003e::` 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**:\n\n1. **`Git.ls_remote`** (git/cmd.py:1128-1143) - checks options (:1139-1141) but forwards the URL positional unchecked; no `allow_unsafe_protocols` parameter exists. The same URL through `clone_from` raises `UnsafeProtocolError`.\n2. **`Repo.merge_base`** (git/repo/base.py:895-936) - forwards `**kwargs`/`*rev` to `self.git.merge_base` with no guard; guarded sibling `iter_commits` sits 12 lines up. Its exception handler additionally swallows every non-128 `GitCommandError` into `[]`, masking the injection.\n3. **`IndexFile.move`** (git/index/base.py:1092-1168) - forwards `**kwargs` to `git mv` twice (:1147, :1167); guarded sibling `remove()` is 23 lines above.\n\nVerified unfixed on `main @ a54d159` (2026-09-21), post-dating latest release 3.1.62 (2026-09-07) - all three sites re-verified today.\n\n### Impact\n\n**Site 1 (`ls_remote`)** - RCE conditional on the victim git transport policy. The common \u0027probe first, then clone\u0027 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 \u003e= 2.38.1 stock config refuses the transport (`fatal: transport \u0027ext\u0027 not allowed`, rc 128) - GitPython adds no defense-in-depth and surfaces a bare `GitCommandError`. Environments on git \u003c 2.38.1 are exposed with stock config - the exact population CVE-2022-24439 was scored against.\n\n**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.\n\n### Dynamic verification (today; GitPython main @ a54d159, git 2.55.0, Linux)\n\n| # | Call | Result |\n|---|---|---|\n| T1 | `repo.git.ls_remote(\u0027ext::/tmp/gp_touch.sh\u0027)`, stock config | git refuses (rc 128); **no `UnsafeProtocolError` from GitPython** |\n| T1b | same + victim config `protocol.ext.allow=always` | **marker created - arbitrary command executed** |\n| T2 | `repo.git.ls_remote(\u0027.\u0027, upload_pack=...)` | blocked by cmd.py:1141 (options-side fixed - protocol-side is the residual gap) |\n| T3 | `repo.merge_base(\u0027main\u0027,\u0027side\u0027, upload_pack=...)` | **not blocked**; argv `git merge-base --upload-pack=... main side`; rc 129 silently converted to `[]` |\n| T4 | `repo.index.move([\u0027f1\u0027,\u0027f2\u0027], output=...)` | **not blocked**; option reaches `git mv` (rc 129) |\n\n### Coverage matrix (main @ a54d159)\n\n`check_unsafe_protocols`: 7 call sites (`_clone`, `archive`, `set_url`, `create`, `fetch`, `pull`, `push`) - none on `ls_remote`.\n`check_unsafe_options`: 24 call sites incl. `ls_remote`, `iter_commits`, `IndexFile.remove` - absent on `merge_base` and `move`.\n\n### Precedents (same repo, same guard family)\n\n- **CVE-2022-24439 / GHSA-hcpj-qp55-gfph** (critical): `check_unsafe_protocols` on the clone path - Site 1 is the same `ext::` primitive on a sibling API.\n- **CVE-2023-40267 / GHSA-pr76-5cm5-w9cj** (critical, \u0027incomplete fix for CVE-2022-24439\u0027): `check_unsafe_options` - unguarded kwargs to argv treated as RCE (`shell=True` on Windows).\n- **CVE-2026-42215 / GHSA-rpm5-65cw-6hj4** (high, fixed 3.1.47): underscored-spelling bypass - rollout still active in 2026.\n- **CVE-2026-67323 / GHSA-956x-8gvw-wg5v** (high, 8.4, fixed 3.1.51): added `check_unsafe_options` to `archive`/`ls_remote`/`iter_commits`/`blame`; its PoC is literally `repo.git.ls_remote(\u0027.\u0027, upload_pack=...)`. Establishes that a wrapper missing the check is itself a vulnerability. This report is the residual gap of that rollout: `ls_remote` got options but not protocols; `merge_base`/`move` got neither.\n\n### Recommended fix (existing helpers, 1-3 lines per site)\n\n1. `Git.ls_remote`: `if not allow_unsafe_protocols: self.check_unsafe_protocols(args[0])` + new `allow_unsafe_protocols: bool = False` (the `_clone` pattern, base.py:1536-1538).\n2. `Repo.merge_base`: `check_unsafe_options(Git._option_candidates(list(rev), kwargs), unsafe_options=self.unsafe_git_revision_options)` (the `iter_commits` pattern); stop masking non-128 statuses.\n3. `IndexFile.move`: the `remove()` pattern with `unsafe_git_pathspec_from_file_options`.\n\nOne advisory, three affected methods, severity anchored on Site 1 - mirroring CVE-2026-67323\u0027s bundling.",
"id": "GHSA-w8jc-g24h-crhw",
"modified": "2026-10-27T15:26:43Z",
"published": "2026-10-27T15:26:43Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-w8jc-g24h-crhw"
},
{
"type": "WEB",
"url": "https://github.com/gitpython-developers/GitPython/pull/2251"
},
{
"type": "WEB",
"url": "https://github.com/gitpython-developers/GitPython/commit/9ad28cc4c7176d22cd6181d69805058a719dd03b"
},
{
"type": "PACKAGE",
"url": "https://github.com/gitpython-developers/GitPython"
},
{
"type": "WEB",
"url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.2.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "GitPython command wrappers omit unsafe protocol and option checks"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.