GitPython Will Happily Clone a Submodule Straight Into .git/hooks
GitPython’s create_submodule() was validating the checkout path after it had already cloned into it. pass it a path of .git/hooks, point the URL at a repo whose root contains an executable named post-checkout, and the next git checkout runs your code. CVE-2026-114145, GHSA-83vg-56qc-22m7, fixed in 3.2.1. on 3.1.x the call succeeds silently. on 3.2.0 it raises ValueError after the hook is already on disk and does not clean it up. either way, you own the next git operation.
what went wrong
Submodule.add() runs a permissive path check (_to_relative_path) that accepts .git/hooks, then calls _clone_repo, then asks the index to add the submodule. the strict check that rejects .git aliases (_validate_repo_path) only runs at index time, and only exists from 3.2.0 onward. so the clone mutates disk first. the ValueError arrives late, if at all, and does not roll back the planted hook.
that breaks the “validate before mutation” rule the project had already tried to enforce. exploitation needs an application that lets an attacker influence the submodule path (for example by ingesting a user-supplied submodule spec) and a victim repo whose hooks directory is empty enough for git to clone into, which is common in minimal CI checkouts and git init with an empty template.

lab PoC: plant, then checkout
setup is two tiny repos. evilsub/ has an executable post-checkout at its root that writes /tmp/gitpython-cve-lab.txt. victim/ is a normal repo with at least one commit and a second branch, and an empty hooks directory. then:
python3 -c "import git, os.path as osp\ntry:\n git.Repo('victim').create_submodule(name='x', path='.git/hooks', url=osp.abspath('evilsub'))\nexcept ValueError:\n pass # 3.2.0+: raises after the clone; hook already planted"\ngit -C victim checkout other-branch


who should care
- any service that creates submodules from user-controlled name/path/url tuples.
- CI helpers, monorepo tools, and internal developer portals that wrap GitPython.
- lockfile and image builders that pin
GitPython<3.2.1and never look up.
Debian’s tracker still shows bookworm, trixie, and forky packaging lines vulnerable at the time of writing, with sid carrying 3.2.1-1. do not assume your distro package is safe because the upstream advisory is two weeks old.
fix and hunt
- upgrade to GitPython 3.2.1+ everywhere, then verify with
python3 -c "import git; print(git.__version__)". - reject submodule paths that touch
.gitbefore calling the library, even after the upgrade. defense in depth is free here. - hunt repos for unexpected executables under
.git/hooks/, especiallypost-checkout,post-commit, andpre-committhat you did not install. - review application code for
create_submodule/Submodule.addcalls that take path from request bodies, tickets, or YAML someone else wrote.
GitPython is in a lot of automation that never gets a second look until something like this ships. pin 3.2.1, validate paths yourself, and stop letting untrusted strings choose where your tools write inside .git.
if your Python estate still pins GitPython from 2024, bump it before the next submodule feature request arrives. Book a slot with Dark Edge Consulting.
