Skip to content

Commit e1d07cc

Browse files
codexByron
authored andcommitted
Validate submodule names before creating module paths
Submodule names read from .gitmodules can become the separate Git directory path. Reject empty names, absolute or drive-qualified names, and parent components with either path separator at the shared path-construction boundary. Add a local-repository regression test that demonstrates initialization cannot create a module repository outside the clone. This follows Git commit 0383bbb901 (submodule-config: verify submodule names as paths) while also accounting for os.path.join absolute-path semantics. Advisory: GHSA-hmq2-w58f-27jc Validation: - pytest -q test/test_submodule.py (39 passed, 1 skipped, 1 xfailed) - ruff check and format --check on changed files - mypy git/objects/submodule/base.py
1 parent 6e61b1d commit e1d07cc

2 files changed

Lines changed: 44 additions & 0 deletions

File tree

git/objects/submodule/base.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import gc
77
from io import BytesIO
88
import logging
9+
import ntpath
910
import os
1011
import os.path as osp
1112
import stat
@@ -305,6 +306,13 @@ def _config_parser_constrained(self, read_only: bool) -> SectionConstraint:
305306
@classmethod
306307
def _module_abspath(cls, parent_repo: "Repo", path: PathLike, name: str) -> PathLike:
307308
if cls._need_gitfile_submodules(parent_repo.git):
309+
if (
310+
not name
311+
or name.startswith(("/", "\\"))
312+
or ntpath.splitdrive(name)[0]
313+
or ".." in name.replace("\\", "/").split("/")
314+
):
315+
raise ValueError("Invalid submodule name %r" % name)
308316
return osp.join(parent_repo.git_dir, "modules", name)
309317
if parent_repo.working_tree_dir:
310318
return osp.join(parent_repo.working_tree_dir, path)

test/test_submodule.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,42 @@ def test_update_submodule_with_relative_path(self, rwdir):
925925

926926
cloned_repo.submodule_update(init=True, recursive=True)
927927

928+
@with_rw_directory
929+
@_patch_git_config("protocol.file.allow", "always")
930+
def test_update_rejects_parent_component_in_name(self, rwdir):
931+
source = git.Repo.init(osp.join(rwdir, "source"))
932+
source.git.commit(m="initial commit", allow_empty=True)
933+
934+
parent = git.Repo.init(osp.join(rwdir, "parent"))
935+
parent.git.submodule("add", source.working_tree_dir, "module")
936+
parent.index.commit("add submodule")
937+
modules_file = Path(parent.working_tree_dir) / ".gitmodules"
938+
modules_file.write_text(
939+
modules_file.read_text().replace('submodule "module"', 'submodule "../../../escaped/module"')
940+
)
941+
parent.index.add([".gitmodules"])
942+
parent.index.commit("change submodule name")
943+
944+
clone = git.Repo.clone_from(parent.working_tree_dir, osp.join(rwdir, "clone"))
945+
with pytest.raises(ValueError, match="submodule name"):
946+
clone.submodules[0].update(init=True)
947+
assert not osp.exists(osp.join(rwdir, "escaped"))
948+
invalid_names = (
949+
"",
950+
"..",
951+
"../module",
952+
R"..\module",
953+
"nested/../module",
954+
R"nested\..\module",
955+
"/module",
956+
R"\module",
957+
"C:module",
958+
R"C:\module",
959+
)
960+
for name in invalid_names:
961+
with pytest.raises(ValueError, match="submodule name"):
962+
Submodule._module_abspath(clone, "module", name)
963+
928964
@with_rw_directory
929965
@_patch_git_config("protocol.file.allow", "always")
930966
def test_list_only_valid_submodules(self, rwdir):

0 commit comments

Comments
 (0)