fix: harden salvaged worktree include checks
Use Path.relative_to-based containment checks for the salvaged .worktreeinclude guard, remove the replayed test logic from the cherry-picked PR, and add real integration regressions for file, directory, and symlink escapes.
This commit is contained in:
parent
12bc86d9c9
commit
f4c012873c
3 changed files with 145 additions and 77 deletions
|
|
@ -633,75 +633,3 @@ class TestSystemPromptInjection:
|
|||
assert info["repo_root"] in wt_note
|
||||
assert "isolated git worktree" in wt_note
|
||||
assert "commit and push" in wt_note
|
||||
|
||||
|
||||
class TestWorktreeIncludePathTraversal:
|
||||
"""Test that .worktreeinclude entries with path traversal are rejected."""
|
||||
|
||||
def test_rejects_parent_directory_traversal(self, git_repo):
|
||||
"""Entries like '../../etc/passwd' must not escape the repo root."""
|
||||
import shutil as _shutil
|
||||
|
||||
# Create a sensitive file outside the repo to simulate the attack
|
||||
outside_file = git_repo.parent / "sensitive.txt"
|
||||
outside_file.write_text("SENSITIVE DATA")
|
||||
|
||||
# Create a .worktreeinclude with a traversal entry
|
||||
(git_repo / ".worktreeinclude").write_text("../sensitive.txt\n")
|
||||
|
||||
info = _setup_worktree(str(git_repo))
|
||||
assert info is not None
|
||||
|
||||
wt_path = Path(info["path"])
|
||||
|
||||
# Replay the fixed logic from cli.py
|
||||
repo_root_resolved = Path(str(git_repo)).resolve()
|
||||
wt_path_resolved = wt_path.resolve()
|
||||
include_file = git_repo / ".worktreeinclude"
|
||||
|
||||
copied_entries = []
|
||||
for line in include_file.read_text().splitlines():
|
||||
entry = line.strip()
|
||||
if not entry or entry.startswith("#"):
|
||||
continue
|
||||
src = Path(str(git_repo)) / entry
|
||||
dst = wt_path / entry
|
||||
try:
|
||||
src_resolved = src.resolve()
|
||||
dst_resolved = dst.resolve(strict=False)
|
||||
except (OSError, ValueError):
|
||||
continue
|
||||
if not str(src_resolved).startswith(str(repo_root_resolved) + os.sep) and src_resolved != repo_root_resolved:
|
||||
continue
|
||||
if not str(dst_resolved).startswith(str(wt_path_resolved) + os.sep) and dst_resolved != wt_path_resolved:
|
||||
continue
|
||||
copied_entries.append(entry)
|
||||
|
||||
# The traversal entry must have been skipped
|
||||
assert len(copied_entries) == 0
|
||||
# The sensitive file must NOT be in the worktree
|
||||
assert not (wt_path / "../sensitive.txt").resolve().is_relative_to(wt_path_resolved)
|
||||
|
||||
def test_allows_valid_entries(self, git_repo):
|
||||
"""Normal entries within the repo should still be processed."""
|
||||
(git_repo / ".env").write_text("KEY=val")
|
||||
(git_repo / ".worktreeinclude").write_text(".env\n")
|
||||
|
||||
info = _setup_worktree(str(git_repo))
|
||||
assert info is not None
|
||||
|
||||
repo_root_resolved = Path(str(git_repo)).resolve()
|
||||
include_file = git_repo / ".worktreeinclude"
|
||||
|
||||
accepted = []
|
||||
for line in include_file.read_text().splitlines():
|
||||
entry = line.strip()
|
||||
if not entry or entry.startswith("#"):
|
||||
continue
|
||||
src = Path(str(git_repo)) / entry
|
||||
src_resolved = src.resolve()
|
||||
if not str(src_resolved).startswith(str(repo_root_resolved) + os.sep) and src_resolved != repo_root_resolved:
|
||||
continue
|
||||
accepted.append(entry)
|
||||
|
||||
assert ".env" in accepted
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue