Block unsafe checkout-index and tag file options · gitpython-developers/GitPython@3af0c25 · GitHub
Skip to content

Commit 3af0c25

Browse files
codexByron
andcommitted
Block unsafe checkout-index and tag file options
GHSA-3f7w-8rr8-f37f reports that caller-controlled options forwarded by IndexFile.checkout() and TagReference.create() can make Git write to or read from arbitrary filesystem paths. Add regression coverage proving the unsafe options are rejected by default while preserving the explicit allow_unsafe_options escape hatch. Reuse GitPython's existing option candidate normalization and unsafe-option guard at both public API boundaries. Git baseline: a23bace963d508bd96983cc637131392d3face18. Documentation/git-checkout-index.adoc defines --prefix as prepending an output path, and Documentation/git-tag.adoc defines -F/--file as reading a tag message from a file. Co-authored-by: Sebastian Thiel <sebastian.thiel@icloud.com>
1 parent 07e8055 commit 3af0c25

4 files changed

Lines changed: 48 additions & 0 deletions

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

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

0 commit comments

Comments
 (0)