Skip to content

Commit ea52206

Browse files
committed
Forbid unsafe protocol URLs in Repo.clone_from()
Since the URL is passed directly to git clone, and the remote-ext helper will happily execute shell commands, so by default disallow URLs that contain a "::" unless a new unsafe_protocols kwarg is passed. (CVE-2022-24439) Fixes gitpython-developers#1515
1 parent 17ff263 commit ea52206

File tree

2 files changed

+24
-0
lines changed

2 files changed

+24
-0
lines changed

git/repo/base.py

+4
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,7 @@ def clone_from(
12531253
progress: Optional[Callable] = None,
12541254
env: Optional[Mapping[str, str]] = None,
12551255
multi_options: Optional[List[str]] = None,
1256+
unsafe_protocols: bool = False,
12561257
**kwargs: Any,
12571258
) -> "Repo":
12581259
"""Create a clone from the given URL
@@ -1268,10 +1269,13 @@ def clone_from(
12681269
as its value.
12691270
:param multi_options: See ``clone`` method
12701271
:param kwargs: see the ``clone`` method
1272+
:param unsafe_protocols: Allow unsafe protocols to be used, like ext::
12711273
:return: Repo instance pointing to the cloned directory"""
12721274
git = cls.GitCommandWrapperType(os.getcwd())
12731275
if env is not None:
12741276
git.update_environment(**env)
1277+
if not unsafe_protocols and "::" in url:
1278+
raise ValueError(f"{url} requires unsafe_protocols flag")
12751279
return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
12761280

12771281
def archive(

test/test_repo.py

+20
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import pickle
1414
import sys
1515
import tempfile
16+
import uuid
1617
from unittest import mock, skipIf, SkipTest
1718

1819
import pytest
@@ -263,6 +264,25 @@ def test_leaking_password_in_clone_logs(self, rw_dir):
263264
to_path=rw_dir,
264265
)
265266

267+
def test_clone_from_forbids_helper_urls_by_default(self):
268+
with self.assertRaises(ValueError):
269+
Repo.clone_from("ext::sh -c touch% /tmp/foo", "tmp")
270+
271+
@with_rw_repo("HEAD")
272+
def test_clone_from_allow_unsafe(self, repo):
273+
bad_filename = pathlib.Path(f'{tempfile.gettempdir()}/{uuid.uuid4()}')
274+
bad_url = f'ext::sh -c touch% {bad_filename}'
275+
try:
276+
repo.clone_from(
277+
bad_url, 'tmp',
278+
multi_options=["-c protocol.ext.allow=always"],
279+
unsafe_protocols=True
280+
)
281+
except GitCommandError:
282+
pass
283+
self.assertTrue(bad_filename.is_file())
284+
bad_filename.unlink()
285+
266286
@with_rw_repo("HEAD")
267287
def test_max_chunk_size(self, repo):
268288
class TestOutputStream(TestBase):

0 commit comments

Comments
 (0)