Merge pull request #1388 from NousResearch/hermes/hermes-0fadff1b

fix: harden .worktreeinclude path containment
This commit is contained in:
Teknium 2026-03-14 21:53:28 -07:00 committed by GitHub
commit d36b3d498d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 157 additions and 1 deletions

28
cli.py
View file

@ -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)