Skip to content

Commit 2ab0516

Browse files
committed
Reject abbreviated forms of unsafe git options
1 parent b920dcd commit 2ab0516

2 files changed

Lines changed: 27 additions & 3 deletions

File tree

git/cmd.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -967,11 +967,20 @@ def check_unsafe_options(cls, options: List[str], unsafe_options: List[str]) ->
967967
arbitrary commands. These are blocked by default.
968968
"""
969969
# Options can be of the form `foo`, `--foo`, `--foo bar`, or `--foo=bar`.
970+
# Git accepts any unambiguous prefix of a long option, so an abbreviated
971+
# spelling such as `--upl` for `--upload-pack` must be rejected too. An
972+
# option is unsafe if its canonical name is a prefix of any blocked
973+
# option's canonical name.
970974
canonical_unsafe_options = {cls._canonicalize_option_name(option): option for option in unsafe_options}
971975
for option in options:
972-
unsafe_option = canonical_unsafe_options.get(cls._canonicalize_option_name(option))
973-
if unsafe_option is not None:
974-
raise UnsafeOptionError(f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it.")
976+
candidate = cls._canonicalize_option_name(option)
977+
if not candidate:
978+
continue
979+
for canonical, unsafe_option in canonical_unsafe_options.items():
980+
if canonical.startswith(candidate):
981+
raise UnsafeOptionError(
982+
f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it."
983+
)
975984

976985
AutoInterrupt: TypeAlias = _AutoInterrupt
977986

test/test_clone.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,21 @@ def test_clone_unsafe_options(self, rw_repo):
138138
rw_repo.clone(tmp_dir, **unsafe_option)
139139
assert not tmp_file.exists()
140140

141+
@with_rw_repo("HEAD")
142+
def test_clone_unsafe_options_abbreviated(self, rw_repo):
143+
with tempfile.TemporaryDirectory() as tdir:
144+
tmp_dir = pathlib.Path(tdir)
145+
tmp_file = tmp_dir / "pwn"
146+
unsafe_options = [
147+
f"--upl='touch {tmp_file}'",
148+
f"--upload-pac='touch {tmp_file}'",
149+
"--conf=protocol.ext.allow=always",
150+
]
151+
for unsafe_option in unsafe_options:
152+
with self.assertRaises(UnsafeOptionError):
153+
rw_repo.clone(tmp_dir, multi_options=[unsafe_option])
154+
assert not tmp_file.exists()
155+
141156
@with_rw_repo("HEAD")
142157
def test_clone_unsafe_options_are_checked_after_splitting_multi_options(self, rw_repo):
143158
with tempfile.TemporaryDirectory() as tdir:

0 commit comments

Comments
 (0)