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

  1. Ensure basedir is a real, non-symlinked directory (or pass its realpath)
  2. Remove stray symlinks under basedir that point outside it
  3. 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

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


AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28). Data as JSON: /api/errors/d7d0e3859cfb0a24. Report an issue: GitHub.