{"record":{"id":"422534c8936ad761","repo":"shareAI-lab/learn-claude-code","slug":"path-escapes-workspace-p-422534","errorCode":null,"errorMessage":"Path escapes workspace: {p}","messagePattern":"Path escapes workspace: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agents/s12_worktree_task_isolation.py","lineNumber":481,"sourceCode":"            \"worktree.keep\",\n            task={\"id\": wt.get(\"task_id\")} if wt.get(\"task_id\") is not None else {},\n            worktree={\n                \"name\": name,\n                \"path\": wt.get(\"path\"),\n                \"status\": \"kept\",\n            },\n        )\n        return json.dumps(kept, indent=2) if kept else f\"Error: Unknown worktree '{name}'\"\n\n\nWORKTREES = WorktreeManager(REPO_ROOT, TASKS, EVENTS)\n\n\n# -- Base tools (kept minimal, same style as previous sessions) --\ndef safe_path(p: str) -> Path:\n    path = (WORKDIR / p).resolve()\n    if not path.is_relative_to(WORKDIR):\n        raise ValueError(f\"Path escapes workspace: {p}\")\n    return path\n\n\ndef run_bash(command: str) -> str:\n    dangerous = [\"rm -rf /\", \"sudo\", \"shutdown\", \"reboot\", \"> /dev/\"]\n    if any(d in command for d in dangerous):\n        return \"Error: Dangerous command blocked\"\n    try:\n        r = subprocess.run(\n            command,\n            shell=True,\n            cwd=WORKDIR,\n            capture_output=True,\n            text=True,\n            timeout=120,\n        )\n        out = (r.stdout + r.stderr).strip()\n        return out[:50000] if out else \"(no output)\"","sourceCodeStart":463,"sourceCodeEnd":499,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/agents/s12_worktree_task_isolation.py#L463-L499","documentation":"Raised by safe_path() in the s12 agent tool layer when a user-supplied relative path resolves outside WORKDIR. The function joins the path onto WORKDIR, resolves symlinks and .. segments, then requires the result to be inside WORKDIR — a classic sandbox containment check that blocks path traversal. All file tools (read/write/edit) route through it, so any traversal attempt fails before touching the filesystem.","triggerScenarios":"Passing \"../../etc/passwd\", an absolute path like \"/etc/hosts\" (WORKDIR / '/etc/hosts' yields /etc/hosts, outside WORKDIR), or a path that traverses a symlink pointing outside WORKDIR. Also fires on benign-looking inputs when WORKDIR itself is a symlink that resolves elsewhere.","commonSituations":"Agents emitting absolute paths from tool output (e.g. copying a path from an error message or git output); symlinks inside the workspace pointing to system directories; WORKDIR configured as a symlinked path so resolve() moves the root out from under the containment check.","solutions":["Strip any leading '/' and re-express the path relative to WORKDIR before calling the tool","Convert absolute paths under WORKDIR to relative: path.relative_to(WORKDIR) when possible","If a legitimate symlink inside WORKDIR points outside, restructure so the data lives inside the workspace","If WORKDIR is itself a symlink, pass the resolved real path when constructing the agent"],"exampleFix":"// before\nrun_read(\"/tmp/errlookup-AdzUmp/notes.txt\")  # absolute -> escapes\n// after\nfrom pathlib import Path\nrel = Path(\"/tmp/errlookup-AdzUmp/notes.txt\").relative_to(WORKDIR)\nrun_read(str(rel))","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef in_workspace(p: str) -> bool:\n    try:\n        (WORKDIR / p).resolve().relative_to(WORKDIR.resolve())\n        return True\n    except ValueError:\n        return False\n\n# or normalize absolute paths first:\ndef to_relative(p: str) -> str:\n    ap = Path(p).resolve()\n    return str(ap.relative_to(WORKDIR.resolve()))","typeGuard":null,"tryCatchPattern":"try:\n    safe_path(p)\nexcept ValueError:\n    p = str(Path(p).resolve().relative_to(WORKDIR.resolve()))  # only if truly inside\n    safe_path(p)","preventionTips":["Always pass workspace-relative paths to file tools","Convert absolute paths with relative_to(WORKDIR) before calling","Audit symlinks inside the workspace periodically","Ensure WORKDIR is the real (resolved) path at agent startup"],"tags":["security","path-traversal","sandbox","validation"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}