{"record":{"id":"47c04cd51f30d796","repo":"shareAI-lab/learn-claude-code","slug":"path-escapes-workspace-p-47c04c","errorCode":null,"errorMessage":"Path escapes workspace: {p}","messagePattern":"Path escapes workspace: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agents/s06_context_compact.py","lineNumber":138,"sourceCode":"            \"1) What was accomplished, 2) Current state, 3) Key decisions made. \"\n            \"Be concise but preserve critical details.\"\n            f\"{focus_instruction}\\n\\n\" + conversation_text}],\n        max_tokens=2000,\n    )\n    summary = next((block.text for block in response.content if hasattr(block, \"text\")), \"\")\n    if not summary:\n        summary = \"No summary generated.\"\n    # Replace all messages with compressed summary\n    return [\n        {\"role\": \"user\", \"content\": f\"[Conversation compressed. Transcript: {transcript_path}]\\n\\n{summary}\"},\n    ]\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":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/agents/s06_context_compact.py#L120-L156","documentation":"The workspace sandbox guard at agents/s06_context_compact.py:138 in the context-compaction agent. After compaction the transcript is written to a file and only its path plus a summary remain in history; every file tool path (including transcript references) is still checked by safe_path() against WORKDIR.","triggerScenarios":"The model calls a file tool with an absolute transcript path (the compaction message embeds an absolute transcript_path) or an outside/`../` path, and safe_path() rejects it. Also triggered by cwd/workspace mismatch at launch.","commonSituations":"Post-compaction turns where the model tries to re-read the transcript using the absolute path from the \"[Conversation compressed. Transcript: ...]\" message instead of a relative one. Paths copied from earlier tool output that was itself produced before a directory change.","solutions":["Reference the transcript by its workspace-relative path; derive it from the absolute one via os.path.relpath","Keep all file tool arguments relative to WORKDIR","Ensure the harness is launched from the intended workspace directory"],"exampleFix":"# before\nread_file(\"/tmp/errlookup-AdzUmp/transcripts/session_3.jsonl\")\n# ValueError: Path escapes workspace\n\n# after\nread_file(\"transcripts/session_3.jsonl\")","handlingStrategy":"validation","validationCode":"import os\nfrom pathlib import Path\n\ndef transcript_rel(abs_transcript: str, workdir: Path) -> str | None:\n    try:\n        ap = Path(abs_transcript).resolve()\n        return os.path.relpath(ap, workdir) if ap.is_relative_to(workdir) else None\n    except (OSError, ValueError):\n        return None\n\nrel = transcript_rel(msg_transcript_path, WORKDIR)\nassert rel, \"transcript outside workspace; do not re-read it with file tools\"","typeGuard":"def is_post_compaction_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\"{p} escapes the workspace. Convert to a path relative to {WORKDIR} (e.g. transcripts/session_3.jsonl).\"","preventionTips":["Write transcripts inside the workspace so post-compaction reads stay relative","Derive relative paths from the transcript path embedded in the compaction message","Keep file tool arguments relative even after context resets — the sandbox survives compaction"],"tags":["security","path-traversal","sandbox","context-compaction","python"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}