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
- Normalize/validate workspace_id to a single safe path component (alnum, dash, underscore) before starting a workspace
- Use generated uuids as workspace ids rather than free-form strings
- 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
- Generate workspace ids as uuids
- Never build ids from user free text
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
- Bubblewrap workdir escapes basedir.
- Unsafe upload path: {entry.path!r}
- Blob key {key!r} escapes the root directory.
- unsafe tar member: ' + m.name
- unsafe archive member: ' + name
AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28).
Data as JSON: /api/errors/199d5badd1102bc5.
Report an issue: GitHub.