{"id":"CVE-2026-110855","aliases":["CLSA-2026-1792479054","DEBIAN-CVE-2026-110855","EUVD-2026-101738","GHSA-23mf-xhv8-69c2","GHSA-cwpg-5qqf-hf36","GHSA-fx3j-rwgx-fr94","GHSA-m64x-33q8-m5h7","MGASA-2026-0580","UBUNTU-CVE-2026-110855","openSUSE-SU-2026:12391-1"],"status":"published","title":"GitPython before 3.2.0 Environment Variable Disclosure via .gitmodules Submodule Name","description":"GitPython before 3.2.0 contains an information disclosure vulnerability that allows malicious repositories to expand environment variables via separate_git_dir in Repo._clone(), which omits expand_vars=False when calling Git.polish_url(). Attackers can craft a .gitmodules submodule name containing variable syntax so that clone plus submodule_update exposes secrets in .git/modules directory names and DEBUG logs.","severity":{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-09","score":0.0029,"percentile":0.21707,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-200"],"published":"2026-10-14T22:52:34Z","modified":"2026-11-07T04:45:03Z","fixes":[{"ecosystem":"PyPI","package":"gitpython","fixed_in":["3.2.0"],"sources":["ghsa","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":"0"},{"last_affected":"3.1.62"},{"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":"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":"0"},{"fixed":"3.2.0"}]}],"versions":["0.1.7","0.2.0-beta1","0.3.0-beta1","0.3.0-beta2","0.3.1-beta2","0.3.2","0.3.2.1","0.3.2.RC1","0.3.3","0.3.4","0.3.5","0.3.6","0.3.7","1.0.0","1.0.1","1.0.2","2.0.0","2.0.1","2.0.2","2.0.3","2.0.4","2.0.5","2.0.6","2.0.7","2.0.8","2.0.9","2.0.9.dev0","2.0.9.dev1","2.1.0","2.1.1","2.1.10","2.1.11","2.1.12","2.1.13","2.1.14","2.1.15","2.1.2","2.1.3","2.1.4","2.1.5","2.1.6","2.1.7","2.1.8","2.1.9","3.0.0","3.0.1","3.0.2","3.0.3","3.0.4","3.0.5","3.0.6","3.0.7","3.0.8","3.0.9","3.1.0","3.1.1","3.1.10","3.1.11","3.1.12","3.1.13","3.1.14","3.1.15","3.1.16","3.1.17","3.1.18","3.1.19","3.1.2","3.1.20","3.1.22","3.1.23","3.1.24","3.1.25","3.1.26","3.1.27","3.1.28","3.1.29","3.1.3","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.4","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.5","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.6","3.1.60","3.1.61","3.1.62","3.1.7","3.1.8","3.1.9"]},{"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-fx3j-rwgx-fr94","tags":["vendor-advisory","exploit","advisory","web","report"]},{"url":"https://www.vulncheck.com/advisories/gitpython-before-3.2.0-environment-variable-disclosure-via-gitmodules-submodule-name","tags":["third-party-advisory","advisory","report"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-110855","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-cwpg-5qqf-hf36"},{"url":"https://github.com/gitpython-developers/GitPython/pull/2255","tags":["web"]},{"url":"https://github.com/gitpython-developers/GitPython/commit/9349ff58ee00c9fb14d515c8e693f33b2144b238","tags":["web"]},{"url":"https://github.com/gitpython-developers/GitPython/releases/tag/3.2.0","tags":["web"]},{"url":"https://github.com/advisories/GHSA-fx3j-rwgx-fr94"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/110xxx/CVE-2026-110855.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-110855","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-110855","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-110855","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-110855","title":"GitPython before 3.2.0 Environment Variable Disclosure via .gitmodules Submodule Name","description":"GitPython before 3.2.0 contains an information disclosure vulnerability that allows malicious repositories to expand environment variables via separate_git_dir in Repo._clone(), which omits expand_vars=False when calling Git.polish_url(). Attackers can craft a .gitmodules submodule name containing variable syntax so that clone plus submodule_update exposes secrets in .git/modules directory names and DEBUG logs.","status":"published","published":"2026-10-14T22:52:34.854Z","modified":"2026-10-15T18:15:45.736Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N","level":"high","assessor":"cna"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-200"],"updated_at":"2026-11-02T13:34:26.24368Z"}],"euvd":[{"id":"EUVD-2026-101738","description":"GitPython before 3.2.0 contains an information disclosure vulnerability that allows malicious repositories to expand environment variables via separate_git_dir in Repo._clone(), which omits expand_vars=False when calling Git.polish_url(). Attackers can craft a .gitmodules submodule name containing variable syntax so that clone plus submodule_update exposes secrets in .git/modules directory names and DEBUG logs.","status":"published","published":"2026-10-14T22:52:34Z","modified":"2026-10-15T18:15:45Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T17:00:10.766559Z"}],"ghsa":[{"id":"GHSA-cwpg-5qqf-hf36","title":"GitPython before 3.2.0 contains an information disclosure vulnerability that allows malicious...","description":"GitPython before 3.2.0 contains an information disclosure vulnerability that allows malicious repositories to expand environment variables via separate_git_dir in Repo._clone(), which omits expand_vars=False when calling Git.polish_url(). Attackers can craft a .gitmodules submodule name containing variable syntax so that clone plus submodule_update exposes secrets in .git/modules directory names and DEBUG logs.","status":"published","published":"2026-10-15T00:31:18Z","modified":"2026-10-15T00:31:19Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/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":"high"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-200"],"updated_at":"2026-11-02T15:13:29.914076Z"},{"id":"GHSA-fx3j-rwgx-fr94","title":"GitPython: separate_git_dir leaks env vars via crafted .gitmodules submodule name","description":"### Summary\n`Repo._clone()`'s handling of `separate_git_dir` never disables `Git.polish_url()`'s environment-variable expansion, unlike every other caller of that helper that handles attacker-influenced data. Since `separate_git_dir` can be derived from an attacker-controlled `.gitmodules` submodule name, a completely ordinary `clone` + `submodule_update(init=True, recursive=True)` on a malicious repository expands an environment variable of the attacker's choosing and exposes its value both as a persistent on-disk directory name and, whenever DEBUG-level logging is enabled (a common CI/verbose-logging configuration), in a `Popen(...)` log line that is not redacted the way credential-bearing arguments are.\n\n### Root cause / Details\n`git/repo/base.py`'s `_clone()` has:\n\n```python\nsep_dir = kwargs.get(\"separate_git_dir\")\nif sep_dir:\n    kwargs[\"separate_git_dir\"] = Git.polish_url(sep_dir)\n```\n\nThis is the only remaining `Git.polish_url()` call site handling attacker-influenced data that does not pass `expand_vars=False` (there is one other unguarded call site, `clone_path` at line 1537, but its input is the caller's own local destination path, not attacker-controlled). The sibling fixes for `GHSA-94p4-4cq8-9g67`/`GHSA-rwj8-pgh3-r573` added `expand_vars=False` to `clone_url` (5 lines below this one, in the same function), `git/repo/base.py:1599`, `remote.py:814`, and `submodule/base.py:686` — this one did not receive it. (`submodule/root.py` also currently sets `expand_vars=False` at a related call site, but that was added incidentally by a later, unrelated commit, not as part of these security fixes.) `Git.polish_url()`'s docstring itself says: \"Disable this for values obtained from an untrusted source, such as remote URLs.\" Its non-Cygwin branch does plain `os.path.expandvars()` on the entire string, including embedded variables anywhere in an absolute path.\n\n`separate_git_dir` reaches `_clone()` from `Submodule._clone_repo()` (`git/objects/submodule/base.py`) as `module_abspath = osp.join(parent_repo.git_dir, \"modules\", name)`, where `name` is the raw `.gitmodules` submodule section name — fully attacker-controlled content of a cloned repository. `_validated_name()` (added by the fix for `GHSA-hmq2-w58f-27jc`) only rejects names that are absolute, contain a drive letter, or contain `..` path components; it does not reject `$`. `_need_gitfile_submodules()` is true for git ≥ 1.7.5, i.e. the default path for virtually every real-world installation.\n\n### Reproduction (live, against real, unmodified GitPython code, no monkeypatching)\nBuilt a hostile repository with a plain, ordinary git command: `git submodule add --name '$GPY_SECRET_MARKER' ../evil_submodule sub`. Set an environment variable in the \"victim\" process (standing in for a real CI secret such as `AWS_SECRET_ACCESS_KEY`): `GPY_SECRET_MARKER=AKIA_TOTALLY_FAKE_BUT_SECRET_LOOKING_VALUE_12345`. Then ran the exact ordinary workflow the `GHSA-hmq2-w58f-27jc` advisory's own PoC also uses:\n\n```python\nrepo = Repo.clone_from(evil_parent_url, victim_clone_dir)\nrepo.submodule_update(init=True, recursive=True)\n```\n\nObserved result (`ls .git/modules/`):\n\n```\n.git/modules/AKIA_TOTALLY_FAKE_BUT_SECRET_LOOKING_VALUE_12345/   (a full git repo: HEAD, config, objects/, refs/, hooks/)\n```\n\nThe literal `$GPY_SECRET_MARKER`-named directory was never created — only the expanded one exists, proving expansion happened before the value reached the `git clone --separate-git-dir=...` subprocess argument. The persistent, expanded-name directory under `.git/modules/` is itself the primary disclosure channel — it survives on disk for as long as the clone does. A second, independent channel: with the `git.cmd`/`git.repo.base` loggers at DEBUG level (a common CI/verbose-logging setting, not requiring any special flag), GitPython's own `Popen(...)` debug log line prints the fully expanded `--separate-git-dir=\u003cvalue\u003e` subprocess argument verbatim — `remove_password_if_present()` only redacts URL passwords/Authorization headers, not path-style arguments like this one, so the expanded value is not scrubbed. (The call also fails downstream with an `OSError`/`FileNotFoundError` referencing the original, unexpanded name, since `_write_git_file_and_module_config()` looks for a directory using the pre-expansion name — but that error message itself does not contain the expanded/secret value.)\n\n**Control**: the same call with an ordinary (no `$`) submodule name succeeds normally and creates `.git/modules/\u003cname\u003e` as expected — isolating the gap to submodule names containing environment-variable syntax specifically.\n\n### Already-known check\nChecked all 35 published `gitpython-developers/GitPython` security advisories in full text. `GHSA-hmq2-w58f-27jc` covers the same `name` → `.git/modules/\u003cname\u003e` path but for path-traversal (`../../..`), not variable expansion. `GHSA-59cr-6r3x-644w` covers the sibling `path` field reaching unguarded `os.makedirs()`. `GHSA-94p4-4cq8-9g67`/`GHSA-rwj8-pgh3-r573` cover `expand_vars`, but only on URL arguments. `git log -p -S \"separate_git_dir\" -- git/repo/base.py` shows neither fix commit touches this line; it is unchanged since its 2016 introduction (commit `57d05379`).\n\nThe closest topical match is `GHSA-8mcc-hrx5-hvxc` (\"`clone_from()`/`clone()` omit `--separate-git-dir` from `unsafe_git_clone_options`\") — already fixed, and its own text discusses this same `_clone()` handling of `separate_git_dir`. That fix only added `--separate-git-dir` to the `unsafe_git_clone_options` denylist, which gates the direct-kwarg vector; `Submodule._clone_repo()` explicitly sets `allow_unsafe_options=True` for the gitfile-submodules path, unconditionally bypassing that exact denylist (confirmed: the reproduction above succeeds against current `main`, which already has that fix). The bug this report describes is independent of that denylist — the *value* itself is expanded via `os.path.expandvars()` regardless of the denylist outcome — so the two are distinct, but flagging the relationship explicitly since a reviewer would otherwise reasonably ask about it.\n\nNo advisory describes environment-variable expansion via the internal `separate_git_dir` path.\n\n### Suggested fix\nAdd `expand_vars=False` to the `Git.polish_url(sep_dir)` call in `_clone()`, matching the four other call sites already fixed for the same class of issue.\n\n### Verification level\nReproduced in a real environment: plain Linux, unmodified GitPython from current `main`, real git ≥ 1.7.5 behavior, an ordinary `git submodule add` + `clone` + `submodule_update(init=True, recursive=True)` workflow with no non-default configuration or monkeypatching required. Causation isolated via a control group (identical workflow, ordinary submodule name, no expansion).\n\n### AI Use Disclosure\nAI (Claude) was used as a tool under my direction for code review, code summarization, and script development. A human reviewed and verified the AI-generated results before submission.\n\nCredit: please use \"baeseungwon1010\" (github.com/baeseungwon1010).","status":"published","published":"2026-10-27T15:26:32Z","modified":"2026-10-27T15:26:33Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-200"],"updated_at":"2026-11-02T14:41:37.912288Z"}],"nvd":[{"id":"CVE-2026-110855","description":"GitPython before 3.2.0 contains an information disclosure vulnerability that allows malicious repositories to expand environment variables via separate_git_dir in Repo._clone(), which omits expand_vars=False when calling Git.polish_url(). Attackers can craft a .gitmodules submodule name containing variable syntax so that clone plus submodule_update exposes secrets in .git/modules directory names and DEBUG logs.","status":"published","published":"2026-10-14T23:17:26.623Z","modified":"2026-10-16T18:14:51.46Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/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":"high","assessor":"secondary"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-200"],"updated_at":"2026-11-02T14:12:59.328869Z"}],"osv":[{"id":"CVE-2026-110855","title":"GitPython before 3.2.0 Environment Variable Disclosure via .gitmodules Submodule Name","description":"GitPython before 3.2.0 contains an information disclosure vulnerability that allows malicious repositories to expand environment variables via separate_git_dir in Repo._clone(), which omits expand_vars=False when calling Git.polish_url(). Attackers can craft a .gitmodules submodule name containing variable syntax so that clone plus submodule_update exposes secrets in .git/modules directory names and DEBUG logs.","status":"published","published":"2026-10-14T22:52:34.854Z","modified":"2026-11-03T17:26:57.661958134Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-200"],"updated_at":"2026-11-03T18:00:15.601011Z"},{"id":"DEBIAN-CVE-2026-110855","description":"GitPython before 3.2.0 contains an information disclosure vulnerability that allows malicious repositories to expand environment variables via separate_git_dir in Repo._clone(), which omits expand_vars=False when calling Git.polish_url(). Attackers can craft a .gitmodules submodule name containing variable syntax so that clone plus submodule_update exposes secrets in .git/modules directory names and DEBUG logs.","status":"published","published":"2026-10-14T23:17:26.623Z","modified":"2026-10-31T14:00:07.259127182Z","cvss":[{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/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":"high"}],"updated_at":"2026-11-02T15:45:49.824149Z"},{"id":"GHSA-fx3j-rwgx-fr94","title":"GitPython: separate_git_dir leaks env vars via crafted .gitmodules submodule name","description":"### Summary\n`Repo._clone()`'s handling of `separate_git_dir` never disables `Git.polish_url()`'s environment-variable expansion, unlike every other caller of that helper that handles attacker-influenced data. Since `separate_git_dir` can be derived from an attacker-controlled `.gitmodules` submodule name, a completely ordinary `clone` + `submodule_update(init=True, recursive=True)` on a malicious repository expands an environment variable of the attacker's choosing and exposes its value both as a persistent on-disk directory name and, whenever DEBUG-level logging is enabled (a common CI/verbose-logging configuration), in a `Popen(...)` log line that is not redacted the way credential-bearing arguments are.\n\n### Root cause / Details\n`git/repo/base.py`'s `_clone()` has:\n\n```python\nsep_dir = kwargs.get(\"separate_git_dir\")\nif sep_dir:\n    kwargs[\"separate_git_dir\"] = Git.polish_url(sep_dir)\n```\n\nThis is the only remaining `Git.polish_url()` call site handling attacker-influenced data that does not pass `expand_vars=False` (there is one other unguarded call site, `clone_path` at line 1537, but its input is the caller's own local destination path, not attacker-controlled). The sibling fixes for `GHSA-94p4-4cq8-9g67`/`GHSA-rwj8-pgh3-r573` added `expand_vars=False` to `clone_url` (5 lines below this one, in the same function), `git/repo/base.py:1599`, `remote.py:814`, and `submodule/base.py:686` — this one did not receive it. (`submodule/root.py` also currently sets `expand_vars=False` at a related call site, but that was added incidentally by a later, unrelated commit, not as part of these security fixes.) `Git.polish_url()`'s docstring itself says: \"Disable this for values obtained from an untrusted source, such as remote URLs.\" Its non-Cygwin branch does plain `os.path.expandvars()` on the entire string, including embedded variables anywhere in an absolute path.\n\n`separate_git_dir` reaches `_clone()` from `Submodule._clone_repo()` (`git/objects/submodule/base.py`) as `module_abspath = osp.join(parent_repo.git_dir, \"modules\", name)`, where `name` is the raw `.gitmodules` submodule section name — fully attacker-controlled content of a cloned repository. `_validated_name()` (added by the fix for `GHSA-hmq2-w58f-27jc`) only rejects names that are absolute, contain a drive letter, or contain `..` path components; it does not reject `$`. `_need_gitfile_submodules()` is true for git ≥ 1.7.5, i.e. the default path for virtually every real-world installation.\n\n### Reproduction (live, against real, unmodified GitPython code, no monkeypatching)\nBuilt a hostile repository with a plain, ordinary git command: `git submodule add --name '$GPY_SECRET_MARKER' ../evil_submodule sub`. Set an environment variable in the \"victim\" process (standing in for a real CI secret such as `AWS_SECRET_ACCESS_KEY`): `GPY_SECRET_MARKER=AKIA_TOTALLY_FAKE_BUT_SECRET_LOOKING_VALUE_12345`. Then ran the exact ordinary workflow the `GHSA-hmq2-w58f-27jc` advisory's own PoC also uses:\n\n```python\nrepo = Repo.clone_from(evil_parent_url, victim_clone_dir)\nrepo.submodule_update(init=True, recursive=True)\n```\n\nObserved result (`ls .git/modules/`):\n\n```\n.git/modules/AKIA_TOTALLY_FAKE_BUT_SECRET_LOOKING_VALUE_12345/   (a full git repo: HEAD, config, objects/, refs/, hooks/)\n```\n\nThe literal `$GPY_SECRET_MARKER`-named directory was never created — only the expanded one exists, proving expansion happened before the value reached the `git clone --separate-git-dir=...` subprocess argument. The persistent, expanded-name directory under `.git/modules/` is itself the primary disclosure channel — it survives on disk for as long as the clone does. A second, independent channel: with the `git.cmd`/`git.repo.base` loggers at DEBUG level (a common CI/verbose-logging setting, not requiring any special flag), GitPython's own `Popen(...)` debug log line prints the fully expanded `--separate-git-dir=\u003cvalue\u003e` subprocess argument verbatim — `remove_password_if_present()` only redacts URL passwords/Authorization headers, not path-style arguments like this one, so the expanded value is not scrubbed. (The call also fails downstream with an `OSError`/`FileNotFoundError` referencing the original, unexpanded name, since `_write_git_file_and_module_config()` looks for a directory using the pre-expansion name — but that error message itself does not contain the expanded/secret value.)\n\n**Control**: the same call with an ordinary (no `$`) submodule name succeeds normally and creates `.git/modules/\u003cname\u003e` as expected — isolating the gap to submodule names containing environment-variable syntax specifically.\n\n### Already-known check\nChecked all 35 published `gitpython-developers/GitPython` security advisories in full text. `GHSA-hmq2-w58f-27jc` covers the same `name` → `.git/modules/\u003cname\u003e` path but for path-traversal (`../../..`), not variable expansion. `GHSA-59cr-6r3x-644w` covers the sibling `path` field reaching unguarded `os.makedirs()`. `GHSA-94p4-4cq8-9g67`/`GHSA-rwj8-pgh3-r573` cover `expand_vars`, but only on URL arguments. `git log -p -S \"separate_git_dir\" -- git/repo/base.py` shows neither fix commit touches this line; it is unchanged since its 2016 introduction (commit `57d05379`).\n\nThe closest topical match is `GHSA-8mcc-hrx5-hvxc` (\"`clone_from()`/`clone()` omit `--separate-git-dir` from `unsafe_git_clone_options`\") — already fixed, and its own text discusses this same `_clone()` handling of `separate_git_dir`. That fix only added `--separate-git-dir` to the `unsafe_git_clone_options` denylist, which gates the direct-kwarg vector; `Submodule._clone_repo()` explicitly sets `allow_unsafe_options=True` for the gitfile-submodules path, unconditionally bypassing that exact denylist (confirmed: the reproduction above succeeds against current `main`, which already has that fix). The bug this report describes is independent of that denylist — the *value* itself is expanded via `os.path.expandvars()` regardless of the denylist outcome — so the two are distinct, but flagging the relationship explicitly since a reviewer would otherwise reasonably ask about it.\n\nNo advisory describes environment-variable expansion via the internal `separate_git_dir` path.\n\n### Suggested fix\nAdd `expand_vars=False` to the `Git.polish_url(sep_dir)` call in `_clone()`, matching the four other call sites already fixed for the same class of issue.\n\n### Verification level\nReproduced in a real environment: plain Linux, unmodified GitPython from current `main`, real git ≥ 1.7.5 behavior, an ordinary `git submodule add` + `clone` + `submodule_update(init=True, recursive=True)` workflow with no non-default configuration or monkeypatching required. Causation isolated via a control group (identical workflow, ordinary submodule name, no expansion).\n\n### AI Use Disclosure\nAI (Claude) was used as a tool under my direction for code review, code summarization, and script development. A human reviewed and verified the AI-generated results before submission.\n\nCredit: please use \"baeseungwon1010\" (github.com/baeseungwon1010).","status":"published","published":"2026-10-27T15:26:32Z","modified":"2026-10-27T15:47:24.291028301Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-200"],"updated_at":"2026-11-02T15:46:47.119724Z"},{"id":"UBUNTU-CVE-2026-110855","description":"GitPython before 3.2.0 contains an information disclosure vulnerability that allows malicious repositories to expand environment variables via separate_git_dir in Repo._clone(), which omits expand_vars=False when calling Git.polish_url(). Attackers can craft a .gitmodules submodule name containing variable syntax so that clone plus submodule_update exposes secrets in .git/modules directory names and DEBUG logs.","status":"published","published":"2026-10-14T23:17:00Z","modified":"2026-10-28T17:50:34Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N","level":"medium"},{"version":"4.0","score":7.1,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N","level":"high"}],"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":"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"}]}}
