agentscope-ai/agentscope · error · ValueError

workspace_id {workspace_id!r} escapes the workspace base dir

Error message

workspace_id {workspace_id!r} escapes the workspace base directory

What it means

DockerWorkspaceManager._workdir_for computes realpath(basedir/workspace_id) and rejects ids whose resolved path falls outside basedir, guarding against '../' traversal in workspace_id.

Source

Thrown at src/agentscope/app/workspace_manager/_docker_workspace_manager.py:184

        filesystem. Anything not landing strictly inside is rejected.

        Args:
            workspace_id (`str`):
                The workspace whose bind mount is being resolved.
            user_id (`str`, defaults to `""`):
                Owner of the workspace, for the legacy layout below.
                Empty for a workspace nobody owns yet.
            agent_id (`str`, defaults to `""`):
                Agent of the workspace, for the legacy layout below.

        Raises:
            `ValueError`:
                If ``workspace_id`` resolves outside ``basedir``.
        """
        root = os.path.realpath(self._basedir)
        workdir = os.path.realpath(os.path.join(root, workspace_id))
        if not workdir.startswith(root + os.sep):
            raise ValueError(
                f"workspace_id {workspace_id!r} escapes the workspace "
                f"base directory",
            )
        if os.path.isdir(workdir) or not (user_id and agent_id):
            return workdir

        # Workspaces built before the id-keyed layout live under
        # ``<basedir>/<user_id>/<agent_id>``. Keep mounting such a
        # directory where it stands: several workspace ids may share
        # one, so no rename can move them all. Legacy paths escaping
        # ``basedir`` are declined rather than rejected, leaving the
        # caller with an ordinary empty workspace.
        legacy = os.path.realpath(os.path.join(root, user_id, agent_id))
        if legacy.startswith(root + os.sep) and os.path.isdir(legacy):
            return legacy
        return workdir

    # ── workspace construction ────────────────────────────────────

View on GitHub (pinned to e90f1c7592)

Solutions

  1. Normalize/validate workspace_id to a single safe path component (alnum, dash, underscore) before starting a workspace
  2. Use generated uuids as workspace ids rather than free-form strings
  3. Use realpath(basedir) when constructing the manager to avoid symlink-induced escapes

Example fix

# before
await mgr._build_and_start(workspace_id="../../etc", ...)
# after
import re
ws = re.sub(r"[^A-Za-z0-9_-]", "-", ws)
await mgr._build_and_start(workspace_id=ws, ...)
Defensive patterns

Strategy: validation

Validate before calling

import re
assert re.fullmatch(r\"[A-Za-z0-9_-]+\", workspace_id), \"unsafe workspace id\"

Type guard

def is_safe_workspace_id(ws_id: str) -> bool:\n    import re\n    return bool(re.fullmatch(r\"[A-Za-z0-9_.-]+\", ws_id)) and \"..\" not in ws_id

Try / catch

try:\n    await mgr._build_and_start(workspace_id=ws, ...)\nexcept ValueError as e:\n    if \"escapes\" in str(e): ws = sanitize(ws); retry\n    else: raise

Prevention

When it happens

Trigger: Passing a workspace_id containing '..' segments, absolute-path-like content, or values whose realpath (via pre-existing symlinks) escapes basedir during _build_and_start.

Common situations: User-supplied or LLM-generated workspace ids containing traversal sequences; symlinked basedir directories; migrating workspace ids between layouts with prefix '../'.

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/199d5badd1102bc5. Report an issue: GitHub.