{"record":{"id":"5c4b11335273de9f","repo":"shareAI-lab/learn-claude-code","slug":"path-escapes-workspace-p-5c4b11","errorCode":null,"errorMessage":"Path escapes workspace: {p}","messagePattern":"Path escapes workspace: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agents/s08_background_tasks.py","lineNumber":118,"sourceCode":"            lines.append(f\"{tid}: [{t['status']}] {t['command'][:60]}\")\n        return \"\\n\".join(lines) if lines else \"No background tasks.\"\n\n    def drain_notifications(self) -> list:\n        \"\"\"Return and clear all pending completion notifications.\"\"\"\n        with self._lock:\n            notifs = list(self._notification_queue)\n            self._notification_queue.clear()\n        return notifs\n\n\nBG = BackgroundManager()\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":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/agents/s08_background_tasks.py#L100-L136","documentation":"The workspace sandbox guard at agents/s08_background_tasks.py:118 in the background-tasks agent. It applies to the synchronous file tools of this script; BackgroundManager processes run shell commands in the background and are not routed through safe_path, so this error always comes from a direct file tool call.","triggerScenarios":"A file tool call with an absolute or `../` path, or a path through an outward symlink. Common variant: the model inspects background job output files by their absolute path (e.g. under /tmp) instead of a workspace-relative log path.","commonSituations":"Background jobs writing logs outside the workspace, then the model trying to read those logs with the sandboxed file tool. cwd mismatch at launch.","solutions":["Direct background jobs to write their logs inside the workspace (e.g. bg_*.log at the root) so later reads are relative","Pass workspace-relative paths to file tools","Use the background manager's own output/notification APIs rather than raw file reads for job output"],"exampleFix":"# before\nrun_bg(\"pytest > /tmp/job.log\", name=\"tests\")\nread_file(\"/tmp/job.log\")  # ValueError: Path escapes workspace\n\n# after\nrun_bg(\"pytest > job_tests.log\", name=\"tests\")\nread_file(\"job_tests.log\")","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef is_bg_log_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\n# at job launch, force logs into the workspace:\ncommand = command_with_redirects_into(\"bg_logs/\")  # e.g. \"pytest > bg_logs/tests.log 2>&1\"\nassert is_bg_log_path(\"bg_logs/tests.log\", WORKDIR)","typeGuard":"def is_workspace_log_path(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. Redirect background output into the workspace (e.g. bg_<name>.log) and read it relatively.\"","preventionTips":["Redirect background job output to files inside the workspace at launch time","Use the background manager's notification/output APIs instead of raw reads","Never pass /tmp-style absolute log paths to the sandboxed file tools"],"tags":["security","path-traversal","sandbox","background-tasks","python"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}