Merge pull request #2193 from gitpython-developers/more-unsafe-options · gitpython-developers/GitPython@d1a631d · GitHub
Skip to content

Commit d1a631d

Browse files
authored
Merge pull request #2193 from gitpython-developers/more-unsafe-options
Block unsafe Git file and URL options
2 parents ab33e33 + 52199b3 commit d1a631d

7 files changed

Lines changed: 66 additions & 1 deletion

File tree

git/index/base.py

Lines changed: 12 additions & 0 deletions

git/refs/tag.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from typing import Any, TYPE_CHECKING, Type, Union
1818

19+
from git.cmd import Git
1920
from git.types import AnyGitObject, PathLike
2021

2122
if TYPE_CHECKING:
@@ -42,6 +43,8 @@ class TagReference(Reference):
4243

4344
__slots__ = ()
4445

46+
unsafe_git_tag_options = ["--file", "-F"]
47+
4548
_common_default = "tags"
4649
_common_path_default = Reference._common_path_default + "/" + _common_default
4750

@@ -92,6 +95,7 @@ def create(
9295
reference: Union[str, "SymbolicReference"] = "HEAD",
9396
logmsg: Union[str, None] = None,
9497
force: bool = False,
98+
allow_unsafe_options: bool = False,
9599
**kwargs: Any,
96100
) -> "TagReference":
97101
"""Create a new tag reference.
@@ -121,12 +125,21 @@ def create(
121125
:param force:
122126
If ``True``, force creation of a tag even though that tag already exists.
123127
128+
:param allow_unsafe_options:
129+
Allow unsafe options, such as ``--file``.
130+
124131
:param kwargs:
125132
Additional keyword arguments to be passed to :manpage:`git-tag(1)`.
126133
127134
:return:
128135
A new :class:`TagReference`.
129136
"""
137+
if not allow_unsafe_options:
138+
Git.check_unsafe_options(
139+
options=Git._option_candidates([], kwargs),
140+
unsafe_options=cls.unsafe_git_tag_options,
141+
)
142+
130143
if "ref" in kwargs and kwargs["ref"]:
131144
reference = kwargs["ref"]
132145

git/repo/base.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,10 @@ class Repo:
151151
"-c",
152152
# Can install hooks that execute during clone:
153153
"--template",
154+
# Fetches from an additional caller-controlled URI:
155+
"--bundle-uri",
154156
]
155-
"""Options to :manpage:`git-clone(1)` that allow arbitrary commands to be executed.
157+
"""Options to :manpage:`git-clone(1)` that permit unsafe command execution or I/O.
156158
157159
The ``--upload-pack``/``-u`` option allows users to execute arbitrary commands
158160
directly:
@@ -164,6 +166,11 @@ class Repo:
164166
165167
The ``--template`` option can install hooks that execute during clone:
166168
https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---templatetemplate-directory
169+
170+
The ``--bundle-uri`` option fetches from an additional URI before fetching from the
171+
clone URL. An untrusted value can therefore make Git access local files or
172+
unintended network resources:
173+
https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---bundle-uriuri
167174
"""
168175

169176
unsafe_git_archive_options = [
@@ -172,6 +179,10 @@ class Repo:
172179
# Writes output to a caller-controlled filesystem path.
173180
"--output",
174181
"-o",
182+
# Reads from a caller-controlled filesystem path:
183+
"--add-file",
184+
# Injects a caller-controlled path and contents:
185+
"--add-virtual-file",
175186
]
176187

177188
unsafe_git_revision_options = [

test/test_clone.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def test_clone_unsafe_options(self, rw_repo):
132132
"-cprotocol.ext.allow=always",
133133
"-vcprotocol.ext.allow=always",
134134
f"--template={tmp_dir}",
135+
f"--bundle-uri=file://{tmp_dir}",
135136
]
136137
for unsafe_option in unsafe_options:
137138
with self.assertRaises(UnsafeOptionError):
@@ -147,6 +148,7 @@ def test_clone_unsafe_options(self, rw_repo):
147148
{"conf": "protocol.ext.allow=always"},
148149
{"c": "protocol.ext.allow=always"},
149150
{"template": tmp_dir},
151+
{"bundle_uri": f"file://{tmp_dir}"},
150152
]
151153
for unsafe_option in unsafe_options:
152154
with self.assertRaises(UnsafeOptionError):

test/test_index.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
HookExecutionError,
3232
InvalidGitRepositoryError,
3333
UnmergedEntriesError,
34+
UnsafeOptionError,
3435
)
3536
from git.index.fun import hook_path, run_commit_hook
3637
from git.index.typ import BaseIndexEntry, IndexEntry
@@ -202,6 +203,15 @@ def _make_hook(git_dir, name, content, make_exec=True):
202203

203204
@ddt.ddt
204205
class TestIndex(TestBase):
206+
@with_rw_repo("HEAD")
207+
def test_checkout_rejects_unsafe_prefix(self, rw_repo):
208+
with tempfile.TemporaryDirectory() as target:
209+
with self.assertRaises(UnsafeOptionError):
210+
rw_repo.index.checkout(prefix=f"{target}/")
211+
212+
rw_repo.index.checkout(prefix=f"{target}/", allow_unsafe_options=True)
213+
self.assertTrue(osp.isfile(osp.join(target, "CHANGES")))
214+
205215
def __init__(self, *args):
206216
super().__init__(*args)
207217
self._reset_progress()

test/test_refs.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
SymbolicReference,
2424
TagReference,
2525
)
26+
from git.exc import UnsafeOptionError
2627
from git.objects.tag import TagObject
2728
import git.refs as refs
2829
from git.util import Actor
@@ -60,6 +61,18 @@ def test_from_path(self):
6061
# Check remoteness
6162
assert Reference(self.rorepo, "refs/remotes/origin").is_remote()
6263

64+
@with_rw_repo("HEAD")
65+
def test_tag_create_rejects_unsafe_file_options(self, rw_repo):
66+
with tempfile.NamedTemporaryFile("w", encoding="utf-8") as message:
67+
message.write("private tag message")
68+
message.flush()
69+
for index, option in enumerate(({"F": message.name}, {"file": message.name})):
70+
with self.assertRaises(UnsafeOptionError):
71+
TagReference.create(rw_repo, f"unsafe-{index}", **option)
72+
73+
tag = TagReference.create(rw_repo, "allowed-file", F=message.name, allow_unsafe_options=True)
74+
self.assertEqual(tag.tag.message, "private tag message")
75+
6376
def test_from_pathlike(self):
6477
# Should be able to create any reference directly.
6578
for ref_type in (Reference, Head, TagReference, RemoteReference):

test/test_repo.py

Lines changed: 4 additions & 0 deletions

0 commit comments

Comments
 (0)