{"record":{"id":"ad9050c8b4783eb8","repo":"shareAI-lab/learn-claude-code","slug":"path-escapes-workspace-p-ad9050","errorCode":null,"errorMessage":"Path escapes workspace: {p}","messagePattern":"Path escapes workspace: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agents/s07_task_system.py","lineNumber":128,"sourceCode":"            tasks.append(json.loads(f.read_text()))\n        if not tasks:\n            return \"No tasks.\"\n        lines = []\n        for t in tasks:\n            marker = {\"pending\": \"[ ]\", \"in_progress\": \"[>]\", \"completed\": \"[x]\"}.get(t[\"status\"], \"[?]\")\n            blocked = f\" (blocked by: {t['blockedBy']})\" if t.get(\"blockedBy\") else \"\"\n            lines.append(f\"{marker} #{t['id']}: {t['subject']}{blocked}\")\n        return \"\\n\".join(lines)\n\n\nTASKS = TaskManager(TASKS_DIR)\n\n\n# -- Base tool implementations --\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\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(command, shell=True, cwd=WORKDIR,\n                           capture_output=True, text=True, timeout=120)\n        out = (r.stdout + r.stderr).strip()\n        return out[:50000] if out else \"(no output)\"\n    except subprocess.TimeoutExpired:\n        return \"Error: Timeout (120s)\"\n\ndef run_read(path: str, limit: int = None) -> str:\n    try:\n        lines = safe_path(path).read_text().splitlines()\n        if limit and limit < len(lines):","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/agents/s07_task_system.py#L110-L146","documentation":"The workspace sandbox guard at agents/s07_task_system.py:128 in the task-system agent, applied to its base file tools. Paths are joined onto WORKDIR (= Path.cwd() at launch), resolved, and must remain under WORKDIR. Task JSON files live in TASKS_DIR and are managed by TaskManager directly, so this guard covers the model-facing file tools, not task files.","triggerScenarios":"A file tool call with an absolute path (including to the tasks dir itself), a `../` traversal, or a symlink target outside the workspace. Also when the harness cwd differs from the directory holding the files the model manipulates.","commonSituations":"Model reading/writing task scratch files by absolute path taken from task JSON output. Launching the harness from $HOME. Symlinked workspaces.","solutions":["Use workspace-relative paths in file tool calls","Derive relative paths from absolute ones with os.path.relpath before calling","Launch the agent from the workspace root"],"exampleFix":"// before\n{\"op\": \"read\", \"path\": \"/tmp/errlookup-AdzUmp/tasks/task_1.json\"}\n// ValueError: Path escapes workspace\n\n// after\n{\"op\": \"read\", \"path\": \"tasks/task_1.json\"}  // or use task_get(1)","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef is_task_file_readable_rel(p: str, workdir: Path) -> bool:\n    try:\n        return (workdir / p).resolve().is_relative_to(workdir)\n    except (OSError, RuntimeError):\n        return False\n\n# prefer structured access over raw reads of task files:\n# task_get(1) instead of read_file(\"tasks/task_1.json\") with an absolute path","typeGuard":"def is_workspace_relative(p: object) -> bool:\n    return isinstance(p, str) and bool(p) and not p.startswith((\"/\", \"~\")) and \"..\" not in Path(p).parts","tryCatchPattern":"try:\n    path = safe_path(p)\nexcept ValueError:\n    return f\"{p} escapes the workspace. Use task_get/task_list for task data, or a relative path for files.\"","preventionTips":["Use the task tools (get/list) rather than raw file reads of task_<id>.json","Keep scratch files inside the workspace and reference them relatively","Launch the harness from the workspace root"],"tags":["security","path-traversal","sandbox","task-system","python"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}