summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorSebastian Thiel <sebastian.thiel@icloud.com>2022-12-29 08:09:28 +0100
committerGitHub <noreply@github.com>2022-12-29 08:09:28 +0100
commit678a8fe08dd466fcfe8676294b52887955138960 (patch)
treee0cb96633dc1c1cf8c362e2097140d75f60447f6 /test
parentae6a6e4b088a35c0fc7b17940722c8a515f7bee7 (diff)
parentf4f2658d5d308b3fb9162e50cd4c7b346e7a0a47 (diff)
downloadgitpython-678a8fe08dd466fcfe8676294b52887955138960.tar.gz
Merge pull request #1521 from stsewd/block-insecure-options
Block insecure options and protocols by default
Diffstat (limited to 'test')
-rw-r--r--test/test_git.py4
-rw-r--r--test/test_remote.py258
-rw-r--r--test/test_repo.py143
-rw-r--r--test/test_submodule.py152
4 files changed, 553 insertions, 4 deletions
diff --git a/test/test_git.py b/test/test_git.py
index 6ba833b4..e7d236de 100644
--- a/test/test_git.py
+++ b/test/test_git.py
@@ -39,12 +39,12 @@ class TestGit(TestBase):
self.assertEqual(git.call_args, ((["git", "version"],), {}))
def test_call_unpack_args_unicode(self):
- args = Git._Git__unpack_args("Unicode€™")
+ args = Git._unpack_args("Unicode€™")
mangled_value = "Unicode\u20ac\u2122"
self.assertEqual(args, [mangled_value])
def test_call_unpack_args(self):
- args = Git._Git__unpack_args(["git", "log", "--", "Unicode€™"])
+ args = Git._unpack_args(["git", "log", "--", "Unicode€™"])
mangled_value = "Unicode\u20ac\u2122"
self.assertEqual(args, ["git", "log", "--", mangled_value])
diff --git a/test/test_remote.py b/test/test_remote.py
index 7df64c20..3a47afab 100644
--- a/test/test_remote.py
+++ b/test/test_remote.py
@@ -23,6 +23,8 @@ from git import (
GitCommandError,
)
from git.cmd import Git
+from pathlib import Path
+from git.exc import UnsafeOptionError, UnsafeProtocolError
from test.lib import (
TestBase,
with_rw_repo,
@@ -690,6 +692,262 @@ class TestRemote(TestBase):
with self.assertRaisesRegex(GitCommandError, "src refspec __BAD_REF__ does not match any"):
rem.push("__BAD_REF__")
+ @with_rw_repo("HEAD")
+ def test_set_unsafe_url(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ remote = rw_repo.remote("origin")
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for url in urls:
+ with self.assertRaises(UnsafeProtocolError):
+ remote.set_url(url)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_set_unsafe_url_allowed(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ remote = rw_repo.remote("origin")
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for url in urls:
+ remote.set_url(url, allow_unsafe_protocols=True)
+ assert list(remote.urls)[-1] == url
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_add_unsafe_url(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ remote = rw_repo.remote("origin")
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for url in urls:
+ with self.assertRaises(UnsafeProtocolError):
+ remote.add_url(url)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_add_unsafe_url_allowed(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ remote = rw_repo.remote("origin")
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for url in urls:
+ remote.add_url(url, allow_unsafe_protocols=True)
+ assert list(remote.urls)[-1] == url
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_create_remote_unsafe_url(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for url in urls:
+ with self.assertRaises(UnsafeProtocolError):
+ Remote.create(rw_repo, "origin", url)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_create_remote_unsafe_url_allowed(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for i, url in enumerate(urls):
+ remote = Remote.create(rw_repo, f"origin{i}", url, allow_unsafe_protocols=True)
+ assert remote.url == url
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_fetch_unsafe_url(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ remote = rw_repo.remote("origin")
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for url in urls:
+ with self.assertRaises(UnsafeProtocolError):
+ remote.fetch(url)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_fetch_unsafe_url_allowed(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ remote = rw_repo.remote("origin")
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for url in urls:
+ # The URL will be allowed into the command, but the command will
+ # fail since we don't have that protocol enabled in the Git config file.
+ with self.assertRaises(GitCommandError):
+ remote.fetch(url, allow_unsafe_protocols=True)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_fetch_unsafe_options(self, rw_repo):
+ remote = rw_repo.remote("origin")
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [{"upload-pack": f"touch {tmp_file}"}]
+ for unsafe_option in unsafe_options:
+ with self.assertRaises(UnsafeOptionError):
+ remote.fetch(**unsafe_option)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_fetch_unsafe_options_allowed(self, rw_repo):
+ remote = rw_repo.remote("origin")
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [{"upload-pack": f"touch {tmp_file}"}]
+ for unsafe_option in unsafe_options:
+ # The options will be allowed, but the command will fail.
+ assert not tmp_file.exists()
+ with self.assertRaises(GitCommandError):
+ remote.fetch(**unsafe_option, allow_unsafe_options=True)
+ assert tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_pull_unsafe_url(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ remote = rw_repo.remote("origin")
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for url in urls:
+ with self.assertRaises(UnsafeProtocolError):
+ remote.pull(url)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_pull_unsafe_url_allowed(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ remote = rw_repo.remote("origin")
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for url in urls:
+ # The URL will be allowed into the command, but the command will
+ # fail since we don't have that protocol enabled in the Git config file.
+ with self.assertRaises(GitCommandError):
+ remote.pull(url, allow_unsafe_protocols=True)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_pull_unsafe_options(self, rw_repo):
+ remote = rw_repo.remote("origin")
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [{"upload-pack": f"touch {tmp_file}"}]
+ for unsafe_option in unsafe_options:
+ with self.assertRaises(UnsafeOptionError):
+ remote.pull(**unsafe_option)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_pull_unsafe_options_allowed(self, rw_repo):
+ remote = rw_repo.remote("origin")
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [{"upload-pack": f"touch {tmp_file}"}]
+ for unsafe_option in unsafe_options:
+ # The options will be allowed, but the command will fail.
+ assert not tmp_file.exists()
+ with self.assertRaises(GitCommandError):
+ remote.pull(**unsafe_option, allow_unsafe_options=True)
+ assert tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_push_unsafe_url(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ remote = rw_repo.remote("origin")
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for url in urls:
+ with self.assertRaises(UnsafeProtocolError):
+ remote.push(url)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_push_unsafe_url_allowed(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ remote = rw_repo.remote("origin")
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for url in urls:
+ # The URL will be allowed into the command, but the command will
+ # fail since we don't have that protocol enabled in the Git config file.
+ with self.assertRaises(GitCommandError):
+ remote.push(url, allow_unsafe_protocols=True)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_push_unsafe_options(self, rw_repo):
+ remote = rw_repo.remote("origin")
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [
+ {
+ "receive-pack": f"touch {tmp_file}",
+ "exec": f"touch {tmp_file}",
+ }
+ ]
+ for unsafe_option in unsafe_options:
+ assert not tmp_file.exists()
+ with self.assertRaises(UnsafeOptionError):
+ remote.push(**unsafe_option)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_push_unsafe_options_allowed(self, rw_repo):
+ remote = rw_repo.remote("origin")
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [
+ {
+ "receive-pack": f"touch {tmp_file}",
+ "exec": f"touch {tmp_file}",
+ }
+ ]
+ for unsafe_option in unsafe_options:
+ # The options will be allowed, but the command will fail.
+ assert not tmp_file.exists()
+ with self.assertRaises(GitCommandError):
+ remote.push(**unsafe_option, allow_unsafe_options=True)
+ assert tmp_file.exists()
+ tmp_file.unlink()
+
class TestTimeouts(TestBase):
@with_rw_repo("HEAD", bare=False)
diff --git a/test/test_repo.py b/test/test_repo.py
index 6382db7e..5874dbe6 100644
--- a/test/test_repo.py
+++ b/test/test_repo.py
@@ -37,6 +37,8 @@ from git import (
)
from git.exc import (
BadObject,
+ UnsafeOptionError,
+ UnsafeProtocolError,
)
from git.repo.fun import touch
from test.lib import TestBase, with_rw_repo, fixture
@@ -223,6 +225,7 @@ class TestRepo(TestBase):
"--config submodule.repo.update=checkout",
"--config filter.lfs.clean='git-lfs clean -- %f'",
],
+ allow_unsafe_options=True,
)
self.assertEqual(cloned.config_reader().get_value("submodule", "active"), "repo")
@@ -264,6 +267,146 @@ class TestRepo(TestBase):
)
@with_rw_repo("HEAD")
+ def test_clone_unsafe_options(self, rw_repo):
+ tmp_dir = pathlib.Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [
+ f"--upload-pack='touch {tmp_file}'",
+ f"-u 'touch {tmp_file}'",
+ "--config=protocol.ext.allow=always",
+ "-c protocol.ext.allow=always",
+ ]
+ for unsafe_option in unsafe_options:
+ with self.assertRaises(UnsafeOptionError):
+ rw_repo.clone(tmp_dir, multi_options=[unsafe_option])
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_clone_unsafe_options_allowed(self, rw_repo):
+ tmp_dir = pathlib.Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [
+ f"--upload-pack='touch {tmp_file}'",
+ f"-u 'touch {tmp_file}'",
+ ]
+ for i, unsafe_option in enumerate(unsafe_options):
+ destination = tmp_dir / str(i)
+ assert not tmp_file.exists()
+ # The options will be allowed, but the command will fail.
+ with self.assertRaises(GitCommandError):
+ rw_repo.clone(destination, multi_options=[unsafe_option], allow_unsafe_options=True)
+ assert tmp_file.exists()
+ tmp_file.unlink()
+
+ unsafe_options = [
+ "--config=protocol.ext.allow=always",
+ "-c protocol.ext.allow=always",
+ ]
+ for i, unsafe_option in enumerate(unsafe_options):
+ destination = tmp_dir / str(i)
+ assert not destination.exists()
+ rw_repo.clone(destination, multi_options=[unsafe_option], allow_unsafe_options=True)
+ assert destination.exists()
+
+ @with_rw_repo("HEAD")
+ def test_clone_safe_options(self, rw_repo):
+ tmp_dir = pathlib.Path(tempfile.mkdtemp())
+ options = [
+ "--depth=1",
+ "--single-branch",
+ "-q",
+ ]
+ for option in options:
+ destination = tmp_dir / option
+ assert not destination.exists()
+ rw_repo.clone(destination, multi_options=[option])
+ assert destination.exists()
+
+ @with_rw_repo("HEAD")
+ def test_clone_from_unsafe_options(self, rw_repo):
+ tmp_dir = pathlib.Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [
+ f"--upload-pack='touch {tmp_file}'",
+ f"-u 'touch {tmp_file}'",
+ "--config=protocol.ext.allow=always",
+ "-c protocol.ext.allow=always",
+ ]
+ for unsafe_option in unsafe_options:
+ with self.assertRaises(UnsafeOptionError):
+ Repo.clone_from(rw_repo.working_dir, tmp_dir, multi_options=[unsafe_option])
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_clone_from_unsafe_options_allowed(self, rw_repo):
+ tmp_dir = pathlib.Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [
+ f"--upload-pack='touch {tmp_file}'",
+ f"-u 'touch {tmp_file}'",
+ ]
+ for i, unsafe_option in enumerate(unsafe_options):
+ destination = tmp_dir / str(i)
+ assert not tmp_file.exists()
+ # The options will be allowed, but the command will fail.
+ with self.assertRaises(GitCommandError):
+ Repo.clone_from(
+ rw_repo.working_dir, destination, multi_options=[unsafe_option], allow_unsafe_options=True
+ )
+ assert tmp_file.exists()
+ tmp_file.unlink()
+
+ unsafe_options = [
+ "--config=protocol.ext.allow=always",
+ "-c protocol.ext.allow=always",
+ ]
+ for i, unsafe_option in enumerate(unsafe_options):
+ destination = tmp_dir / str(i)
+ assert not destination.exists()
+ Repo.clone_from(rw_repo.working_dir, destination, multi_options=[unsafe_option], allow_unsafe_options=True)
+ assert destination.exists()
+
+ @with_rw_repo("HEAD")
+ def test_clone_from_safe_options(self, rw_repo):
+ tmp_dir = pathlib.Path(tempfile.mkdtemp())
+ options = [
+ "--depth=1",
+ "--single-branch",
+ "-q",
+ ]
+ for option in options:
+ destination = tmp_dir / option
+ assert not destination.exists()
+ Repo.clone_from(rw_repo.common_dir, destination, multi_options=[option])
+ assert destination.exists()
+
+ def test_clone_from_unsafe_procol(self):
+ tmp_dir = pathlib.Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::17/foo",
+ ]
+ for url in urls:
+ with self.assertRaises(UnsafeProtocolError):
+ Repo.clone_from(url, tmp_dir)
+ assert not tmp_file.exists()
+
+ def test_clone_from_unsafe_procol_allowed(self):
+ tmp_dir = pathlib.Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ urls = [
+ "ext::sh -c touch% /tmp/pwn",
+ "fd::/foo",
+ ]
+ for url in urls:
+ # The URL will be allowed into the command, but the command will
+ # fail since we don't have that protocol enabled in the Git config file.
+ with self.assertRaises(GitCommandError):
+ Repo.clone_from(url, tmp_dir, allow_unsafe_protocols=True)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
def test_max_chunk_size(self, repo):
class TestOutputStream(TestBase):
def __init__(self, max_chunk_size):
diff --git a/test/test_submodule.py b/test/test_submodule.py
index fef6bda3..13878df2 100644
--- a/test/test_submodule.py
+++ b/test/test_submodule.py
@@ -3,6 +3,8 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import os
import shutil
+import tempfile
+from pathlib import Path
import sys
from unittest import skipIf
@@ -12,7 +14,13 @@ import git
from git.cmd import Git
from git.compat import is_win
from git.config import GitConfigParser, cp
-from git.exc import InvalidGitRepositoryError, RepositoryDirtyError
+from git.exc import (
+ GitCommandError,
+ InvalidGitRepositoryError,
+ RepositoryDirtyError,
+ UnsafeOptionError,
+ UnsafeProtocolError,
+)
from git.objects.submodule.base import Submodule
from git.objects.submodule.root import RootModule, RootUpdateProgress
from git.repo.fun import find_submodule_git_dir, touch
@@ -1026,7 +1034,7 @@ class TestSubmodule(TestBase):
)
# Act
- sm.update(init=True, clone_multi_options=["--config core.eol=true"])
+ sm.update(init=True, clone_multi_options=["--config core.eol=true"], allow_unsafe_options=True)
# Assert
sm_config = GitConfigParser(file_or_files=osp.join(parent.git_dir, "modules", sm_name, "config"))
@@ -1070,6 +1078,7 @@ class TestSubmodule(TestBase):
sm_name,
url=self._small_repo_url(),
clone_multi_options=["--config core.eol=true"],
+ allow_unsafe_options=True,
)
# Assert
@@ -1089,3 +1098,142 @@ class TestSubmodule(TestBase):
sm_config = GitConfigParser(file_or_files=osp.join(parent.git_dir, "modules", sm_name, "config"))
with self.assertRaises(cp.NoOptionError):
sm_config.get_value("core", "eol")
+
+ @with_rw_repo("HEAD")
+ def test_submodule_add_unsafe_url(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::/foo",
+ ]
+ for url in urls:
+ with self.assertRaises(UnsafeProtocolError):
+ Submodule.add(rw_repo, "new", "new", url)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_submodule_add_unsafe_url_allowed(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::/foo",
+ ]
+ for url in urls:
+ # The URL will be allowed into the command, but the command will
+ # fail since we don't have that protocol enabled in the Git config file.
+ with self.assertRaises(GitCommandError):
+ Submodule.add(rw_repo, "new", "new", url, allow_unsafe_protocols=True)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_submodule_add_unsafe_options(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [
+ f"--upload-pack='touch {tmp_file}'",
+ f"-u 'touch {tmp_file}'",
+ "--config=protocol.ext.allow=always",
+ "-c protocol.ext.allow=always",
+ ]
+ for unsafe_option in unsafe_options:
+ with self.assertRaises(UnsafeOptionError):
+ Submodule.add(rw_repo, "new", "new", str(tmp_dir), clone_multi_options=[unsafe_option])
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_submodule_add_unsafe_options_allowed(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [
+ f"--upload-pack='touch {tmp_file}'",
+ f"-u 'touch {tmp_file}'",
+ ]
+ for unsafe_option in unsafe_options:
+ # The options will be allowed, but the command will fail.
+ with self.assertRaises(GitCommandError):
+ Submodule.add(
+ rw_repo, "new", "new", str(tmp_dir), clone_multi_options=[unsafe_option], allow_unsafe_options=True
+ )
+ assert not tmp_file.exists()
+
+ unsafe_options = [
+ "--config=protocol.ext.allow=always",
+ "-c protocol.ext.allow=always",
+ ]
+ for unsafe_option in unsafe_options:
+ with self.assertRaises(GitCommandError):
+ Submodule.add(
+ rw_repo, "new", "new", str(tmp_dir), clone_multi_options=[unsafe_option], allow_unsafe_options=True
+ )
+
+ @with_rw_repo("HEAD")
+ def test_submodule_update_unsafe_url(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::/foo",
+ ]
+ for url in urls:
+ submodule = Submodule(rw_repo, b"\0" * 20, name="new", path="new", url=url)
+ with self.assertRaises(UnsafeProtocolError):
+ submodule.update()
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_submodule_update_unsafe_url_allowed(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ urls = [
+ f"ext::sh -c touch% {tmp_file}",
+ "fd::/foo",
+ ]
+ for url in urls:
+ submodule = Submodule(rw_repo, b"\0" * 20, name="new", path="new", url=url)
+ # The URL will be allowed into the command, but the command will
+ # fail since we don't have that protocol enabled in the Git config file.
+ with self.assertRaises(GitCommandError):
+ submodule.update(allow_unsafe_protocols=True)
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_submodule_update_unsafe_options(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [
+ f"--upload-pack='touch {tmp_file}'",
+ f"-u 'touch {tmp_file}'",
+ "--config=protocol.ext.allow=always",
+ "-c protocol.ext.allow=always",
+ ]
+ submodule = Submodule(rw_repo, b"\0" * 20, name="new", path="new", url=str(tmp_dir))
+ for unsafe_option in unsafe_options:
+ with self.assertRaises(UnsafeOptionError):
+ submodule.update(clone_multi_options=[unsafe_option])
+ assert not tmp_file.exists()
+
+ @with_rw_repo("HEAD")
+ def test_submodule_update_unsafe_options_allowed(self, rw_repo):
+ tmp_dir = Path(tempfile.mkdtemp())
+ tmp_file = tmp_dir / "pwn"
+ unsafe_options = [
+ f"--upload-pack='touch {tmp_file}'",
+ f"-u 'touch {tmp_file}'",
+ ]
+ submodule = Submodule(rw_repo, b"\0" * 20, name="new", path="new", url=str(tmp_dir))
+ for unsafe_option in unsafe_options:
+ # The options will be allowed, but the command will fail.
+ with self.assertRaises(GitCommandError):
+ submodule.update(clone_multi_options=[unsafe_option], allow_unsafe_options=True)
+ assert not tmp_file.exists()
+
+ unsafe_options = [
+ "--config=protocol.ext.allow=always",
+ "-c protocol.ext.allow=always",
+ ]
+ submodule = Submodule(rw_repo, b"\0" * 20, name="new", path="new", url=str(tmp_dir))
+ for unsafe_option in unsafe_options:
+ with self.assertRaises(GitCommandError):
+ submodule.update(clone_multi_options=[unsafe_option], allow_unsafe_options=True)