abhigyanpatwari/GitNexus · error · ValueError
{label} contains an unsafe path component: {relative}
Error message
{label} contains an unsafe path component: {relative} What it means
Thrown by _require_directory_chain() in eval/workflow_bench/evolution.py when iterating relative.parts and finding a component that is '', '.', or '..'. These components are rejected outright because they enable path traversal or no-op confusion when constructing paths inside the sandbox, without needing to resolve links.
Source
Thrown at eval/workflow_bench/evolution.py:81
def _require_real_directory(path: Path, *, label: str) -> None:
try:
metadata = path.lstat()
except OSError as exc:
raise ValueError(f"{label} is unavailable: {path}: {exc}") from exc
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):
raise ValueError(f"{label} must be a real non-symlink directory: {path}")
def _require_directory_chain(root: Path, relative: Path, *, label: str) -> None:
"""Validate each lexical directory without erasing links via resolve()."""
_require_real_directory(root, label=label)
current = root
for part in relative.parts:
if part in {"", ".", ".."}:
raise ValueError(f"{label} contains an unsafe path component: {relative}")
current /= part
_require_real_directory(current, label=label)
def _bounded_regular_bytes(path: Path, *, limit: int, label: str) -> bytes:
"""Read one bounded regular file without following its leaf link."""
try:
before = path.lstat()
except OSError as exc:
raise ValueError(f"{label} is unreadable: {path}: {exc}") from exc
if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):
raise ValueError(f"{label} must be a regular non-symlink file: {path}")
if before.st_size > limit:
raise ValueError(f"{label} exceeds the bounded evidence limit")
descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0))
try:View on GitHub (pinned to d540b00184)
Solutions
- Rebuild the overlay so every path is a clean relative path with no '.'/'..'/empty segments.
- Audit the candidate generator to normalize paths (PurePosixPath and reject non-normal).
- Re-extract tarballs defensively and drop any entry whose relative path has unsafe parts.
- Treat this as a security signal if the source is untrusted — investigate provenance.
Example fix
# before files = ['../secret.yaml', 'skill/x.yaml'] # ValueError: candidate overlay directory contains an unsafe path component: ../secret.yaml # after files = ['skill/x.yaml'] # traversal entries dropped at generation time
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import PurePosixPath
def safe_relative(rel: str) -> PurePosixPath:
parts = PurePosixPath(rel).parts
if any(c in {'', '.', '..'} for c in parts):
raise SystemExit(f'unsafe overlay path component: {rel}')
return PurePosixPath(rel)
# validate every candidate path before passing to candidate_overlay_payload Type guard
def is_unsafe_path_error(exc: ValueError) -> bool:
return 'contains an unsafe path component' in str(exc) Try / catch
try:
candidate_overlay_payload(overlay)
except ValueError as e:
if is_unsafe_path_error(e):
# drop offending entries, regenerate overlay, retry
raise
raise Prevention
- Normalize candidate paths at generation time and reject any with . or .. parts.
- Treat path-traversal entries from untrusted sources as a security signal.
- Use PurePosixPath and validate .parts explicitly.
When it happens
Trigger: A candidate overlay file whose path relative to the overlay root contains '..' or '.' or an empty segment — e.g. ../escape.yaml, ./x.yaml, or a//b.yaml. The check fires during candidate_overlay_payload() walking each file's parent chain.
Common situations: A hand-crafted or tarball-extracted candidate that includes traversal segments to escape the overlay root; buggy candidate-generation code joining paths with leading '/'; an empty path part from string splitting.
Related errors
- {label} must be a real non-symlink directory: {path}
- {label} must be a regular non-symlink file: {path}
- {label} changed while opening: {path}
- unsafe results artifact path: {relative_value!r}
- {label} is unavailable: {path}: {exc}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/718ab598b4f6d42f.
Report an issue: GitHub.