{"record":{"id":"7aac7556190f4f7a","repo":"shareAI-lab/learn-claude-code","slug":"path-escapes-workspace-p-7aac75","errorCode":null,"errorMessage":"Path escapes workspace: {p}","messagePattern":"Path escapes workspace: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agents/s04_subagent.py","lineNumber":50,"sourceCode":"\nload_dotenv(override=True)\n\nif os.getenv(\"ANTHROPIC_BASE_URL\"):\n    os.environ.pop(\"ANTHROPIC_AUTH_TOKEN\", None)\n\nWORKDIR = Path.cwd()\nclient = Anthropic(base_url=os.getenv(\"ANTHROPIC_BASE_URL\"))\nMODEL = os.environ[\"MODEL_ID\"]\n\nSYSTEM = f\"You are a coding agent at {WORKDIR}. Use the task tool to delegate exploration or subtasks.\"\nSUBAGENT_SYSTEM = f\"You are a coding subagent at {WORKDIR}. Complete the given task, then summarize your findings.\"\n\n\n# -- Tool implementations shared by parent and child --\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    except (FileNotFoundError, OSError) as e:\n        return f\"Error: {e}\"\n\ndef run_read(path: str, limit: int = None) -> str:\n    try:","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/agents/s04_subagent.py#L32-L68","documentation":"The workspace sandbox guard repeated at agents/s04_subagent.py:50, shared by parent and subagent in the delegation demo. Both roles' file tools route through safe_path(): join onto WORKDIR, resolve(), require the result to stay under WORKDIR. Subagents inherit the same WORKDIR, so a child asked to read a path outside the workspace gets the same rejection.","triggerScenarios":"The parent delegates \"read /var/log/system.log and summarize\" and the subagent passes that absolute path to a file tool. Or a subagent follows a `../` reference from a file inside the workspace. Or the model uses an absolute path printed by a bash command's output.","commonSituations":"Delegation prompts that name files outside the workspace (logs, /etc configs, home-dir files). Subagents quoting absolute paths verbatim from parent instructions. Harness launched from the wrong cwd.","solutions":["Scope every delegated task (and file tool call) to workspace-relative paths","If outside-workspace data is needed, use the bash tool's constrained read (e.g. `cat` with a specific file) rather than the sandboxed file tools, or copy the file into the workspace first","Rewrite absolute in-workspace paths to relative before the tool call"],"exampleFix":"# before (parent -> subagent task)\n\"Read /tmp/errlookup-AdzUmp/docs/plan.md and summarize\"\n# subagent file tool: ValueError: Path escapes workspace\n\n# after\n\"Read docs/plan.md and summarize\"","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef is_delegation_safe_path(p: str, workdir: Path) -> bool:\n    if not isinstance(p, str) or not p:\n        return False\n    try:\n        return (workdir / p).resolve().is_relative_to(workdir)\n    except (OSError, RuntimeError):\n        return False\n\n# parent-side: rewrite the task brief before spawning the subagent\nbrief = brief.replace(str(workdir) + \"/\", \"\") if is_delegation_safe_path(brief_path, workdir) else brief","typeGuard":"def is_relative_child_path(p: object) -> bool:\n    \"\"\"Subagents should only ever receive/emit relative paths.\"\"\"\n    return isinstance(p, str) and p != \"\" and not p.startswith((\"/\", \"~\")) and \"..\" not in Path(p).parts","tryCatchPattern":"try:\n    path = safe_path(p)\nexcept ValueError:\n    return f\"Sandbox rejection for {p}. Subagents must use paths relative to {WORKDIR}; ask the parent for a relative path.\"","preventionTips":["Write delegation briefs with workspace-relative paths only","Have subagents convert any absolute path to relpath before file tool use","Keep outside-workspace data out of subagent scope by design"],"tags":["security","path-traversal","sandbox","subagent","python"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}