{"id":"CVE-2026-110858","aliases":["CLSA-2026-1792479054","CLSA-2026-1793612016","DEBIAN-CVE-2026-110858","EUVD-2026-101741","GHSA-23mf-xhv8-69c2","GHSA-f9j4-qggq-h239","GHSA-fx3j-rwgx-fr94","GHSA-m64x-33q8-m5h7","GHSA-mcf4-6385-6rcq","MGASA-2026-0580","UBUNTU-CVE-2026-110858","openSUSE-SU-2026:12391-1"],"status":"published","title":"GitPython 3.1.30 before 3.2.0 Command Injection via Remote.pull() Refspec","description":"GitPython from 3.1.30 before 3.2.0 contains a remote code execution vulnerability in Remote.pull() whose unsafe-option guard inspects only keyword arguments, not the positional refspec. Attackers controlling the refspec can supply --upload-pack=\u003ccommand\u003e to execute OS commands as the calling process over local-path or SSH transports.","severity":{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00465,"percentile":0.4074,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-78","CWE-88"],"published":"2026-10-14T22:52:42Z","modified":"2026-11-07T04:45:03Z","fixes":[{"ecosystem":"PyPI","package":"gitpython","fixed_in":["3.2.0"],"sources":["ghsa","osv"]},{"ecosystem":"TuxCare:PyPI","package":"gitpython","fixed_in":["3.1.31.post18+tuxcare"],"sources":["osv"]},{"ecosystem":"Debian:12","package":"python-git","fixed_in":["3.1.30-1+deb12u3"],"sources":["osv"]},{"ecosystem":"Debian:13","package":"python-git","fixed_in":["3.1.44-1+deb13u1"],"sources":["osv"]},{"ecosystem":"Debian:14","package":"python-git","fixed_in":["3.2.1-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"gitpython","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.1.30"},{"fixed":"3.2.0"}]}]},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"gitpython","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.1.31"},{"fixed":"3.1.31.post15+tuxcare"}]}],"versions":["3.1.31","3.1.31.post1+tuxcare","3.1.31.post10+tuxcare","3.1.31.post11+tuxcare","3.1.31.post12+tuxcare","3.1.31.post13+tuxcare","3.1.31.post14+tuxcare","3.1.31.post2+tuxcare","3.1.31.post3+tuxcare","3.1.31.post4+tuxcare","3.1.31.post5+tuxcare","3.1.31.post6+tuxcare","3.1.31.post7+tuxcare","3.1.31.post8+tuxcare","3.1.31.post9+tuxcare"],"bundled":4},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"gitpython","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.1.31"},{"fixed":"3.1.31.post18+tuxcare"}]}],"versions":["3.1.31","3.1.31.post1+tuxcare","3.1.31.post10+tuxcare","3.1.31.post11+tuxcare","3.1.31.post12+tuxcare","3.1.31.post13+tuxcare","3.1.31.post14+tuxcare","3.1.31.post15+tuxcare","3.1.31.post16+tuxcare","3.1.31.post17+tuxcare","3.1.31.post2+tuxcare","3.1.31.post3+tuxcare","3.1.31.post4+tuxcare","3.1.31.post5+tuxcare","3.1.31.post6+tuxcare","3.1.31.post7+tuxcare","3.1.31.post8+tuxcare","3.1.31.post9+tuxcare"]},{"source":"osv","ecosystem":"Debian:12","package":"python-git","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.1.30-1+deb12u3"}]}],"versions":["3.1.30-1","3.1.30-1+deb12u2"]},{"source":"osv","ecosystem":"Debian:13","package":"python-git","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.1.44-1+deb13u1"}]}],"versions":["3.1.44-1"]},{"source":"osv","ecosystem":"Debian:14","package":"python-git","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.2.1-1"}]}],"versions":["3.1.44-1","3.1.45-1","3.1.46-1","3.1.50-1","3.1.61-1","3.1.62-1","3.1.62-2","3.1.62-3"]},{"source":"osv","ecosystem":"PyPI","package":"gitpython","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.1.30"},{"fixed":"3.2.0"}]}],"versions":["3.1.30","3.1.31","3.1.32","3.1.33","3.1.34","3.1.35","3.1.36","3.1.37","3.1.38","3.1.40","3.1.41","3.1.42","3.1.43","3.1.44","3.1.45","3.1.46","3.1.47","3.1.48","3.1.49","3.1.50","3.1.51","3.1.52","3.1.53","3.1.54","3.1.55","3.1.56","3.1.57","3.1.58","3.1.59","3.1.60","3.1.61","3.1.62"]},{"source":"osv","ecosystem":"Mageia:10","package":"python-gitpython","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.2.1-1.mga10"}]}],"bundled":6},{"source":"osv","ecosystem":"Ubuntu:Pro:14.04:LTS","package":"python-git","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.3.2~RC1-2","0.3.2~RC1-3","0.3.2~RC1-3ubuntu0.1~esm1","0.3.2~RC1-3ubuntu0.1~esm2","0.3.2~RC1-3ubuntu0.1~esm3"]},{"source":"osv","ecosystem":"Ubuntu:Pro:16.04:LTS","package":"python-git","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["1.0.1+git137-gc8b8379-1","1.0.1+git137-gc8b8379-2","1.0.1+git137-gc8b8379-2.1","1.0.1+git137-gc8b8379-2.1ubuntu0.1~esm1","1.0.1+git137-gc8b8379-2.1ubuntu0.1~esm2","1.0.1+git137-gc8b8379-2.1ubuntu0.1~esm4"]},{"source":"osv","ecosystem":"Ubuntu:Pro:18.04:LTS","package":"python-git","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.1.5-1","2.1.6-1","2.1.7-1","2.1.8-1","2.1.8-1ubuntu0.1~esm1","2.1.8-1ubuntu0.1~esm2","2.1.8-1ubuntu0.1~esm4"]},{"source":"osv","ecosystem":"Ubuntu:Pro:20.04:LTS","package":"python-git","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.1.11-1","3.0.4-1","3.0.5-1","3.0.7-1","3.0.7-1ubuntu0.1~esm1","3.0.7-1ubuntu0.1~esm2","3.0.7-1ubuntu0.1~esm4"]},{"source":"osv","ecosystem":"Ubuntu:Pro:22.04:LTS","package":"python-git","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3.1.14-1","3.1.23-1","3.1.24-1","3.1.24-1ubuntu0.1~esm1","3.1.24-1ubuntu0.1~esm2","3.1.24-1ubuntu0.1~esm3"]},{"source":"osv","ecosystem":"Ubuntu:Pro:24.04:LTS","package":"python-git","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3.1.30-1","3.1.37-1","3.1.37-3","3.1.37-3ubuntu0.1~esm2"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"python-git","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3.1.46-1","3.1.50-1","3.1.61-1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:26.04:LTS","package":"python-git","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3.1.44-1","3.1.45-1","3.1.46-1","3.1.46-1ubuntu0.1~esm1"]},{"source":"osv","ecosystem":"openSUSE:Tumbleweed","package":"python-GitPython","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.2.1-1.1"}]}],"bundled":6}],"products":[{"vendor":"gitpython-developers","product":"gitpython"}],"references":[{"url":"https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-f9j4-qggq-h239","tags":["vendor-advisory","advisory","web","report"]},{"url":"https://www.vulncheck.com/advisories/gitpython-3.1.30-before-3.2.0-command-injection-via-remote-pull-refspec","tags":["third-party-advisory","advisory","report"]},{"url":"https://github.com/gitpython-developers/GitPython/pull/2247","tags":["web"]},{"url":"https://github.com/gitpython-developers/GitPython/commit/790bb3164948625ffb043d2a162425d55923fe9a","tags":["web"]},{"url":"https://github.com/gitpython-developers/GitPython/releases/tag/3.2.0","tags":["web"]},{"url":"https://github.com/advisories/GHSA-f9j4-qggq-h239"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-110858","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-mcf4-6385-6rcq"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/110xxx/CVE-2026-110858.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-110858","tags":["advisory"]},{"url":"https://github.com/gitpython-developers/GitPython","tags":["package"]},{"url":"https://advisories.mageia.org/MGASA-2026-0580.html","tags":["advisory"]},{"url":"https://bugs.mageia.org/show_bug.cgi?id=36507","tags":["report"]},{"url":"https://github.com/gitpython-developers/GitPython/releases#release-3.2.1","tags":["web"]},{"url":"https://github.com/gitpython-developers/GitPython/releases#release-3.2.0","tags":["web"]},{"url":"https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/OOS5EDMLYZWKECAUWB5INCTGXLGARLNL/","tags":["web"]},{"url":"https://ubuntu.com/security/CVE-2026-110858","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-110858","tags":["report"]},{"url":"","tags":["advisory"]},{"url":"https://www.suse.com/security/cve/CVE-2026-110855","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-110856","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-110857","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-110858","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-110859","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-114145","tags":["web"]}],"sources":{"cve":[{"id":"CVE-2026-110858","title":"GitPython 3.1.30 before 3.2.0 Command Injection via Remote.pull() Refspec","description":"GitPython from 3.1.30 before 3.2.0 contains a remote code execution vulnerability in Remote.pull() whose unsafe-option guard inspects only keyword arguments, not the positional refspec. Attackers controlling the refspec can supply --upload-pack=\u003ccommand\u003e to execute OS commands as the calling process over local-path or SSH transports.","status":"published","published":"2026-10-14T22:52:42.217Z","modified":"2026-10-16T17:14:41.14Z","cvss":[{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical","assessor":"cna"},{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"cna"}],"cwes":["CWE-88"],"updated_at":"2026-11-02T13:34:26.24368Z"}],"euvd":[{"id":"EUVD-2026-101741","description":"GitPython from 3.1.30 before 3.2.0 contains a remote code execution vulnerability in Remote.pull() whose unsafe-option guard inspects only keyword arguments, not the positional refspec. Attackers controlling the refspec can supply --upload-pack=\u003ccommand\u003e to execute OS commands as the calling process over local-path or SSH transports.","status":"published","published":"2026-10-14T22:52:42Z","modified":"2026-10-16T17:14:41Z","cvss":[{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical"}],"updated_at":"2026-11-02T17:00:04.331134Z"}],"ghsa":[{"id":"GHSA-f9j4-qggq-h239","title":"GitPython Remote.pull() refspec bypasses the --upload-pack unsafe-option guard, enabling arbitrary command execution","description":"## Summary\n\n`Remote.pull()` in GitPython forwards its positional `refspec` argument directly to the `git pull` child process while its unsafe-option guard inspects only keyword arguments. An application that passes attacker-influenced data as the `refspec` (for example a user-supplied branch or refspec parameter in a web or CI service) can supply an option-shaped value such as `--upload-pack=\u003ccommand\u003e`. The value bypasses the `unsafe_git_pull_options` check, is placed after the `--` separator on the `git pull` command line, and is executed as the upload-pack program for local-path or SSH transports. This yields arbitrary command execution with the privileges of the calling process using the documented default arguments (`allow_unsafe_options=False`).\n\n## Root Cause\n\n`Remote.pull()` is documented to reject dangerous options unless `allow_unsafe_options=True`, and `--upload-pack` is explicitly listed as such an option. The guard, however, only screens keyword arguments and never the positional `refspec`. The check builds its candidate list with `Git._option_candidates([], kwargs)`, passing an empty list in place of the positionals, so the `refspec` value is never compared against `unsafe_git_pull_options = [\"--upload-pack\"]`. The normalized `refspec` is then forwarded verbatim to the `git pull` invocation. The leading `--` separator that neutralizes an option-shaped refspec for `git fetch` and `git push` does not protect `git pull`, because `git pull` re-forwards its trailing positionals to an internal `git fetch` without re-inserting a `--` separator, so the positional becomes an option to that internal fetch.\n\n```text\nAffected function: git.remote.Remote.pull\nSource: git/remote.py (guard at the check_unsafe_options call using _option_candidates([], kwargs); sink at the self.repo.git.pull(\"--\", self, refspec, ...) invocation)\n```\n\n## Impact\n\nAn attacker who controls the `refspec` value passed to `Remote.pull()` achieves arbitrary operating-system command execution as the process invoking GitPython, with no additional privileges and no user interaction, on the default code path. Exploitation requires that the pulled remote uses a transport that runs upload-pack on the local side, namely a local filesystem path or SSH; over HTTPS the `--upload-pack` value is inert because the server ignores a client-supplied upload-pack program. This transport requirement narrows the population of vulnerable deployments but does not add work for the attacker once a vulnerable target is reached. Working checkouts commonly use local-path or SSH origins, so the exposed population is substantial.\n\n## Proof of Concept\n\n```text\nEnvironment: GitPython from the affected source tree; system git 2.43.0.\nThe pulled origin uses a local filesystem transport (protocol.file.allow=always for git \u003e= 2.38).\n```\n\nReproduction steps:\n\n1. Create an origin repository reachable over a local-path transport.\n2. In a consumer repository, register that origin and call `Remote.pull()` with an attacker-controlled refspec.\n3. Observe that the injected command executes.\n\n```python\nimport git\nrepo = git.Repo(\"consumer\")                 # has an origin using a local-path or ssh transport\norigin = repo.remote(\"origin\")\n# Attacker-controlled refspec value; default arguments (allow_unsafe_options=False):\norigin.pull(refspec=\"--upload-pack=touch /tmp/pwned; git-upload-pack\")\n```\n\n```text\ncmdline: git pull -v -- origin --upload-pack=touch /tmp/pwned; git-upload-pack\n[attack]  marker created (RCE)      : True\n[isolate] upload_pack passed as KWARG blocked : True   (only the positional refspec escapes)\n[sibling] same value via git fetch  : safe            (pull-specific behavior)\n[control] benign refspec            : safe\n```\n\n## Attack Chain\n\n1. **Exposure:** An application calls `Remote.pull(refspec=\u003cvalue\u003e)` on the default path, where `\u003cvalue\u003e` is derived from attacker-influenced input such as a branch or refspec request parameter.\n2. **Control:** The attacker supplies the string `--upload-pack=\u003ccommand\u003e; git-upload-pack`. The value passes through `Git._unpack_args`, which only flattens and stringifies it, preserving the option shape.\n3. **Path:** The protocol loop calls `Git.check_unsafe_protocols` on the value, which only matches remote-helper transport URLs (a `\u003ctransport\u003e::` form) and does not flag an `--upload-pack=` token.\n4. **Guard:** The unsafe-option guard calls `check_unsafe_options(options=Git._option_candidates([], kwargs), ...)`. Because positionals are passed as an empty list, the positional `refspec` is never inspected against `unsafe_git_pull_options`.\n5. **Primitive:** The value is forwarded to `git pull -v -- \u003cremote\u003e --upload-pack=\u003ccommand\u003e; git-upload-pack`; `git pull` re-forwards the positional to an internal `git fetch` without a `--` separator, so it is honored as the upload-pack program.\n6. **Result:** For a local-path or SSH transport, git executes `\u003ccommand\u003e` as the upload-pack program, giving arbitrary command execution as the calling process user.\n\n## Bypass Evidence\n\nThe relevant guard is `Remote.pull()`'s own `check_unsafe_options` call, present in the current released artifact. It is incomplete: it inspects `Git._option_candidates([], kwargs)`, which only considers keyword arguments, so the positional `refspec` is never checked. This was confirmed empirically: passing the identical value as the `upload_pack=` keyword argument raises `UnsafeOptionError`, while passing it as the positional `refspec` does not, proving that only the positional escapes the guard.\n\nThe `--` separator on the `git pull` command line was tested as a possible mitigation and refuted: with system git 2.43.0, `git pull -- \u003cremote\u003e '--upload-pack=\u003ccmd\u003e'` executed the injected command, while the identical value supplied to `git fetch -- ...` and `git push -- ...` did not execute, because those subcommands honor the `--` terminator whereas `git pull` re-forwards positionals to an internal fetch without one. The `check_unsafe_protocols` transport check was also examined and does not flag the `--upload-pack=` token because it has no `\u003ctransport\u003e::` form. No prior published advisory for this package addresses `Remote.pull`, the fetch or pull refspec positional, or `--upload-pack` injection via a refspec; the closest accepted issue covers the same class (a positional argument escaping a keyword-only guard) in a different function and sink.\n\n## Affected Versions\n\n- `Ecosystem: pip (PyPI)`\n- `Package: GitPython`\n- `Confirmed affected range: \u003e= 3.1.30`\n- `Latest release checked: 3.1.62 (PyPI); source verified against the 3.1.62 release tag`\n- `Fix status: not fixed in any released version or on the development branch`\n\nThe keyword-only pull guard together with the positional `refspec` sink is present in every released tag from 3.1.30 through the latest release 3.1.62, verified by inspecting the shipped `git/remote.py` in each tagged artifact. Version 3.1.30 introduced the `unsafe_git_pull_options` guard; the affected range is stated from that boundary under the incomplete-guard framing. Releases before 3.1.30 forward the `refspec` positionally with no unsafe-option guard and no `--` separator and are also exploitable, but the range is stated conservatively from the guard-introduction boundary. No fixed release exists.\n\n## Suggested Fix\n\nEnforce the unsafe-option invariant on the positional `refspec`, not only on keyword arguments. The guard should build its candidate list from the actual forwarded positionals (for example by passing the normalized refspec list into `Git._option_candidates` alongside `kwargs`) so that an option-shaped `refspec` such as `--upload-pack=...` is rejected with `UnsafeOptionError` unless `allow_unsafe_options=True`. The same correction should be verified for any other reference or refspec forwarding path that relies on the keyword-only guard. As an interim mitigation without a code change, callers should validate or reject refspec values that begin with `-`, avoid passing untrusted data as the `refspec`, and prefer HTTPS remotes where a client-supplied upload-pack program is inert.\n\nReported by zx (GitHub: @manus-pi).","status":"published","published":"2026-10-27T15:26:50Z","modified":"2026-10-27T15:28:29Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-78","CWE-88"],"updated_at":"2026-11-02T14:41:37.912288Z"},{"id":"GHSA-mcf4-6385-6rcq","title":"GitPython from 3.1.30 before 3.2.0 contains a remote code execution vulnerability in Remote.pull(...","description":"GitPython from 3.1.30 before 3.2.0 contains a remote code execution vulnerability in Remote.pull() whose unsafe-option guard inspects only keyword arguments, not the positional refspec. Attackers controlling the refspec can supply --upload-pack=\u003ccommand\u003e to execute OS commands as the calling process over local-path or SSH transports.","status":"published","published":"2026-10-15T00:31:18Z","modified":"2026-10-15T00:31:26Z","cvss":[{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"critical"},{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-88"],"updated_at":"2026-11-02T15:13:29.914076Z"}],"nvd":[{"id":"CVE-2026-110858","description":"GitPython from 3.1.30 before 3.2.0 contains a remote code execution vulnerability in Remote.pull() whose unsafe-option guard inspects only keyword arguments, not the positional refspec. Attackers controlling the refspec can supply --upload-pack=\u003ccommand\u003e to execute OS commands as the calling process over local-path or SSH transports.","status":"published","published":"2026-10-14T23:17:27.043Z","modified":"2026-10-16T18:17:34.013Z","cvss":[{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"critical","assessor":"secondary"},{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"secondary"}],"cwes":["CWE-88"],"updated_at":"2026-11-02T14:12:59.328869Z"}],"osv":[{"id":"CVE-2026-110858","title":"GitPython 3.1.30 before 3.2.0 Command Injection via Remote.pull() Refspec","description":"GitPython from 3.1.30 before 3.2.0 contains a remote code execution vulnerability in Remote.pull() whose unsafe-option guard inspects only keyword arguments, not the positional refspec. Attackers controlling the refspec can supply --upload-pack=\u003ccommand\u003e to execute OS commands as the calling process over local-path or SSH transports.","status":"published","published":"2026-10-14T22:52:42.217Z","modified":"2026-11-03T17:26:57.552566422Z","cvss":[{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical"}],"cwes":["CWE-88"],"updated_at":"2026-11-03T18:00:16.24745Z"},{"id":"DEBIAN-CVE-2026-110858","description":"GitPython from 3.1.30 before 3.2.0 contains a remote code execution vulnerability in Remote.pull() whose unsafe-option guard inspects only keyword arguments, not the positional refspec. Attackers controlling the refspec can supply --upload-pack=\u003ccommand\u003e to execute OS commands as the calling process over local-path or SSH transports.","status":"published","published":"2026-10-14T23:17:27.043Z","modified":"2026-10-31T14:00:07.201071297Z","cvss":[{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"critical"}],"updated_at":"2026-11-02T15:45:49.824149Z"},{"id":"GHSA-f9j4-qggq-h239","title":"GitPython Remote.pull() refspec bypasses the --upload-pack unsafe-option guard, enabling arbitrary command execution","description":"## Summary\n\n`Remote.pull()` in GitPython forwards its positional `refspec` argument directly to the `git pull` child process while its unsafe-option guard inspects only keyword arguments. An application that passes attacker-influenced data as the `refspec` (for example a user-supplied branch or refspec parameter in a web or CI service) can supply an option-shaped value such as `--upload-pack=\u003ccommand\u003e`. The value bypasses the `unsafe_git_pull_options` check, is placed after the `--` separator on the `git pull` command line, and is executed as the upload-pack program for local-path or SSH transports. This yields arbitrary command execution with the privileges of the calling process using the documented default arguments (`allow_unsafe_options=False`).\n\n## Root Cause\n\n`Remote.pull()` is documented to reject dangerous options unless `allow_unsafe_options=True`, and `--upload-pack` is explicitly listed as such an option. The guard, however, only screens keyword arguments and never the positional `refspec`. The check builds its candidate list with `Git._option_candidates([], kwargs)`, passing an empty list in place of the positionals, so the `refspec` value is never compared against `unsafe_git_pull_options = [\"--upload-pack\"]`. The normalized `refspec` is then forwarded verbatim to the `git pull` invocation. The leading `--` separator that neutralizes an option-shaped refspec for `git fetch` and `git push` does not protect `git pull`, because `git pull` re-forwards its trailing positionals to an internal `git fetch` without re-inserting a `--` separator, so the positional becomes an option to that internal fetch.\n\n```text\nAffected function: git.remote.Remote.pull\nSource: git/remote.py (guard at the check_unsafe_options call using _option_candidates([], kwargs); sink at the self.repo.git.pull(\"--\", self, refspec, ...) invocation)\n```\n\n## Impact\n\nAn attacker who controls the `refspec` value passed to `Remote.pull()` achieves arbitrary operating-system command execution as the process invoking GitPython, with no additional privileges and no user interaction, on the default code path. Exploitation requires that the pulled remote uses a transport that runs upload-pack on the local side, namely a local filesystem path or SSH; over HTTPS the `--upload-pack` value is inert because the server ignores a client-supplied upload-pack program. This transport requirement narrows the population of vulnerable deployments but does not add work for the attacker once a vulnerable target is reached. Working checkouts commonly use local-path or SSH origins, so the exposed population is substantial.\n\n## Proof of Concept\n\n```text\nEnvironment: GitPython from the affected source tree; system git 2.43.0.\nThe pulled origin uses a local filesystem transport (protocol.file.allow=always for git \u003e= 2.38).\n```\n\nReproduction steps:\n\n1. Create an origin repository reachable over a local-path transport.\n2. In a consumer repository, register that origin and call `Remote.pull()` with an attacker-controlled refspec.\n3. Observe that the injected command executes.\n\n```python\nimport git\nrepo = git.Repo(\"consumer\")                 # has an origin using a local-path or ssh transport\norigin = repo.remote(\"origin\")\n# Attacker-controlled refspec value; default arguments (allow_unsafe_options=False):\norigin.pull(refspec=\"--upload-pack=touch /tmp/pwned; git-upload-pack\")\n```\n\n```text\ncmdline: git pull -v -- origin --upload-pack=touch /tmp/pwned; git-upload-pack\n[attack]  marker created (RCE)      : True\n[isolate] upload_pack passed as KWARG blocked : True   (only the positional refspec escapes)\n[sibling] same value via git fetch  : safe            (pull-specific behavior)\n[control] benign refspec            : safe\n```\n\n## Attack Chain\n\n1. **Exposure:** An application calls `Remote.pull(refspec=\u003cvalue\u003e)` on the default path, where `\u003cvalue\u003e` is derived from attacker-influenced input such as a branch or refspec request parameter.\n2. **Control:** The attacker supplies the string `--upload-pack=\u003ccommand\u003e; git-upload-pack`. The value passes through `Git._unpack_args`, which only flattens and stringifies it, preserving the option shape.\n3. **Path:** The protocol loop calls `Git.check_unsafe_protocols` on the value, which only matches remote-helper transport URLs (a `\u003ctransport\u003e::` form) and does not flag an `--upload-pack=` token.\n4. **Guard:** The unsafe-option guard calls `check_unsafe_options(options=Git._option_candidates([], kwargs), ...)`. Because positionals are passed as an empty list, the positional `refspec` is never inspected against `unsafe_git_pull_options`.\n5. **Primitive:** The value is forwarded to `git pull -v -- \u003cremote\u003e --upload-pack=\u003ccommand\u003e; git-upload-pack`; `git pull` re-forwards the positional to an internal `git fetch` without a `--` separator, so it is honored as the upload-pack program.\n6. **Result:** For a local-path or SSH transport, git executes `\u003ccommand\u003e` as the upload-pack program, giving arbitrary command execution as the calling process user.\n\n## Bypass Evidence\n\nThe relevant guard is `Remote.pull()`'s own `check_unsafe_options` call, present in the current released artifact. It is incomplete: it inspects `Git._option_candidates([], kwargs)`, which only considers keyword arguments, so the positional `refspec` is never checked. This was confirmed empirically: passing the identical value as the `upload_pack=` keyword argument raises `UnsafeOptionError`, while passing it as the positional `refspec` does not, proving that only the positional escapes the guard.\n\nThe `--` separator on the `git pull` command line was tested as a possible mitigation and refuted: with system git 2.43.0, `git pull -- \u003cremote\u003e '--upload-pack=\u003ccmd\u003e'` executed the injected command, while the identical value supplied to `git fetch -- ...` and `git push -- ...` did not execute, because those subcommands honor the `--` terminator whereas `git pull` re-forwards positionals to an internal fetch without one. The `check_unsafe_protocols` transport check was also examined and does not flag the `--upload-pack=` token because it has no `\u003ctransport\u003e::` form. No prior published advisory for this package addresses `Remote.pull`, the fetch or pull refspec positional, or `--upload-pack` injection via a refspec; the closest accepted issue covers the same class (a positional argument escaping a keyword-only guard) in a different function and sink.\n\n## Affected Versions\n\n- `Ecosystem: pip (PyPI)`\n- `Package: GitPython`\n- `Confirmed affected range: \u003e= 3.1.30`\n- `Latest release checked: 3.1.62 (PyPI); source verified against the 3.1.62 release tag`\n- `Fix status: not fixed in any released version or on the development branch`\n\nThe keyword-only pull guard together with the positional `refspec` sink is present in every released tag from 3.1.30 through the latest release 3.1.62, verified by inspecting the shipped `git/remote.py` in each tagged artifact. Version 3.1.30 introduced the `unsafe_git_pull_options` guard; the affected range is stated from that boundary under the incomplete-guard framing. Releases before 3.1.30 forward the `refspec` positionally with no unsafe-option guard and no `--` separator and are also exploitable, but the range is stated conservatively from the guard-introduction boundary. No fixed release exists.\n\n## Suggested Fix\n\nEnforce the unsafe-option invariant on the positional `refspec`, not only on keyword arguments. The guard should build its candidate list from the actual forwarded positionals (for example by passing the normalized refspec list into `Git._option_candidates` alongside `kwargs`) so that an option-shaped `refspec` such as `--upload-pack=...` is rejected with `UnsafeOptionError` unless `allow_unsafe_options=True`. The same correction should be verified for any other reference or refspec forwarding path that relies on the keyword-only guard. As an interim mitigation without a code change, callers should validate or reject refspec values that begin with `-`, avoid passing untrusted data as the `refspec`, and prefer HTTPS remotes where a client-supplied upload-pack program is inert.\n\nReported by zx (GitHub: @manus-pi).","status":"published","published":"2026-10-27T15:26:50Z","modified":"2026-10-27T15:47:24.258288646Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-78","CWE-88"],"updated_at":"2026-11-02T15:46:46.39441Z"},{"id":"UBUNTU-CVE-2026-110858","description":"GitPython from 3.1.30 before 3.2.0 contains a remote code execution vulnerability in Remote.pull() whose unsafe-option guard inspects only keyword arguments, not the positional refspec. Attackers controlling the refspec can supply --upload-pack=\u003ccommand\u003e to execute OS commands as the calling process over local-path or SSH transports.","status":"published","published":"2026-10-14T23:17:00Z","modified":"2026-10-28T17:50:34Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high"},{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical"}],"updated_at":"2026-11-02T16:55:59.591851Z"},{"id":"CLSA-2026-1792479054","title":"TuxCare security update for gitpython (7 CVEs)","description":"TuxCare rebuilt gitpython to address 7 CVEs. Fixed in 3.1.31.post15+tuxcare.","status":"published","published":"2026-10-20T06:50:54Z","modified":"2026-11-02T14:57:54.310986569Z","updated_at":"2026-11-02T15:43:54.378946Z"},{"id":"CLSA-2026-1793612016","title":"TuxCare security update for gitpython (1 CVE)","description":"TuxCare rebuilt gitpython to address 1 CVE. Fixed in 3.1.31.post18+tuxcare.","status":"published","published":"2026-11-02T10:33:37Z","modified":"2026-11-02T14:45:04.955719433Z","updated_at":"2026-11-02T15:43:55.669514Z"},{"id":"MGASA-2026-0580","title":"Updated python-gitpython package fixes security vulnerabilities","description":"Please see the links for additional information.\n","status":"published","published":"2026-11-07T04:34:18Z","modified":"2026-11-07T04:45:03.36121595Z","updated_at":"2026-11-07T05:09:09.270904Z"},{"id":"openSUSE-SU-2026:12391-1","title":"python313-GitPython-3.2.1-1.1 on GA media","description":"These are all security issues fixed in the python313-GitPython-3.2.1-1.1 package on the GA media of openSUSE Tumbleweed.","status":"published","published":"2026-11-02T00:00:00Z","modified":"2026-11-03T17:23:11.780078468Z","updated_at":"2026-11-03T18:00:34.078068Z"}]}}
