Merge pull request #1388 from NousResearch/hermes/hermes-0fadff1b
fix: harden .worktreeinclude path containment
This commit is contained in:
commit
d36b3d498d
2 changed files with 157 additions and 1 deletions
28
cli.py
28
cli.py
|
|
@ -517,6 +517,15 @@ def _git_repo_root() -> Optional[str]:
|
|||
return None
|
||||
|
||||
|
||||
def _path_is_within_root(path: Path, root: Path) -> bool:
|
||||
"""Return True when a resolved path stays within the expected root."""
|
||||
try:
|
||||
path.relative_to(root)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def _setup_worktree(repo_root: str = None) -> Optional[Dict[str, str]]:
|
||||
"""Create an isolated git worktree for this CLI session.
|
||||
|
||||
|
|
@ -570,12 +579,29 @@ def _setup_worktree(repo_root: str = None) -> Optional[Dict[str, str]]:
|
|||
include_file = Path(repo_root) / ".worktreeinclude"
|
||||
if include_file.exists():
|
||||
try:
|
||||
repo_root_resolved = Path(repo_root).resolve()
|
||||
wt_path_resolved = wt_path.resolve()
|
||||
for line in include_file.read_text().splitlines():
|
||||
entry = line.strip()
|
||||
if not entry or entry.startswith("#"):
|
||||
continue
|
||||
src = Path(repo_root) / entry
|
||||
dst = wt_path / entry
|
||||
# Prevent path traversal and symlink escapes: both the resolved
|
||||
# source and the resolved destination must stay inside their
|
||||
# expected roots before any file or symlink operation happens.
|
||||
try:
|
||||
src_resolved = src.resolve(strict=False)
|
||||
dst_resolved = dst.resolve(strict=False)
|
||||
except (OSError, ValueError):
|
||||
logger.debug("Skipping invalid .worktreeinclude entry: %s", entry)
|
||||
continue
|
||||
if not _path_is_within_root(src_resolved, repo_root_resolved):
|
||||
logger.warning("Skipping .worktreeinclude entry outside repo root: %s", entry)
|
||||
continue
|
||||
if not _path_is_within_root(dst_resolved, wt_path_resolved):
|
||||
logger.warning("Skipping .worktreeinclude entry that escapes worktree: %s", entry)
|
||||
continue
|
||||
if src.is_file():
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(str(src), str(dst))
|
||||
|
|
@ -583,7 +609,7 @@ def _setup_worktree(repo_root: str = None) -> Optional[Dict[str, str]]:
|
|||
# Symlink directories (faster, saves disk)
|
||||
if not dst.exists():
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
os.symlink(str(src.resolve()), str(dst))
|
||||
os.symlink(str(src_resolved), str(dst))
|
||||
except Exception as e:
|
||||
logger.debug("Error copying .worktreeinclude entries: %s", e)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue