HKUDS/Vibe-Trading · warning · WorkspaceScopeError
workspace root must stay under {default_root}
Error message
workspace root must stay under {default_root} What it means
Raised by _scope_from_envelope when an incoming envelope's workspace root resolves outside the gateway's configured workspace_path and default_restrict_to_workspace is enabled. It is a security guard preventing clients from scoping the agent to arbitrary filesystem locations. The raised type is WorkspaceScopeError.
Source
Thrown at agent/src/channelsui/gateway_services.py:131
"""Return the active scope for a message."""
del chat_running, controls_available
return self._scope_from_envelope(envelope) or self._scopes.get(chat_id) or self._default_scope()
def persist_scope(self, chat_id: str, scope: WorkspaceScope) -> None:
"""Persist an in-memory scope for the current gateway process."""
self._scopes[chat_id] = scope
def _scope_from_envelope(self, envelope: dict[str, Any]) -> WorkspaceScope | None:
raw = envelope.get("workspace_scope")
if not isinstance(raw, dict):
return None
root = raw.get("root")
if not isinstance(root, str) or not root.strip():
return None
resolved = Path(root).expanduser().resolve()
default_root = self.workspace_path.expanduser().resolve()
if self.default_restrict_to_workspace and not _is_relative_to(resolved, default_root):
raise WorkspaceScopeError(f"workspace root must stay under {default_root}")
restrict = raw.get("restrict_to_workspace", self.default_restrict_to_workspace)
return WorkspaceScope(root=str(resolved), restrict_to_workspace=bool(restrict))
class SimpleHttpRouter:
"""Small HTTP fallback router for WebSocket server requests."""
def workspace_controls_available(self, connection: Any) -> bool:
"""Return whether workspace controls may be shown to this connection."""
del connection
return True
async def dispatch(self, connection: Any, request: Any) -> Any:
"""Return a compact JSON 404 for non-WebSocket HTTP requests."""
del request
return connection.respond(
404,
json.dumps({"detail": "not found"}, ensure_ascii=False),View on GitHub (pinned to 80ffdda44c)
Solutions
- Set root in the envelope to a directory inside the gateway workspace_path.
- If cross-root access is intentional and trusted, disable default_restrict_to_workspace on the gateway config.
- Check for symlinks that resolve outside the root and adjust the path accordingly.
Example fix
# before
envelope = {"root": "/etc"}
# after
envelope = {"root": "/srv/workspace/project-a"} # inside workspace_path Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def safe_root(raw_root: str, workspace: str) -> str | None:
resolved = Path(raw_root).expanduser().resolve()
ws = Path(workspace).expanduser().resolve()
try:
resolved.relative_to(ws)
return str(resolved)
except ValueError:
return None # caller should reject/fix before sending the envelope Try / catch
try:
scope = gateway.scope_for_message(envelope)
except WorkspaceScopeError as e:
scope = fallback_default_scope() # fall back to the default workspace root Prevention
- Always send roots relative to the known workspace_path
- Resolve symlinks client-side before sending the envelope
- Keep restrict_to_workspace enabled except in fully trusted deployments
When it happens
Trigger: scope_for_new_chat / scope_for_set_request / scope_for_message receiving a root that is absolute, symlinked, or '..'-escaping beyond the configured workspace root while restrict_to_workspace defaults to true.
Common situations: Client sends a root like '/etc' or '/home/other-user'; symlinks inside the workspace pointing outside; containerized deployment where the client's paths don't match the server's mount layout.
Related errors
- invalid alpha_id
- alpha_id not found
- invalid period: {exc}
- too many running benches; wait for one to finish
- invalid job_id
AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28).
Data as JSON: /api/errors/2ff299525dafc081.
Report an issue: GitHub.