datawhalechina/hello-agents · error · PatchApplyError

Path escapes repo_root: {rel_path}

Error message

Path escapes repo_root: {rel_path}

What it means

Raised by _safe_path when (repo_root / rel_path).resolve() falls outside repo_root after symlink resolution — the classic path-traversal defense. It fires even though the raw string is relative, because components like '../' (or a symlinked directory pointing outside) resolve to a target not under repo_root. Note the check compares string prefixes with os.sep appended, which also rejects a sibling directory whose name starts with repo_root's name.

Source

Thrown at Co-creation-projects/YYHDBL-HelloCodeAgentCli/code_agent/executors/apply_patch_executor.py:204

        """
        验证路径安全性,防止路径遍历攻击 (Path Traversal)。
        确保目标路径在 repo_root 目录下,防止访问仓库外的文件。
        
        参数:
            rel_path: 相对路径字符串
            
        返回:
            Path: 安全的绝对路径对象
            
        异常:
            PatchApplyError: 当路径是绝对路径、包含特殊字符或试图访问仓库外时抛出
        """
        if rel_path.startswith("/") or rel_path.startswith("~"):
            raise PatchApplyError(f"Absolute paths are not allowed: {rel_path}")
        target = (self.repo_root / rel_path).resolve()
        # 检查解析后的路径是否以 repo_root 开头
        if not str(target).startswith(str(self.repo_root.resolve()) + os.sep) and target != self.repo_root.resolve():
            raise PatchApplyError(f"Path escapes repo_root: {rel_path}")
        if target.exists() and target.is_symlink():
            raise PatchApplyError(f"Refusing to modify symlink: {rel_path}")
        return target

    def _enforce_suffix(self, target: Path) -> None:
        """
        检查目标文件的后缀是否在允许的列表中。
        防止意外修改二进制文件、配置文件或其他敏感文件。
        
        参数:
            target: 目标文件路径对象
            
        异常:
            PatchApplyError: 当文件后缀不在允许列表中时抛出
        """
        if target.suffix and target.suffix not in self.allowed_write_suffixes:
            raise PatchApplyError(f"Disallowed file suffix for write: {target.suffix}")

View on GitHub (pinned to 606a07d341)

Solutions

  1. Rewrite the patch path to a clean repo-relative path with no '..' components.
  2. Ensure repo_root is passed to the executor as a fully resolved absolute path (Path(...).resolve()).
  3. If a symlink inside the repo must be traversed, replace it with the real location inside repo_root or move the dependency into the repo.
  4. Pre-validate rel_path with posixpath normalization and reject '..' before invoking apply.

Example fix

# before
rel_path = '../../shared/lib.py'  # escapes repo_root

# after
import posixpath
rel_path = posixpath.normpath(rel_path)
assert not rel_path.startswith('..'), 'path escapes repo'
# use 'shared/lib.py' copied into the repo instead
Defensive patterns

Strategy: validation

Validate before calling

import posixpath

def safe_rel(rel_path: str) -> str:
    if rel_path.startswith(('/', '~')):
        raise ValueError('absolute path')
    norm = posixpath.normpath(rel_path)
    if norm.startswith('..') or posixpath.isabs(norm):
        raise ValueError(f'escapes repo_root: {rel_path}')
    return norm

Type guard

def stays_in_repo(rel_path: str, repo_root: Path) -> bool:
    target = (repo_root / rel_path).resolve()
    root = repo_root.resolve()
    return target == root or str(target).startswith(str(root) + os.sep)

Try / catch

try:
    executor.apply(patch)
except PatchApplyError as e:
    if 'escapes repo_root' in str(e):
        raise PermissionError(f'rejected traversal attempt: {e}')  # do not retry
    raise

Prevention

When it happens

Trigger: rel_path containing '../' sequences (e.g. '../../etc/cron.d/x'), a path that resolves to repo_root itself being rejected only when it equals neither condition, or a symlink inside the repo pointing to an external directory that resolve() follows.

Common situations: LLM patches copying '../../../' paths from stack traces; repos containing convenience symlinks to shared code outside the repo; repo_root passed as a non-canonical path (e.g. './proj' vs '/abs/proj') so prefix comparison behaves unexpectedly.

Related errors


AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14). Data as JSON: /api/errors/7694f3222d569122. Report an issue: GitHub.