Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions astrbot/core/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,24 +95,24 @@ def workspace_path_to_root(path: str) -> Path:

Args:
path: Stored workspace path. Relative values are rooted under AstrBot
workspaces. Absolute values are allowed and resolved as provided.
workspaces. Absolute values must also stay within AstrBot workspaces.

Returns:
Absolute resolved path.

Raises:
ValueError: If a relative path escapes or targets the AstrBot workspaces
root.
ValueError: If the path escapes or targets the AstrBot workspaces root.
"""
workspaces_root = Path(get_astrbot_workspaces_path()).resolve(strict=False)
candidate = Path(path).expanduser()
if candidate.is_absolute():
return candidate.resolve(strict=False)

resolved = (workspaces_root / candidate).resolve(strict=False)
resolved = (
candidate.resolve(strict=False)
if candidate.is_absolute()
else (workspaces_root / candidate).resolve(strict=False)
)
if resolved == workspaces_root or not resolved.is_relative_to(workspaces_root):
raise ValueError(
"Relative workspace path must stay within a subdirectory of AstrBot workspaces"
"Custom workspace path must stay within a subdirectory of AstrBot workspaces"
)
return resolved

Expand Down
38 changes: 29 additions & 9 deletions tests/unit/test_chatui_project_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ def test_custom_workspace_rejects_file_path(tmp_path, monkeypatch):
)


def test_custom_workspace_relative_path_uses_astrbot_workspaces(
tmp_path, monkeypatch
):
def test_custom_workspace_relative_path_uses_astrbot_workspaces(tmp_path, monkeypatch):
"""Relative custom workspace paths should resolve under AstrBot workspaces."""
relative_workspace = tmp_path / "relative-workspace"
relative_workspace.mkdir()
Expand Down Expand Up @@ -118,14 +116,14 @@ def test_custom_workspace_rejects_workspaces_root(tmp_path, monkeypatch):
)


def test_custom_workspace_accepts_absolute_path_outside_workspaces(
def test_custom_workspace_accepts_absolute_path_inside_workspaces(
tmp_path, monkeypatch
):
"""Absolute custom workspace paths may point outside AstrBot workspaces."""
outside_workspace = tmp_path / "outside"
"""Absolute custom workspace paths may point inside AstrBot workspaces."""
workspaces_root = tmp_path / "workspaces"
outside_workspace.mkdir()
workspaces_root.mkdir()
custom_workspace = workspaces_root / "custom"
custom_workspace.mkdir()
monkeypatch.setattr(
"astrbot.core.workspace.get_astrbot_workspaces_path",
lambda: str(workspaces_root),
Expand All @@ -134,9 +132,31 @@ def test_custom_workspace_accepts_absolute_path_outside_workspaces(
workspace_type, workspace_path = ChatUIProjectService._normalize_workspace_config(
{
"workspace_type": "custom",
"workspace_path": str(outside_workspace),
"workspace_path": str(custom_workspace),
}
)

assert workspace_type == "custom"
assert workspace_path == str(outside_workspace)
assert workspace_path == str(custom_workspace)


def test_custom_workspace_rejects_absolute_path_outside_workspaces(
tmp_path, monkeypatch
):
"""Absolute custom workspace paths must not escape AstrBot workspaces."""
outside_workspace = tmp_path / "outside"
workspaces_root = tmp_path / "workspaces"
outside_workspace.mkdir()
workspaces_root.mkdir()
monkeypatch.setattr(
"astrbot.core.workspace.get_astrbot_workspaces_path",
lambda: str(workspaces_root),
)

with pytest.raises(ChatUIProjectServiceError, match="must stay within"):
ChatUIProjectService._normalize_workspace_config(
{
"workspace_type": "custom",
"workspace_path": str(outside_workspace),
}
)
Loading