CVE-2026-110860
GitPython 3.1.62 before 3.2.0 Symlink Path Traversal via Submodule.move()
GitPython from 3.1.62 before 3.2.0 contains a symlink-following flaw in Submodule.move(), which checks the destination only lexically and does not reject symbolic links in intermediate path components. An untrusted repository can commit a symlink such as vendor pointing outside the worktree, so an application moving an initialized submodule beneath it relocates the checkout externally and corrupts the index and .gitmodules.
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Passive
What an attacker can affect
- Confidentiality
- None
- Integrity
- High
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
6.9
GitPython 3.1.62 before 3.2.0 Symlink Path Traversal via Submodule.move()
GitPython from 3.1.62 before 3.2.0 contains a symlink-following flaw in Submodule.move(), which checks the destination only lexically and does not reject symbolic links in intermediate path components. An untrusted repository can commit a symlink such as vendor pointing outside the worktree, so an application moving an initialized submodule beneath it relocates the checkout externally and corrupts the index and .gitmodules.
- Record
CVE-2026-110860- CVSS 4.0
- 6.9 Medium
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N - CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L - Weakness
- CWE-59
- Updated
- 2026-11-02
EUVDEUVD-2026-101743
6.9
GitPython from 3.1.62 before 3.2.0 contains a symlink-following flaw in Submodule.move(), which checks the destination only lexically and does not reject symbolic links in intermediate path components. An untrusted repository can commit a symlink such as vendor pointing outside the worktree, so an application moving an initialized submodule beneath it relocates the checkout externally and corrupts the index and .gitmodules.
- Record
EUVD-2026-101743- CVSS 4.0
- 6.9 Medium
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-cfp4-478m-4f52
6.9
GitPython from 3.1.62 before 3.2.0 contains a symlink-following flaw in Submodule.move(), which...
GitPython from 3.1.62 before 3.2.0 contains a symlink-following flaw in Submodule.move(), which checks the destination only lexically and does not reject symbolic links in intermediate path components. An untrusted repository can commit a symlink such as vendor pointing outside the worktree, so an application moving an initialized submodule beneath it relocates the checkout externally and corrupts the index and .gitmodules.
- Record
GHSA-cfp4-478m-4f52- CVSS 4.0
- 6.9 Medium
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:H/VA:L/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 - CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L - Weakness
- CWE-59
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-gq48-pqfc-9p58 No score
GitPython: Submodule.move() follows an attacker-committed intermediate symlink outside the worktree
Summary
GitPython's public Submodule.move() API verifies that its destination is lexically inside the parent repository, but it does not reject symbolic links in the destination's intermediate components before filesystem operations. On systems that check out Git symlinks, an untrusted repository can therefore redirect a fixed, application-selected move destination outside the parent worktree.
The reporter reproduced this on the latest release, GitPython 3.1.62, and current main. In the strict scenario, the repository commits vendor -> ../outside, while the consuming application independently passes the fixed literal vendor/module. Submodule.move() follows vendor, moves the initialized submodule checkout to outside/module, rewrites .gitmodules and the index to the symlinked path, and leaves the index unable to produce a tree.
The same result occurs when the committed symlink contains an absolute path to an external directory. No attacker-controlled API argument, local symlink planting, race, Git hook, unsafe-option flag, or network access during the proof is required.
Trust boundary and preconditions
The attacker controls:
- an untrusted Git repository that the application clones;
- a mode-
120000tracked symlink namedvendor; - the initialized submodule's content.
The consuming application controls the call and uses a fixed layout rule:
submodule.move("vendor/module")
The trigger requires the application to initialize the submodule and later call Submodule.move() with a path below the attacker-committed symlink. Ordinary clone or submodule update does not automatically make this call. Windows was not tested. The primitive applies where Git materializes the tracked entry as a real symlink; on Windows that generally requires core.symlinks=true with Developer Mode or sufficient privilege. Configurations that materialize it as an ordinary file do not take this path.
Product and directly verified revisions
- Package:
GitPython(pipecosystem) - Latest release: 3.1.62, tag commit
db47516dac750c5968b8f6d388b18712c355df50 - Current
main:b62e91be897f687a622e40cd1e59c33dce45f124 - Relevant
git/objects/submodule/base.pySHA-256 in both revisions:a4355d49e0b75c7c7e74d33ac70ff9550f940c977848c99da40eb41e034b189c - Upstream state rechecked: 2026-09-10 KST
Only these two revisions were directly tested.
Severity and weakness
The demonstrated impact is an integrity boundary violation: repository content selects an existing directory outside the worktree, and GitPython moves an attacker-influenced checkout into a missing or empty child path there with the victim process's permissions. It also removes the original checkout and corrupts the parent repository's index/configuration state.
For a downstream service that remotely accepts repositories and performs this exact layout operation with user involvement retained, AV:N gives a conditional application-level score of 7.1. A fully automatic service may warrant a different UI assessment. The library report uses AV:L because the vulnerable API call is a separate application action and is not part of GitPython's ordinary clone/update flow.
No confidentiality impact, code execution, overwrite of an existing file, or overwrite of a non-empty directory was demonstrated.
Reproducer
The following script creates only temporary local repositories and a self-generated canary. It passes protocol.file.allow=always separately with -c on each fixture Git invocation; that permission is process-scoped and is not persisted to global or repository configuration.
Run it from a fresh source checkout:
git clone --depth 1 --branch 3.1.62 https://github.com/gitpython-developers/GitPython.git
python3 poc.py /path/to/GitPython
python3 poc.py --absolute-link /path/to/GitPython
--absolute-link commits the temporary external directory's absolute path instead of ../outside.
#!/usr/bin/env python3…
- Record
GHSA-gq48-pqfc-9p58- Weakness
- CWE-22, CWE-59
- Updated
- 2026-11-02
NVD
6.9
GitPython from 3.1.62 before 3.2.0 contains a symlink-following flaw in Submodule.move(), which checks the destination only lexically and does not reject symbolic links in intermediate path components. An untrusted repository can commit a symlink such as vendor pointing outside the worktree, so an application moving an initialized submodule beneath it relocates the checkout externally and corrupts the index and .gitmodules.
- Record
CVE-2026-110860- CVSS 4.0
- 6.9 Medium
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:H/VA:L/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 - CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L - Weakness
- CWE-59
- Updated
- 2026-11-02
OSV
6.9
GitPython 3.1.62 before 3.2.0 Symlink Path Traversal via Submodule.move()
GitPython from 3.1.62 before 3.2.0 contains a symlink-following flaw in Submodule.move(), which checks the destination only lexically and does not reject symbolic links in intermediate path components. An untrusted repository can commit a symlink such as vendor pointing outside the worktree, so an application moving an initialized submodule beneath it relocates the checkout externally and corrupts the index and .gitmodules.
- Record
CVE-2026-110860- CVSS 4.0
- 6.9 Medium
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N - Weakness
- CWE-59
- Updated
- 2026-11-02
Show 4 moreShow less
OSVDEBIAN-CVE-2026-110860
6.9
GitPython from 3.1.62 before 3.2.0 contains a symlink-following flaw in Submodule.move(), which checks the destination only lexically and does not reject symbolic links in intermediate path components. An untrusted repository can commit a symlink such as vendor pointing outside the worktree, so an application moving an initialized submodule beneath it relocates the checkout externally and corrupts the index and .gitmodules.
- Record
DEBIAN-CVE-2026-110860- CVSS 4.0
- 6.9 Medium
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:H/VA:L/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 - Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-110860
6.9
GitPython from 3.1.62 before 3.2.0 contains a symlink-following flaw in Submodule.move(), which checks the destination only lexically and does not reject symbolic links in intermediate path components. An untrusted repository can commit a symlink such as vendor pointing outside the worktree, so an application moving an initialized submodule beneath it relocates the checkout externally and corrupts the index and .gitmodules.
- Record
UBUNTU-CVE-2026-110860- CVSS 4.0
- 6.9 Medium
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N - CVSS 3.1
- 6.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L - Updated
- 2026-11-02
OSVGHSA-gq48-pqfc-9p58 No score
GitPython: Submodule.move() follows an attacker-committed intermediate symlink outside the worktree
Summary
GitPython's public Submodule.move() API verifies that its destination is lexically inside the parent repository, but it does not reject symbolic links in the destination's intermediate components before filesystem operations. On systems that check out Git symlinks, an untrusted repository can therefore redirect a fixed, application-selected move destination outside the parent worktree.
The reporter reproduced this on the latest release, GitPython 3.1.62, and current main. In the strict scenario, the repository commits vendor -> ../outside, while the consuming application independently passes the fixed literal vendor/module. Submodule.move() follows vendor, moves the initialized submodule checkout to outside/module, rewrites .gitmodules and the index to the symlinked path, and leaves the index unable to produce a tree.
The same result occurs when the committed symlink contains an absolute path to an external directory. No attacker-controlled API argument, local symlink planting, race, Git hook, unsafe-option flag, or network access during the proof is required.
Trust boundary and preconditions
The attacker controls:
- an untrusted Git repository that the application clones;
- a mode-
120000tracked symlink namedvendor; - the initialized submodule's content.
The consuming application controls the call and uses a fixed layout rule:
submodule.move("vendor/module")
The trigger requires the application to initialize the submodule and later call Submodule.move() with a path below the attacker-committed symlink. Ordinary clone or submodule update does not automatically make this call. Windows was not tested. The primitive applies where Git materializes the tracked entry as a real symlink; on Windows that generally requires core.symlinks=true with Developer Mode or sufficient privilege. Configurations that materialize it as an ordinary file do not take this path.
Product and directly verified revisions
- Package:
GitPython(pipecosystem) - Latest release: 3.1.62, tag commit
db47516dac750c5968b8f6d388b18712c355df50 - Current
main:b62e91be897f687a622e40cd1e59c33dce45f124 - Relevant
git/objects/submodule/base.pySHA-256 in both revisions:a4355d49e0b75c7c7e74d33ac70ff9550f940c977848c99da40eb41e034b189c - Upstream state rechecked: 2026-09-10 KST
Only these two revisions were directly tested.
Severity and weakness
The demonstrated impact is an integrity boundary violation: repository content selects an existing directory outside the worktree, and GitPython moves an attacker-influenced checkout into a missing or empty child path there with the victim process's permissions. It also removes the original checkout and corrupts the parent repository's index/configuration state.
For a downstream service that remotely accepts repositories and performs this exact layout operation with user involvement retained, AV:N gives a conditional application-level score of 7.1. A fully automatic service may warrant a different UI assessment. The library report uses AV:L because the vulnerable API call is a separate application action and is not part of GitPython's ordinary clone/update flow.
No confidentiality impact, code execution, overwrite of an existing file, or overwrite of a non-empty directory was demonstrated.
Reproducer
The following script creates only temporary local repositories and a self-generated canary. It passes protocol.file.allow=always separately with -c on each fixture Git invocation; that permission is process-scoped and is not persisted to global or repository configuration.
Run it from a fresh source checkout:
git clone --depth 1 --branch 3.1.62 https://github.com/gitpython-developers/GitPython.git
python3 poc.py /path/to/GitPython
python3 poc.py --absolute-link /path/to/GitPython
--absolute-link commits the temporary external directory's absolute path instead of ../outside.
#!/usr/bin/env python3…
- Record
GHSA-gq48-pqfc-9p58- Weakness
- CWE-22, CWE-59
- Updated
- 2026-11-02
OSVMGASA-2026-0580 No score
Updated python-gitpython package fixes security vulnerabilities
Please see the links for additional information.
- Record
MGASA-2026-0580- Updated
- 2026-11-07
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
gitpythonPyPI |
before 3.2.0 | 3.2.0 |
python-gitDebian:12 |
before 3.1.30-1+deb12u3, plus listed versions 3.1.30-13.1.30-1+deb12u2 |
3.1.30-1+deb12u3 |
python-gitDebian:13 |
before 3.1.44-1+deb13u1, plus listed versions 3.1.44-1 |
3.1.44-1+deb13u1 |
python-gitDebian:14 |
before 3.2.1-1, plus listed versions 3.1.44-13.1.45-13.1.46-13.1.50-13.1.61-13.1.62-13.1.62-23.1.62-3 |
3.2.1-1 |
gitpythonPyPI |
before 3.2.0, plus listed versions 0.1.70.2.0-beta10.3.0-beta10.3.0-beta20.3.1-beta2 Show 45 moreShow less0.3.20.3.2.10.3.2.RC10.3.30.3.40.3.50.3.60.3.71.0.01.0.11.0.22.0.02.0.12.0.22.0.32.0.42.0.52.0.62.0.72.0.82.0.92.0.9.dev02.0.9.dev12.1.02.1.12.1.102.1.112.1.122.1.132.1.142.1.152.1.22.1.32.1.42.1.52.1.62.1.72.1.82.1.93.0.03.0.13.0.23.0.33.0.43.0.5 |
3.2.0 |
python-gitpythonMageia:10 From an advisory covering 6 CVEs |
before 3.2.1-1.mga10 | 3.2.1-1.mga10 |
python-gitUbuntu:Pro:14.04:LTS |
All versions, plus listed versions 0.3.2~RC1-20.3.2~RC1-30.3.2~RC1-3ubuntu0.1~esm10.3.2~RC1-3ubuntu0.1~esm20.3.2~RC1-3ubuntu0.1~esm3 |
None known |
python-gitUbuntu:Pro:16.04:LTS |
All versions, plus listed versions 1.0.1+git137-gc8b8379-11.0.1+git137-gc8b8379-21.0.1+git137-gc8b8379-2.11.0.1+git137-gc8b8379-2.1ubuntu0.1~esm11.0.1+git137-gc8b8379-2.1ubuntu0.1~esm21.0.1+git137-gc8b8379-2.1ubuntu0.1~esm4 |
None known |
python-gitUbuntu:Pro:18.04:LTS |
All versions, plus listed versions 2.1.5-12.1.6-12.1.7-12.1.8-12.1.8-1ubuntu0.1~esm12.1.8-1ubuntu0.1~esm22.1.8-1ubuntu0.1~esm4 |
None known |
python-gitUbuntu:Pro:20.04:LTS |
All versions, plus listed versions 2.1.11-13.0.4-13.0.5-13.0.7-13.0.7-1ubuntu0.1~esm13.0.7-1ubuntu0.1~esm23.0.7-1ubuntu0.1~esm4 |
None known |
Show 4 moreShow less
| Package | Affected | Fixed |
|---|---|---|
python-gitUbuntu:Pro:22.04:LTS |
All versions, plus listed versions 3.1.14-13.1.23-13.1.24-13.1.24-1ubuntu0.1~esm13.1.24-1ubuntu0.1~esm23.1.24-1ubuntu0.1~esm3 |
None known |
python-gitUbuntu:Pro:24.04:LTS |
All versions, plus listed versions 3.1.30-13.1.37-13.1.37-33.1.37-3ubuntu0.1~esm2 |
None known |
python-gitUbuntu:26.10 |
All versions, plus listed versions 3.1.46-13.1.50-13.1.61-1 |
None known |
python-gitUbuntu:Pro:26.04:LTS |
All versions, plus listed versions 3.1.44-13.1.45-13.1.46-13.1.46-1ubuntu0.1~esm1 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, aliases, facts, references | OSV | |
| affected, aliases, references | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-110860 is a medium vulnerability (CVSS 6.9) in Gitpython-developers Gitpython, published October 14, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.19%, the 8th percentile. Fixed in gitpython 3.2.0.
References
github.com/gitpython-developers/GitPython/security/advisories/GHSA-gq48-pqfc-9p58
vulncheck.com/advisories/gitpython-3.1.62-before-3.2.0-symlink-path-traversal-via-submodule-move
nvd.nist.gov/vuln/detail/CVE-2026-110860
github.com/advisories/GHSA-cfp4-478m-4f52
github.com/gitpython-developers/GitPython/pull/2232
github.com/gitpython-developers/GitPython/commit/24b6f95d4b90ab1483fb601c736ac6e7c059cd61
Show 11 moreShow less
github.com/gitpython-developers/GitPython/releases/tag/3.2.0
github.com/advisories/GHSA-gq48-pqfc-9p58
github.com/CVEProject/cvelistV5/tree/main/cves/2026/110xxx/CVE-2026-110860.json
security-tracker.debian.org/tracker/CVE-2026-110860
github.com/gitpython-developers/GitPython
advisories.mageia.org/MGASA-2026-0580.html
bugs.mageia.org/show_bug.cgi?id=36507
github.com/gitpython-developers/GitPython/releases
lists.fedoraproject.org/archives/list/[email protected]/message/OOS5EDMLYZWKECAUWB5INCTGXLGARLNL
ubuntu.com/security/CVE-2026-110860
cve.org/CVERecord?id=CVE-2026-110860
Left out here: 65 versions and the rest of 2 source descriptions. The JSON record has them.