{"record":{"id":"ce0c3e853a939fb5","repo":"shareAI-lab/learn-claude-code","slug":"path-escapes-workspace-p-ce0c3e","errorCode":null,"errorMessage":"Path escapes workspace: {p}","messagePattern":"Path escapes workspace: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agents/s05_skill_loading.py","lineNumber":121,"sourceCode":"            return f\"Error: Unknown skill '{name}'. Available: {', '.join(self.skills.keys())}\"\n        return f\"<skill name=\\\"{name}\\\">\\n{skill['body']}\\n</skill>\"\n\n\nSKILL_LOADER = SkillLoader(SKILLS_DIR)\n\n# Layer 1: skill metadata injected into system prompt\nSYSTEM = f\"\"\"You are a coding agent at {WORKDIR}.\nUse load_skill to access specialized knowledge before tackling unfamiliar topics.\n\nSkills available:\n{SKILL_LOADER.get_descriptions()}\"\"\"\n\n\n# -- Tool implementations --\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":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/agents/s05_skill_loading.py#L103-L139","documentation":"The workspace sandbox guard at agents/s05_skill_loading.py:121 in the skill-loading agent. All file tool calls pass through safe_path(), which resolves the model's path against WORKDIR and rejects anything that escapes it. This variant coexists with load_skill, so failures usually involve paths the model picked up from skill bodies rather than the skill mechanism itself.","triggerScenarios":"A loaded skill's instructions reference files by absolute path or `../` and the model copies them into a file tool call. Or the model tries to read the skill files themselves from outside the workspace (SKILL_LOADER may live elsewhere). Absolute paths and symlink escapes resolve outside WORKDIR and are rejected.","commonSituations":"Skill markdown containing machine-specific absolute paths from the skill author's machine. Model attempting to inspect the skills directory directly instead of via load_skill. Harness cwd mismatch.","solutions":["Keep paths in file tool calls relative to WORKDIR; treat skill-internal paths as data to rewrite, not to follow blindly","Author skills with workspace-relative paths or placeholders","Use load_skill (not raw file reads) to access skill content; if skill files are outside WORKDIR, that is by design"],"exampleFix":"# before\nread_file(\"/home/beagle/.agents/skills/agnt/SKILL.md\")\n# ValueError: Path escapes workspace\n\n# after\nload_skill(\"agnt\")  # content is returned by the skill loader","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef is_skill_safe_path(p: str, workdir: Path) -> bool:\n    try:\n        return (workdir / p).resolve().is_relative_to(workdir)\n    except (OSError, RuntimeError):\n        return False\n\nif not is_skill_safe_path(p, WORKDIR):\n    p = None  # do not call the file tool; use load_skill content instead","typeGuard":"def is_workspace_path_str(p: object) -> bool:\n    return isinstance(p, str) and bool(p) and not p.startswith((\"/\", \"~\")) and \"..\" not in Path(p).parts","tryCatchPattern":"try:\n    path = safe_path(p)\nexcept ValueError:\n    return f\"{p} is outside the workspace. Use load_skill for skill content and relative paths for files.\"","preventionTips":["Access skill content via load_skill, never raw file reads of the skills dir","Treat absolute paths found inside skill bodies as data to rewrite, not paths to follow","Author skills with workspace-relative paths or explicit placeholders"],"tags":["security","path-traversal","sandbox","skills","python"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}