{"record":{"id":"77687dcb198f4f81","repo":"shareAI-lab/learn-claude-code","slug":"path-escapes-workspace-p-77687d","errorCode":null,"errorMessage":"Path escapes workspace: {p}","messagePattern":"Path escapes workspace: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s13_agent_teams/code.py","lineNumber":612,"sourceCode":"        \"only; it is not a sandbox. Worktree removal stays with the host or \"\n        \"user. After spawning a teammate, end the current turn instead of \"\n        \"polling its status; the runtime will deliver team events and wake the \"\n        \"Lead. React to those events, and shut teammates down when \"\n        \"coordination is complete.\"\n    ),\n    \"workspace\": f\"Working directory: {WORKDIR}\",\n}\n\nSYSTEM = \"\\n\\n\".join(PROMPT_SECTIONS.values())\n\n\n# -- Base Tools --\n\ndef safe_path(p: str, cwd: Path | None = None) -> Path:\n    base = (cwd or WORKDIR).resolve()\n    path = (base / p).resolve()\n    if not path.is_relative_to(base):\n        raise ValueError(f\"Path escapes workspace: {p}\")\n    return path\n\n\ndef run_bash(command: str, cwd: Path | None = None) -> str:\n    try:\n        result = subprocess.run(\n            command,\n            shell=True,\n            cwd=cwd or WORKDIR,\n            capture_output=True,\n            text=True,\n            timeout=120,\n        )\n        output = (result.stdout + result.stderr).strip()\n        output = output[:50000] if output else \"(no output)\"\n        if result.returncode:\n            return f\"Error: command exited with status {result.returncode}\\n{output}\"\n        return output","sourceCodeStart":594,"sourceCodeEnd":630,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s13_agent_teams/code.py#L594-L630","documentation":"safe_path() resolves a user/model-supplied relative path against a base (default WORKDIR) and raises if the resolved path is not inside that base. It is the guardrail that keeps every file-oriented tool from touching anything outside the workspace, including '../' escapes and absolute paths that resolve elsewhere.","triggerScenarios":"safe_path('../outside.txt'), safe_path('/etc/passwd') (absolute path joined then resolved outside base), or a path whose intermediate symlink component points outside the workspace; also passing a path with cwd= pointing to a directory that doesn't contain the result.","commonSituations":"Model-generated file tool calls referencing parent directories or absolute paths; symlinks inside the repo pointing to dotfiles elsewhere; tests calling tools with repo-external fixtures.","solutions":["Pass paths relative to the workspace root and stay inside it.","If a file legitimately lives outside, copy it into the workspace first.","For nested agents, pass the agent's worktree as cwd so its relative paths resolve inside its own scope.","Remove symlinks inside the workspace that point outside, or expect them to be rejected."],"exampleFix":"// before\nread_file(safe_path('../../shared/config.yaml'))  // ValueError\n\n// after\nrun_bash('cp /shared/config.yaml ./config.yaml')\nread_file(safe_path('config.yaml'))","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef path_in_workspace(p: str, base: Path | None = None) -> bool:\n    base = (base or WORKDIR).resolve()\n    return (base / p).resolve().is_relative_to(base)","typeGuard":null,"tryCatchPattern":"try:\n    resolved = safe_path(user_path)\nexcept ValueError as exc:\n    if 'escapes workspace' in str(exc):\n        return error_to_model(f'{user_path!r} is outside the workspace; use a relative path')\n    raise","preventionTips":["Always call file tools with paths relative to WORKDIR (or the agent's worktree cwd).","Copy external files into the workspace instead of referencing absolute paths.","For nested agents, pass their worktree as cwd so their relative paths resolve inside it."],"tags":["security","path-traversal","sandbox","validation"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}