Skip to content

GitPython Will Happily Clone a Submodule Straight Into .git/hooks

GitPython CVE-2026-114145: Submodule Path Plants .git/hooks RCE

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.

Lab only. the PoC below builds a disposable victim repo and a disposable evil submodule on a Kali box you own. it plants a hook that writes a marker file, nothing else. do not point create_submodule at paths inside repositories you do not control. upgrade GitPython to 3.2.1 or later in every environment that accepts submodule paths from untrusted input.

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.

Kali terminal checking installed GitPython version and confirming it is below the 3.2.1 fix for CVE-2026-114145.
Step 0: check the installed GitPython. anything at or below 3.2.0 is in scope. 3.2.1 is the floor.

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
Kali terminal running a lab-only create_submodule into .git/hooks and showing the planted post-checkout hook on disk.
Step 1 (lab): create_submodule clones into .git/hooks. ls shows post-checkout executable before any “validation” finishes.
Kali terminal triggering git checkout so the planted post-checkout hook runs a benign lab marker, proving RCE.
Step 2 (lab): git checkout runs the planted hook. the marker file appears. that is RCE on an ordinary git operation.

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.1 and 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 .git before calling the library, even after the upgrade. defense in depth is free here.
  • hunt repos for unexpected executables under .git/hooks/, especially post-checkout, post-commit, and pre-commit that you did not install.
  • review application code for create_submodule / Submodule.add calls 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.