Remote Code Execution (RCE)
Description
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.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
GitPython is vulnerable to remote code execution via unsanitized remote URLs during clone operations.
Root
Cause
CVE-2022-24439 is a critical vulnerability in the GitPython library that stems from insufficient validation of user-supplied remote URLs passed to the Repo.clone() and Repo.clone_from() methods. The library invokes the external git clone command without properly sanitizing the URL argument, allowing an attacker to inject arbitrary command-line arguments or use unsafe protocol schemes such as ext::. This enables the execution of arbitrary commands on the host system [1][4].
Exploitation
An attacker can exploit this flaw by providing a crafted remote URL to an application that uses GitPython's clone functionality. The malicious URL, when passed unmodified to git clone, can include protocol options like --config or use the ext:: remote helper to execute shell commands. The vulnerability requires no authentication beyond the user context of the calling process, and the attacker only needs to control the URL input (e.g., through user-provided repository links) [1][3].
Impact
Successful exploitation results in Remote Code Execution (RCE) with the privileges of the user running the GitPython-based application. An attacker can execute arbitrary OS commands, potentially leading to full system compromise, data theft, or lateral movement within a network. The vulnerability is classified with a CVSS score of 9.8 (Critical) due to its low attack complexity and no required privileges [1].
Mitigation
The vulnerability was patched in GitPython release 3.1.30 by introducing the unsafe_protocols parameter and blocking dangerous protocol options by default. The fix adds validation to reject ext:: URLs and options like --upload-pack or --config protocol.* unless explicitly opted in by the application [3]. Users should upgrade to GitPython ≥3.1.30 immediately. As of the advisory, no workarounds are available for earlier versions [2][4]. The project is in maintenance mode, so further security patches depend on community contributions [2].
- NVD - CVE-2022-24439
- GitHub - gitpython-developers/GitPython: GitPython is a python library used to interact with Git repositories.
- Forbid unsafe protocol URLs in Repo.clone{,_from}() · gitpython-developers/GitPython@2625ed9
- advisory-database/vulns/gitpython/PYSEC-2022-42992.yaml at main · pypa/advisory-database
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.
| Package | Affected versions | Patched versions |
|---|---|---|
GitPythonPyPI | < 3.1.30 | 3.1.30 |
Affected products
2- gitpython/gitpythondescription
Patches
12625ed9fc074Forbid unsafe protocol URLs in Repo.clone{,_from}()
3 files changed · +70 −1
git/exc.py+4 −0 modified@@ -37,6 +37,10 @@ class NoSuchPathError(GitError, OSError): """Thrown if a path could not be access by the system.""" +class UnsafeOptionsUsedError(GitError): + """Thrown if unsafe protocols or options are passed without overridding.""" + + class CommandError(GitError): """Base class for exceptions thrown at every stage of `Popen()` execution.
git/repo/base.py+30 −1 modified@@ -21,7 +21,12 @@ ) from git.config import GitConfigParser from git.db import GitCmdObjectDB -from git.exc import InvalidGitRepositoryError, NoSuchPathError, GitCommandError +from git.exc import ( + GitCommandError, + InvalidGitRepositoryError, + NoSuchPathError, + UnsafeOptionsUsedError, +) from git.index import IndexFile from git.objects import Submodule, RootModule, Commit from git.refs import HEAD, Head, Reference, TagReference @@ -128,6 +133,7 @@ class Repo(object): re_envvars = re.compile(r"(\$(\{\s?)?[a-zA-Z_]\w*(\}\s?)?|%\s?[a-zA-Z_]\w*\s?%)") re_author_committer_start = re.compile(r"^(author|committer)") re_tab_full_line = re.compile(r"^\t(.*)$") + re_config_protocol_option = re.compile(r"-[-]?c(|onfig)\s+protocol\.", re.I) # invariants # represents the configuration level of a configuration file @@ -1215,11 +1221,27 @@ def _clone( # END handle remote repo return repo + @classmethod + def unsafe_options( + cls, + url: str, + multi_options: Optional[List[str]] = None, + ) -> bool: + if "ext::" in url: + return True + if multi_options is not None: + if any(["--upload-pack" in m for m in multi_options]): + return True + if any([re.match(cls.re_config_protocol_option, m) for m in multi_options]): + return True + return False + def clone( self, path: PathLike, progress: Optional[Callable] = None, multi_options: Optional[List[str]] = None, + unsafe_protocols: bool = False, **kwargs: Any, ) -> "Repo": """Create a clone from this repository. @@ -1230,12 +1252,15 @@ def clone( option per list item which is passed exactly as specified to clone. For example ['--config core.filemode=false', '--config core.ignorecase', '--recurse-submodule=repo1_path', '--recurse-submodule=repo2_path'] + :param unsafe_protocols: Allow unsafe protocols to be used, like ext :param kwargs: * odbt = ObjectDatabase Type, allowing to determine the object database implementation used by the returned Repo instance * All remaining keyword arguments are given to the git-clone command :return: ``git.Repo`` (the newly cloned repo)""" + if not unsafe_protocols and self.unsafe_options(path, multi_options): + raise UnsafeOptionsUsedError(f"{path} requires unsafe_protocols flag") return self._clone( self.git, self.common_dir, @@ -1254,6 +1279,7 @@ def clone_from( progress: Optional[Callable] = None, env: Optional[Mapping[str, str]] = None, multi_options: Optional[List[str]] = None, + unsafe_protocols: bool = False, **kwargs: Any, ) -> "Repo": """Create a clone from the given URL @@ -1268,11 +1294,14 @@ def clone_from( If you want to unset some variable, consider providing empty string as its value. :param multi_options: See ``clone`` method + :param unsafe_protocols: Allow unsafe protocols to be used, like ext :param kwargs: see the ``clone`` method :return: Repo instance pointing to the cloned directory""" git = cls.GitCommandWrapperType(os.getcwd()) if env is not None: git.update_environment(**env) + if not unsafe_protocols and cls.unsafe_options(url, multi_options): + raise UnsafeOptionsUsedError(f"{url} requires unsafe_protocols flag") return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs) def archive(
test/test_repo.py+36 −0 modified@@ -13,6 +13,7 @@ import pickle import sys import tempfile +import uuid from unittest import mock, skipIf, SkipTest import pytest @@ -37,6 +38,7 @@ ) from git.exc import ( BadObject, + UnsafeOptionsUsedError, ) from git.repo.fun import touch from test.lib import TestBase, with_rw_repo, fixture @@ -263,6 +265,40 @@ def test_leaking_password_in_clone_logs(self, rw_dir): to_path=rw_dir, ) + def test_unsafe_options(self): + self.assertFalse(Repo.unsafe_options("github.com/deploy/deploy")) + + def test_unsafe_options_ext_url(self): + self.assertTrue(Repo.unsafe_options("ext::ssh")) + + def test_unsafe_options_multi_options_upload_pack(self): + self.assertTrue(Repo.unsafe_options("", ["--upload-pack='touch foo'"])) + + def test_unsafe_options_multi_options_config_user(self): + self.assertFalse(Repo.unsafe_options("", ["--config user"])) + + def test_unsafe_options_multi_options_config_protocol(self): + self.assertTrue(Repo.unsafe_options("", ["--config protocol.foo"])) + + def test_clone_from_forbids_helper_urls_by_default(self): + with self.assertRaises(UnsafeOptionsUsedError): + Repo.clone_from("ext::sh -c touch% /tmp/foo", "tmp") + + @with_rw_repo("HEAD") + def test_clone_from_allow_unsafe(self, repo): + bad_filename = pathlib.Path(f'{tempfile.gettempdir()}/{uuid.uuid4()}') + bad_url = f'ext::sh -c touch% {bad_filename}' + try: + repo.clone_from( + bad_url, 'tmp', + multi_options=["-c protocol.ext.allow=always"], + unsafe_protocols=True + ) + except GitCommandError: + pass + self.assertTrue(bad_filename.is_file()) + bad_filename.unlink() + @with_rw_repo("HEAD") def test_max_chunk_size(self, repo): class TestOutputStream(TestBase):
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
24- github.com/advisories/GHSA-hcpj-qp55-gfphghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/AV5DV7GBLMOZT7U3Q4TDOJO5R6G3V6GH/mitrevendor-advisory
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/IKMVYKLWX62UEYKAN64RUZMOIAMZM5JN/mitrevendor-advisory
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/PF6AXUTC5BO7L2SBJMCVKJSPKWY52I5R/mitrevendor-advisory
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/SJHN3QUXPJIMM6SULIR3PR34UFWRAE7X/mitrevendor-advisory
- nvd.nist.gov/vuln/detail/CVE-2022-24439ghsaADVISORY
- security.gentoo.org/glsa/202311-01ghsavendor-advisoryWEB
- github.com/gitpython-developers/GitPython/blob/bec61576ae75803bc4e60d8de7a629c194313d1c/git/repo/base.pyghsaWEB
- github.com/gitpython-developers/GitPython/blob/bec61576ae75803bc4e60d8de7a629c194313d1c/git/repo/base.py%23L1249ghsaWEB
- github.com/gitpython-developers/GitPython/commit/2625ed9fc074091c531c27ffcba7902771130261ghsaWEB
- github.com/gitpython-developers/GitPython/issues/1515ghsaWEB
- github.com/gitpython-developers/GitPython/releases/tag/3.1.30ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/gitpython/PYSEC-2022-42992.yamlghsaWEB
- lists.debian.org/debian-lts-announce/2023/07/msg00024.htmlghsamailing-listWEB
- lists.debian.org/debian-lts-announce/2024/10/msg00030.htmlghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/AV5DV7GBLMOZT7U3Q4TDOJO5R6G3V6GHghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/IKMVYKLWX62UEYKAN64RUZMOIAMZM5JNghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/PF6AXUTC5BO7L2SBJMCVKJSPKWY52I5RghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/SJHN3QUXPJIMM6SULIR3PR34UFWRAE7XghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/AV5DV7GBLMOZT7U3Q4TDOJO5R6G3V6GHghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/IKMVYKLWX62UEYKAN64RUZMOIAMZM5JNghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/PF6AXUTC5BO7L2SBJMCVKJSPKWY52I5RghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/SJHN3QUXPJIMM6SULIR3PR34UFWRAE7XghsaWEB
- security.snyk.io/vuln/SNYK-PYTHON-GITPYTHON-3113858ghsaWEB
News mentions
0No linked articles in our index yet.