{"record":{"id":"87713dcf06ad2c00","repo":"shareAI-lab/learn-claude-code","slug":"path-escapes-workspace-p-87713d","errorCode":null,"errorMessage":"Path escapes workspace: {p}","messagePattern":"Path escapes workspace: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agents/s_full.py","lineNumber":77,"sourceCode":"\nTEAM_DIR = WORKDIR / \".team\"\nINBOX_DIR = TEAM_DIR / \"inbox\"\nTASKS_DIR = WORKDIR / \".tasks\"\nSKILLS_DIR = WORKDIR / \"skills\"\nTRANSCRIPT_DIR = WORKDIR / \".transcripts\"\nTOKEN_THRESHOLD = 100000\nPOLL_INTERVAL = 5\nIDLE_TIMEOUT = 60\n\nVALID_MSG_TYPES = {\"message\", \"broadcast\", \"shutdown_request\",\n                   \"shutdown_response\", \"plan_approval_response\"}\n\n\n# === SECTION: base_tools ===\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":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/agents/s_full.py#L59-L95","documentation":"Same safe_path() containment guard as in the other session modules, duplicated into the consolidated agent s_full.py. It resolves WORKDIR-joined paths and rejects anything landing outside WORKDIR, protecting the file tools from path traversal. Because it appears in the merged module, every file tool in the full agent (read, write, edit, etc.) inherits the same restriction.","triggerScenarios":"A tool call with \"../secrets.env\", an absolute path from a prior error message, or a workspace symlink chain escaping WORKDIR after resolve(). Any file tool in s_full.py (run_read/run_write/run_edit) receives the path.","commonSituations":"Agent copies an absolute path out of a traceback or git output and feeds it back to a file tool; nested symlinked node_modules-style layouts resolving outside the tree; WORKDIR misconfigured at agent startup.","solutions":["Always pass paths relative to WORKDIR and without leading '..' segments","Normalize absolute in-workspace paths with Path(p).relative_to(WORKDIR) before the call","Audit symlinks inside the workspace with find WORKDIR -type l and remove ones pointing outside","Verify the WORKDIR value used at agent construction matches where files actually live"],"exampleFix":"// before\nrun_edit(\"/home/user/proj/src/app.py\", ...)\n// after\nrun_edit(\"src/app.py\", ...)","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef workspace_relative(p: str) -> str | None:\n    ap = Path(p)\n    if not ap.is_absolute():\n        ap = (WORKDIR / p)\n    try:\n        return str(ap.resolve().relative_to(WORKDIR.resolve()))\n    except ValueError:\n        return None  # escapes workspace — do not call the tool\n\nif (rel := workspace_relative(p)) is not None:\n    run_read(rel)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Strip absolute prefixes from agent-echoed paths before reuse","Keep all working files inside WORKDIR","Treat this error as a security signal — log it, do not silently rewrite the path"],"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"}