agentscope-ai/agentscope · error · PermissionError
Bubblewrap workdir escapes basedir.
Error message
Bubblewrap workdir escapes basedir.
What it means
Raised while computing the per-workspace directory: after realpath resolution, the path built from user_id/workspace_id components no longer lies inside basedir. This is a path-traversal guard, so it raises PermissionError even if the escape is only due to symlink resolution.
Source
Thrown at src/agentscope/app/workspace_manager/_bubblewrap_workspace_manager.py:115
self._ttl = ttl
self._sweep_interval = sweep_interval
super().__init__(isolation=isolation)
self._cache: dict[str, tuple[BubblewrapWorkspace, float]] = {}
self._lock = asyncio.Lock()
self._sweep_task: asyncio.Task[None] | None = None
def _workdir_for(self, user_id: str, workspace_id: str) -> str:
"""Resolve the host workdir for ``(user_id, workspace_id)``."""
path = os.path.join(
self._basedir,
_safe_component(user_id),
_safe_component(workspace_id),
)
basedir = os.path.realpath(self._basedir)
real_path = os.path.realpath(path)
if os.path.commonpath([basedir, real_path]) != basedir:
raise PermissionError("Bubblewrap workdir escapes basedir.")
return path
async def _build_and_start(
self,
*,
workspace_id: str,
user_id: str,
agent_id: str,
) -> BubblewrapWorkspace:
"""Construct and initialize a Bubblewrap workspace."""
del agent_id
workdir = self._workdir_for(user_id, workspace_id)
os.makedirs(workdir, mode=0o700, exist_ok=True)
os.chmod(workdir, 0o700)
ws = BubblewrapWorkspace(
workspace_id=workspace_id,
host_workdir=workdir,
gateway_port=self._gateway_port,View on GitHub (pinned to e90f1c7592)
Solutions
- Ensure basedir is a real, non-symlinked directory (or pass its realpath)
- Remove stray symlinks under basedir that point outside it
- Sanitize user_id/workspace_id before they reach the manager if they come from untrusted input
Example fix
# before
mgr = BubblewrapWorkspaceManager(basedir="/tmp/ws") # /tmp is a symlink on macOS
# after
import os
mgr = BubblewrapWorkspaceManager(basedir=os.path.realpath("/tmp/ws")) Defensive patterns
Strategy: try-catch
Validate before calling
base = os.path.realpath(basedir)\nwd = os.path.realpath(os.path.join(base, user_id, workspace_id))\nassert os.path.commonpath([base, wd]) == base
Try / catch
try:\n await mgr._build_and_start(...)\nexcept PermissionError:\n cleanup_symlinks(basedir); retry
Prevention
- Pass realpath(basedir) when constructing the manager
- Disallow symlinks inside the workspace tree via policy/monitoring
When it happens
Trigger: A symlink inside basedir pointing outside it such that realpath(workdir) leaves basedir; or user_id/workspace_id components containing '../' style content that survives _safe_component sanitization.
Common situations: basedir itself contains a symlink (e.g. /tmp -> /private/tmp style, or a linked data volume); leftover attacker-controlled or hand-crafted symlinks in the workspace tree; unusual filesystem layouts where basedir was not realpath-normalized on the manager side.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- workspace_id {workspace_id!r} escapes the workspace base dir
- Unsafe upload path: {entry.path!r}
- Blob key {key!r} escapes the root directory.
- basedir must not be empty.
- BubblewrapWorkspaceManager currently requires share_net=True
AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28).
Data as JSON: /api/errors/d7d0e3859cfb0a24.
Report an issue: GitHub.