From 85e1e1b6f28cb93d583078fad26807a2cee9d9f9 Mon Sep 17 00:00:00 2001 From: Samy6767f Date: Mon, 24 Aug 2026 18:48:32 +0530 Subject: [PATCH 1/2] fix(interface): prevent path traversal and argument injection in clone_repository - Replace unsafe Path().stem/name derivation with derive_repo_base_name() which strips .git suffix and sanitizes via regex, preventing '..git' from resolving to '..' and causing rmtree to delete parent directories. - Add resolved-path containment check: clone_path.resolve() must start with temp_dir.resolve(), rejecting any traversal attempt. - Insert '--' end-of-options separator before repo_url in the git clone subprocess call, preventing targets starting with '-' from being interpreted as git flags (e.g. --upload-pack for RCE). - Add explicit validation rejecting repo_url starting with '-' with a clear error message before any subprocess invocation. Fixes #1133, fixes #1134. --- strix/interface/utils.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/strix/interface/utils.py b/strix/interface/utils.py index faab1772..8a80942b 100644 --- a/strix/interface/utils.py +++ b/strix/interface/utils.py @@ -1563,19 +1563,34 @@ def clone_repository(repo_url: str, run_name: str, dest_name: str | None = None) if dest_name: repo_name = dest_name else: - repo_name = Path(repo_url).stem if repo_url.endswith(".git") else Path(repo_url).name + repo_name = derive_repo_base_name(repo_url) - clone_path = temp_dir / repo_name + # Guard against path traversal: the derived name must not resolve + # outside the temp directory (e.g. ".." or "."). + clone_path = (temp_dir / repo_name).resolve() + if not str(clone_path).startswith(str(temp_dir.resolve())): + raise ValueError( + f"Refusing to clone: derived directory name '{repo_name}' " + f"escapes the temporary directory" + ) if clone_path.exists(): shutil.rmtree(clone_path) try: + # Reject targets that start with '-' to block argument injection + if repo_url.startswith("-"): + raise ValueError( + f"Refusing to clone: target '{repo_url}' starts with '-' " + f"and would be interpreted as a git flag" + ) + with console.status(f"[bold cyan]Cloning repository {repo_url}...", spinner="dots"): subprocess.run( # noqa: S603 [ git_executable, "clone", + "--", # end-of-options: everything after is a positional arg repo_url, str(clone_path), ], From c82b4f17f731f5a4603ece763abaf5fff6e6ea09 Mon Sep 17 00:00:00 2001 From: Samy6767f Date: Mon, 24 Aug 2026 18:55:09 +0530 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20use=20strict=20child=20check=20in=20?= =?UTF-8?q?path=20containment=20=E2=80=94=20reject=20clone=5Fpath=20=3D=3D?= =?UTF-8?q?=20temp=5Fdir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous startswith check accepted clone_path equal to temp_dir itself (a string starts with itself), so '..git' deriving '.' would still resolve to temp_dir and pass through to shutil.rmtree. Now requires clone_path to be a strict descendant: adds equality rejection and uses os.sep-terminated prefix to also prevent sibling directory prefix collisions. --- strix/interface/utils.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/strix/interface/utils.py b/strix/interface/utils.py index 8a80942b..6030fa6b 100644 --- a/strix/interface/utils.py +++ b/strix/interface/utils.py @@ -1565,10 +1565,14 @@ def clone_repository(repo_url: str, run_name: str, dest_name: str | None = None) else: repo_name = derive_repo_base_name(repo_url) - # Guard against path traversal: the derived name must not resolve - # outside the temp directory (e.g. ".." or "."). + # Guard against path traversal: the derived name must resolve to a + # strict child of the temp directory — not temp_dir itself, and not + # any path outside it (e.g. "." or ".."). clone_path = (temp_dir / repo_name).resolve() - if not str(clone_path).startswith(str(temp_dir.resolve())): + temp_dir_resolved = temp_dir.resolve() + if clone_path == temp_dir_resolved or not str(clone_path).startswith( + str(temp_dir_resolved) + os.sep + ): raise ValueError( f"Refusing to clone: derived directory name '{repo_name}' " f"escapes the temporary directory"