can1357/oh-my-pi · error · ValueError
Unsafe {scheme}:// path (absolute or traversal): {path}
Error message
Unsafe {scheme}:// path (absolute or traversal): {path} What it means
_resolve_omp_path rejects scheme paths whose relative component is absolute (e.g. scheme:///abs/x) or contains '..' segments, raising ValueError 'Unsafe <scheme>:// path (absolute or traversal)'. This is a path-traversal guard: every resolved path must stay inside the configured root for that scheme.
Source
Thrown at packages/coding-agent/src/eval/py/prelude.py:112
return Path(path)
scheme = match.group(1).lower()
try:
roots = json.loads(os.environ.get("PI_EVAL_LOCAL_ROOTS") or "{}")
except (ValueError, TypeError):
roots = {}
root = roots.get(scheme) if isinstance(roots, dict) else None
if not root:
raise ValueError(f"Protocol paths are not supported by this helper: {path}")
relative = unquote(match.group(2).replace("\\", "/"))
# Mirror the host `path.resolve`/`resolveLocalUrlToPath`: normalize and
# make absolute WITHOUT realpath'ing symlinks (Path.resolve would turn
# /tmp into /private/tmp and diverge from the read-side resolution).
root_path = os.path.abspath(root)
if relative == "":
return Path(root_path)
rel_path = Path(relative)
if rel_path.is_absolute() or ".." in rel_path.parts:
raise ValueError(f"Unsafe {scheme}:// path (absolute or traversal): {path}")
resolved = os.path.abspath(os.path.join(root_path, relative))
if resolved != root_path and not resolved.startswith(root_path + os.sep):
raise ValueError(f"{scheme}:// path escapes its root: {path}")
return Path(resolved)
def read(path: str | Path, offset: int = 1, limit: int | None = None) -> str:
"""Read file or read-tool URI contents. offset/limit are 1-indexed lines."""
if _should_delegate_read(path):
if limit is not None and limit <= 0:
return ""
selector = _read_line_selector(offset, limit)
tool_path = path if selector is None else f"{path}:{selector}"
return _read_tool_text(tool_path)
p = _resolve_omp_path(path)
data = p.read_text(encoding="utf-8")
lines = data.splitlines(keepends=True)
if offset > 1 or limit is not None:
start = max(0, offset - 1)View on GitHub (pinned to 9690622007)
Solutions
- Rewrite the URI to a path relative to the scheme root with no leading '/' and no '..' segments.
- Copy the target file into the root directory configured for that scheme, then reference it relatively.
- If the file genuinely lives outside the root, access it via a plain local path if the harness permits, or extend PI_EVAL_LOCAL_ROOTS to mount the needed directory.
Example fix
# before
read("omp:///../outside/secret.txt")
# after
read("omp://artifacts/result.json") # relative, no traversal Defensive patterns
Strategy: validation
Validate before calling
from pathlib import PurePosixPath def is_safe_rel(rel): p = PurePosixPath(rel); return not p.is_absolute() and '..' not in p.parts
Type guard
def is_protocol_path(p): return '://' in str(p)
Try / catch
try:
data = read(uri)
except ValueError as e:
if 'Unsafe' in str(e) or 'traversal' in str(e):
raise PermissionError(f'Refusing path outside session root: {uri}') from e
raise Prevention
- Normalize any user/model-supplied URI: strip leading slashes and resolve '..' against the intended target.
- Treat 'Unsafe ... path' errors as inputs to sanitize, not paths to work around.
- Copy external files into the scheme root host-side instead of traversing to them.
- Reject '..' segments at input-validation time in generated code review.
When it happens
Trigger: read()/write() with a URI whose path after the scheme is absolute ('scheme:///etc/passwd') or climbs out with '..' ('scheme://../../secret').
Common situations: LLM-generated code constructing URIs with leading slashes or trying to reach files outside the session root; joining user-supplied relative paths that include '..'; echoing back absolute paths captured from host tool output into a scheme URI.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- {scheme}:// path escapes its root: {path}
- Destination paths cannot contain parent traversal or NUL byt
- Shared-folder destination escapes its configured root
- Refusing to download outside the workspace: ${downloadPath}
- Absolute paths are not allowed in ${scheme}:// URLs: ${rawPa
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/8d7b2b93b6cbbcd6.
Report an issue: GitHub.