VYPR
Moderate severityNVD Advisory· Published Aug 30, 2023· Updated Nov 3, 2025

GitPython blind local file inclusion

CVE-2023-41040

Description

GitPython is a python library used to interact with Git repositories. 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. 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. This vulnerability cannot be used to read the contents of files but could in theory be used to trigger a denial of service for the program. This issue has been addressed in version 3.1.37.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

GitPython 3.1.36 and earlier allow a path traversal in reference resolution, letting an attacker force reads of arbitrary files outside .git for blind file existence or denial of service.

Vulnerability

Overview

CVE-2023-41040 is a path traversal vulnerability in GitPython, a Python library for interacting with Git repositories. The flaw resides in the symbolic reference resolution code located in git/refs/symbolic.py at lines 174-175 [1][4]. When resolving user-supplied reference names, the library joins the repository base directory with the provided string without validating whether the resulting path stays within the .git directory. This allows an attacker to craft reference names containing ../ sequences to read arbitrary files on the system.

Exploitation

Conditions

The vulnerability is exploitable from several high-level API methods, including commit(), tree(), and index.diff() [4]. An attacker only needs to supply a crafted revision string, such as ../README.md or ../../../../../../../../../dev/random, to a GitPython operation. The attack does not require authentication beyond the ability to interact with the library, but the program must be processing user-controlled revision names. The vulnerability does not expose file contents in error messages or return values, making it a blind file inclusion issue [1][4].

Impact

While GitPython cannot disclose the contents of read files to the attacker, the flaw can be used for two purposes: (1) checking whether a given file exists on the system by observing differences in behavior or error messages, and (2) causing a denial of service (DoS) by instructing GitPython to read a large or infinite stream like /dev/random, which may hang or crash the application [1][4]. The impact is limited to programs that accept untrusted revision names; however, the DoS vector is especially concerning in server contexts.

Mitigation

GitPython version 3.1.37, released on September 21, 2023, includes a proper fix for this vulnerability [3]. The fix adds validation to ensure that resolved reference paths remain within the repository directory. Users are strongly advised to upgrade to 3.1.37 or later. No workarounds are documented, but applications should sanitize any user-supplied revision names to prevent path traversal characters.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
GitPythonPyPI
< 3.1.373.1.37

Affected products

6

Patches

2
e98f57b81f79

Merge pull request #1672 from trail-of-forks/robust-refname-checks

https://github.com/gitpython-developers/GitPythonSebastian ThielSep 22, 2023via ghsa
2 files changed · +84 2
  • git/refs/symbolic.py+48 2 modified
    @@ -161,15 +161,61 @@ def dereference_recursive(cls, repo: "Repo", ref_path: Union[PathLike, None]) ->
                     return hexsha
             # END recursive dereferencing
     
    +    @staticmethod
    +    def _check_ref_name_valid(ref_path: PathLike) -> None:
    +        # Based on the rules described in https://git-scm.com/docs/git-check-ref-format/#_description
    +        previous: Union[str, None] = None
    +        one_before_previous: Union[str, None] = None
    +        for c in str(ref_path):
    +            if c in " ~^:?*[\\":
    +                raise ValueError(
    +                    f"Invalid reference '{ref_path}': references cannot contain spaces, tildes (~), carets (^),"
    +                    f" colons (:), question marks (?), asterisks (*), open brackets ([) or backslashes (\\)"
    +                )
    +            elif c == ".":
    +                if previous is None or previous == "/":
    +                    raise ValueError(
    +                        f"Invalid reference '{ref_path}': references cannot start with a period (.) or contain '/.'"
    +                    )
    +                elif previous == ".":
    +                    raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '..'")
    +            elif c == "/":
    +                if previous == "/":
    +                    raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '//'")
    +                elif previous is None:
    +                    raise ValueError(
    +                        f"Invalid reference '{ref_path}': references cannot start with forward slashes '/'"
    +                    )
    +            elif c == "{" and previous == "@":
    +                raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '@{{'")
    +            elif ord(c) < 32 or ord(c) == 127:
    +                raise ValueError(f"Invalid reference '{ref_path}': references cannot contain ASCII control characters")
    +
    +            one_before_previous = previous
    +            previous = c
    +
    +        if previous == ".":
    +            raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a period (.)")
    +        elif previous == "/":
    +            raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a forward slash (/)")
    +        elif previous == "@" and one_before_previous is None:
    +            raise ValueError(f"Invalid reference '{ref_path}': references cannot be '@'")
    +        elif any([component.endswith(".lock") for component in str(ref_path).split("/")]):
    +            raise ValueError(
    +                f"Invalid reference '{ref_path}': references cannot have slash-separated components that end with"
    +                f" '.lock'"
    +            )
    +
         @classmethod
         def _get_ref_info_helper(
             cls, repo: "Repo", ref_path: Union[PathLike, None]
         ) -> Union[Tuple[str, None], Tuple[None, str]]:
             """Return: (str(sha), str(target_ref_path)) if available, the sha the file at
             rela_path points to, or None. target_ref_path is the reference we
             point to, or None"""
    -        if ".." in str(ref_path):
    -            raise ValueError(f"Invalid reference '{ref_path}'")
    +        if ref_path:
    +            cls._check_ref_name_valid(ref_path)
    +
             tokens: Union[None, List[str], Tuple[str, str]] = None
             repodir = _git_dir(repo, ref_path)
             try:
    
  • test/test_refs.py+36 0 modified
    @@ -631,3 +631,39 @@ def test_refs_outside_repo(self):
                 ref_file.flush()
                 ref_file_name = Path(ref_file.name).name
                 self.assertRaises(BadName, self.rorepo.commit, f"../../{ref_file_name}")
    +
    +    def test_validity_ref_names(self):
    +        check_ref = SymbolicReference._check_ref_name_valid
    +        # Based on the rules specified in https://git-scm.com/docs/git-check-ref-format/#_description
    +        # Rule 1
    +        self.assertRaises(ValueError, check_ref, ".ref/begins/with/dot")
    +        self.assertRaises(ValueError, check_ref, "ref/component/.begins/with/dot")
    +        self.assertRaises(ValueError, check_ref, "ref/ends/with/a.lock")
    +        self.assertRaises(ValueError, check_ref, "ref/component/ends.lock/with/period_lock")
    +        # Rule 2
    +        check_ref("valid_one_level_refname")
    +        # Rule 3
    +        self.assertRaises(ValueError, check_ref, "ref/contains/../double/period")
    +        # Rule 4
    +        for c in " ~^:":
    +            self.assertRaises(ValueError, check_ref, f"ref/contains/invalid{c}/character")
    +        for code in range(0, 32):
    +            self.assertRaises(ValueError, check_ref, f"ref/contains/invalid{chr(code)}/ASCII/control_character")
    +        self.assertRaises(ValueError, check_ref, f"ref/contains/invalid{chr(127)}/ASCII/control_character")
    +        # Rule 5
    +        for c in "*?[":
    +            self.assertRaises(ValueError, check_ref, f"ref/contains/invalid{c}/character")
    +        # Rule 6
    +        self.assertRaises(ValueError, check_ref, "/ref/begins/with/slash")
    +        self.assertRaises(ValueError, check_ref, "ref/ends/with/slash/")
    +        self.assertRaises(ValueError, check_ref, "ref/contains//double/slash/")
    +        # Rule 7
    +        self.assertRaises(ValueError, check_ref, "ref/ends/with/dot.")
    +        # Rule 8
    +        self.assertRaises(ValueError, check_ref, "ref/contains@{/at_brace")
    +        # Rule 9
    +        self.assertRaises(ValueError, check_ref, "@")
    +        # Rule 10
    +        self.assertRaises(ValueError, check_ref, "ref/contain\\s/backslash")
    +        # Valid reference name should not raise
    +        check_ref("valid/ref/name")
    
74e55ee45448

Merge pull request #1644 from trail-of-forks/fix-cve-2023-41040

https://github.com/gitpython-developers/GitPythonSebastian ThielSep 7, 2023via ghsa
2 files changed · +17 0
  • git/refs/symbolic.py+2 0 modified
    @@ -168,6 +168,8 @@ def _get_ref_info_helper(
             """Return: (str(sha), str(target_ref_path)) if available, the sha the file at
             rela_path points to, or None. target_ref_path is the reference we
             point to, or None"""
    +        if ".." in str(ref_path):
    +            raise ValueError(f"Invalid reference '{ref_path}'")
             tokens: Union[None, List[str], Tuple[str, str]] = None
             repodir = _git_dir(repo, ref_path)
             try:
    
  • test/test_refs.py+15 0 modified
    @@ -5,6 +5,7 @@
     # the BSD License: http://www.opensource.org/licenses/bsd-license.php
     
     from itertools import chain
    +from pathlib import Path
     
     from git import (
         Reference,
    @@ -20,9 +21,11 @@
     from git.objects.tag import TagObject
     from test.lib import TestBase, with_rw_repo
     from git.util import Actor
    +from gitdb.exc import BadName
     
     import git.refs as refs
     import os.path as osp
    +import tempfile
     
     
     class TestRefs(TestBase):
    @@ -616,3 +619,15 @@ def test_dereference_recursive(self):
     
         def test_reflog(self):
             assert isinstance(self.rorepo.heads.master.log(), RefLog)
    +
    +    def test_refs_outside_repo(self):
    +        # Create a file containing a valid reference outside the repository. Attempting
    +        # to access it should raise an exception, due to it containing a parent directory
    +        # reference ('..'). This tests for CVE-2023-41040.
    +        git_dir = Path(self.rorepo.git_dir)
    +        repo_parent_dir = git_dir.parent.parent
    +        with tempfile.NamedTemporaryFile(dir=repo_parent_dir) as ref_file:
    +            ref_file.write(b"91b464cd624fe22fbf54ea22b85a7e5cca507cfe")
    +            ref_file.flush()
    +            ref_file_name = Path(ref_file.name).name
    +            self.assertRaises(BadName, self.rorepo.commit, f"../../{ref_file_name}")
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

11

News mentions

0

No linked articles in our index yet.