{"record":{"id":"9347abe68665c2c8","repo":"shareAI-lab/learn-claude-code","slug":"path-escapes-workspace-p-9347ab","errorCode":null,"errorMessage":"Path escapes workspace: {p}","messagePattern":"Path escapes workspace: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agents/s09_agent_teams.py","lineNumber":259,"sourceCode":"        if not self.config[\"members\"]:\n            return \"No teammates.\"\n        lines = [f\"Team: {self.config['team_name']}\"]\n        for m in self.config[\"members\"]:\n            lines.append(f\"  {m['name']} ({m['role']}): {m['status']}\")\n        return \"\\n\".join(lines)\n\n    def member_names(self) -> list:\n        return [m[\"name\"] for m in self.config[\"members\"]]\n\n\nTEAM = TeammateManager(TEAM_DIR)\n\n\n# -- Base tool implementations (these base tools are unchanged from s02) --\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\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(\n            command, shell=True, cwd=WORKDIR,\n            capture_output=True, text=True, timeout=120,\n        )\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\n","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/agents/s09_agent_teams.py#L241-L277","documentation":"The workspace sandbox guard at agents/s09_agent_teams.py:259 (_safe_path, underscore-prefixed because this script layers team tools on the s02 base). Same semantics: join onto WORKDIR, resolve(), require the result under WORKDIR. It governs the shared base file tools used by team members.","triggerScenarios":"A teammate agent passes an absolute path or `../` traversal to a file tool — often a path quoted verbatim from a teammate message or from the team config (TEAM_DIR files may embed absolute paths). Symlink escapes and cwd mismatch also trigger it.","commonSituations":"Team protocols where one member posts an absolute path and another copies it into a tool call. Harness started outside the workspace. Teammate config files referencing machine-specific paths.","solutions":["Normalize paths to workspace-relative before file tool calls, especially paths received from other teammates","Keep team config and message conventions on relative paths","Launch the harness from the workspace root"],"exampleFix":"# before (teammate message: \"check /tmp/errlookup-AdzUmp/src/api.py\")\n_safe_path(\"/tmp/errlookup-AdzUmp/src/api.py\")\n# ValueError: Path escapes workspace\n\n# after\nimport os\n_safe_path(os.path.relpath(\"/tmp/errlookup-AdzUmp/src/api.py\", WORKDIR))  # \"src/api.py\"","handlingStrategy":"validation","validationCode":"import os\nfrom pathlib import Path\n\ndef normalize_received_path(p: str, workdir: Path) -> str | None:\n    \"\"\"Teammates send paths in any form; make them workspace-relative or drop them.\"\"\"\n    try:\n        cand = Path(p)\n        abs_c = cand if cand.is_absolute() else (workdir / cand)\n        abs_c = abs_c.resolve()\n        return os.path.relpath(abs_c, workdir) if abs_c.is_relative_to(workdir) else None\n    except (OSError, ValueError, RuntimeError):\n        return None\n\nrel = normalize_received_path(teammate_path, WORKDIR)\nassert rel, f\"teammate path {teammate_path} escapes workspace\"","typeGuard":"def is_team_safe_path(p: object, workdir) -> 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","tryCatchPattern":"try:\n    path = _safe_path(p)\nexcept ValueError:\n    return f\"Teammate path {p} rejected by sandbox. Re-issue as a workspace-relative path.\"","preventionTips":["Normalize any path received from a teammate before using it in a tool call","Keep team config files on relative paths","Re-state file locations relatively when relaying work between members"],"tags":["security","path-traversal","sandbox","agent-teams","python"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}