{"record":{"id":"a4ddedceafcf4af1","repo":"shareAI-lab/learn-claude-code","slug":"path-escapes-workspace-path","errorCode":null,"errorMessage":"Path escapes workspace: {path}","messagePattern":"Path escapes workspace: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s15_integrated_harness/code.py","lineNumber":804,"sourceCode":"                    \"\\nUse load_skill(name) when a skill is relevant.\")\n    if context.get(\"memory_catalog\"):\n        sections.append(f\"Memory catalog:\\n{context['memory_catalog']}\")\n    if context.get(\"memories\"):\n        sections.append(f\"Relevant memory records:\\n{context['memories']}\")\n    mcp_names = list(mcp_clients.keys())\n    if mcp_names:\n        sections.append(f\"Connected MCP servers: {', '.join(mcp_names)}\")\n    return \"\\n\\n\".join(sections)\n\n\n# -- Basic Tools --\n\n\ndef safe_path(path: str, cwd: Path | None = None) -> Path:\n    base = (cwd or WORKDIR).resolve()\n    resolved = (base / path).resolve()\n    if not resolved.is_relative_to(base):\n        raise ValueError(f\"Path escapes workspace: {path}\")\n    return resolved\n\n\n_shell_processes: set[subprocess.Popen] = set()\n_shell_process_lock = threading.RLock()\n\n\ndef _stop_process_group(process: subprocess.Popen):\n    \"\"\"Stop processes that remain in the command's original process group.\"\"\"\n    for sig in (signal.SIGTERM, signal.SIGKILL):\n        try:\n            os.killpg(process.pid, sig)\n        except ProcessLookupError:\n            return\n        except OSError:\n            return\n        time.sleep(0.05)\n","sourceCodeStart":786,"sourceCodeEnd":822,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s15_integrated_harness/code.py#L786-L822","documentation":"safe_path() resolves a user/model-supplied relative path against a base (the given cwd or WORKDIR) and requires the result to stay inside that base. Any absolute path outside the workspace, or a relative path using ../ that climbs out, is rejected before any file operation. It is the central confinement guard for read/write/edit/bash tools.","triggerScenarios":"Passing '/etc/passwd' or '~/secrets' (unexpanded) to a file tool; 'build/../../outside.txt'; a cwd argument that is itself outside WORKDIR combined with a relative path escaping it. Note ~ is NOT expanded, so '~/x' resolves under base as a literal '~' directory — paths outside base raise here.","commonSituations":"Model writing absolute paths from error messages; agents following a symlinked include to a system file; scripts assuming home-directory access inside a sandboxed harness.","solutions":["Use paths relative to the workspace root (or the tool's cwd) and stay inside it.","If a file legitimately lives outside, copy it into the workspace first with shell access permitted by policy.","Expand and pre-check paths caller-side: resolve against base and confirm is_relative_to before calling file tools."],"exampleFix":"// before\nread_file(\"../../etc/hosts\")\n\n// after\nread_file(\"notes/etc-hosts-copy.txt\")  // work inside WORKDIR","handlingStrategy":"type-guard","validationCode":"from pathlib import Path\n\ndef inside_workspace(path: str, base: Path) -> bool:\n    try:\n        return (base / path).resolve().is_relative_to(base.resolve())\n    except (TypeError, ValueError):\n        return False","typeGuard":"def is_safe_relative(path: str) -> bool:\n    return isinstance(path, str) and not path.startswith((\"/\", \"~\")) and \"..\" not in Path(path).parts","tryCatchPattern":"try:\n    resolved = safe_path(path, cwd)\nexcept ValueError as e:\n    if \"escapes workspace\" in str(e):\n        return f\"Error: {e}. Use a path inside the workspace.\"\n    raise","preventionTips":["Always use workspace-relative paths in tool calls.","Reject absolute paths and '..' segments before calling file tools.","Remember '~' is not expanded — pass real relative paths."],"tags":["security","path-traversal","sandbox","filesystem"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}