abhigyanpatwari/GitNexus · critical · ValueError
candidate destination escapes the clone: {relative}
Error message
candidate destination escapes the clone: {relative} What it means
Thrown by _replace_regular_file (evolution.py:170) before writing an overlay file into the clone. It rejects any relative path that is_absolute(), has no parts, or contains a '..' component. This is the path-traversal guard preventing a candidate overlay from writing outside the clone root through a relative destination.
Source
Thrown at eval/workflow_bench/evolution.py:170
return _fingerprint_payload(payload), payload
def _fingerprint_payload(payload: list[tuple[PurePosixPath, bytes]]) -> str:
digest = hashlib.sha256()
for relative_path, content in payload:
relative = relative_path.as_posix().encode()
digest.update(len(relative).to_bytes(8, "big"))
digest.update(relative)
digest.update(len(content).to_bytes(8, "big"))
digest.update(content)
return digest.hexdigest()
def _replace_regular_file(root: Path, relative: Path, content: bytes) -> None:
"""Replace a clone file through validated directory descriptors."""
if relative.is_absolute() or not relative.parts or ".." in relative.parts:
raise ValueError(f"candidate destination escapes the clone: {relative}")
_require_real_directory(root, label="candidate destination root")
directory_flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0)
descriptor = os.open(root, directory_flags)
try:
for part in relative.parts[:-1]:
try:
os.mkdir(part, mode=0o700, dir_fd=descriptor)
except FileExistsError:
pass
try:
child = os.open(part, directory_flags, dir_fd=descriptor)
except OSError as exc:
raise ValueError(
f"candidate destination parent must be a real directory: {relative.parent}: {exc}"
) from exc
os.close(descriptor)
descriptor = child
View on GitHub (pinned to d540b00184)
Solutions
- Remove all '..' and absolute components from overlay paths; keep paths as clean relative POSIX paths under .claude/skills/.
- Sanitize with PurePosixPath: reject p.is_absolute() or '..' in p.parts, then pass only the normalized result.
- Re-create the overlay from a clean checkout so no stray traversal components remain.
Example fix
# before: overlay contains a traversal path
.claude/skills/../../package.json
# after: keep only real skill paths; guard before applying
from pathlib import PurePosixPath
rel = PurePosixPath('.claude/skills/gitnexus-work/SKILL.md')
assert not rel.is_absolute() and rel.parts and '..' not in rel.parts, 'escapes clone' Defensive patterns
Strategy: validation
Validate before calling
from pathlib import PurePosixPath
def overlay_path_is_safe(relative: PurePosixPath) -> bool:
return (
not relative.is_absolute()
and bool(relative.parts)
and '..' not in relative.parts
and '' not in relative.parts
) Type guard
from pathlib import PurePosixPath
def is_safe_overlay_path(p: PurePosixPath) -> bool:
return not p.is_absolute() and bool(p.parts) and '..' not in p.parts and '' not in p.parts Try / catch
try:
apply_candidate_overlay(overlay, worktree, sandbox=sandbox)
except ValueError as exc:
if 'escapes the clone' in str(exc):
raise SystemExit(f'refuse: overlay path traversal detected: {exc}') Prevention
- Build overlay paths only from trusted constants, never raw user input.
- Reject '..' and absolute components before constructing the overlay tree.
- Treat this error as a potential integrity attack, not a recoverable typo.
When it happens
Trigger: An overlay file whose repo-relative path includes '..' (e.g. .claude/skills/../../etc/passwd), an absolute path (/etc/x), or an empty leaf name. Any such destination would escape the clone and is refused before any directory-fd walk begins.
Common situations: Manually crafting an overlay that reuses an existing repo path with a '../' shortcut; a buggy script that builds overlay paths from user input without normalization; an adversarial candidate attempting to escape the trust boundary.
Related errors
- candidate destination must be a regular non-symlink file: {r
- candidate overlay cannot traverse symlinks: {overlay}
- candidate overlay cannot contain symlinks: {relative}
- candidate overlay entries must be regular files: {relative}
- candidate overlays may only contain Markdown files under .cl
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/0c9e46a3b9021127.
Report an issue: GitHub.