fix(webui): harden skill management

This commit is contained in:
Xubin Ren
2026-07-30 01:13:37 +08:00
parent d276fe1386
commit 8a56eb06ad
16 changed files with 422 additions and 8 deletions
+10 -1
View File
@@ -10,6 +10,7 @@ from typing import Any
from nanobot.agent.skills import SkillsLoader
from nanobot.config.loader import load_config, save_config
from nanobot.security.workspace_policy import WorkspaceBoundaryError, require_path_within
class SkillManagementError(Exception):
@@ -100,7 +101,15 @@ def delete_webui_skill(
if entry.get("source") != "workspace":
raise SkillManagementError("built-in skills cannot be deleted", status=403)
skills_root = (workspace_path.expanduser().resolve() / "skills").resolve()
workspace = workspace_path.expanduser().resolve()
try:
skills_root = require_path_within(
workspace / "skills",
workspace,
message="skills directory must stay inside the workspace",
)
except WorkspaceBoundaryError as exc:
raise SkillManagementError(str(exc), status=403) from exc
target = skills_root / name
if target.parent != skills_root:
raise SkillManagementError("invalid skill name")
+21 -3
View File
@@ -19,6 +19,7 @@ import httpx
from nanobot.agent.skills import SkillsLoader
from nanobot.security.network import PinnedDNSAsyncTransport
from nanobot.security.workspace_policy import WorkspaceBoundaryError, require_path_within
_PROVIDER_ALL = "all"
_PROVIDER_SKILLS_SH = "skills_sh"
@@ -353,6 +354,17 @@ async def _install_skills_sh_skill(
if skill_id in existing:
return {"installed": True, "already_installed": True, "name": skill_id}
workspace = workspace_path.expanduser().resolve()
workspace.mkdir(parents=True, exist_ok=True)
try:
require_path_within(
workspace / "skills",
workspace,
message="skills directory must stay inside the workspace",
)
except WorkspaceBoundaryError as exc:
raise SkillsMarketplaceError(str(exc), status=403) from exc
npx = shutil.which("npx")
if npx is None:
raise SkillsMarketplaceError(
@@ -360,8 +372,6 @@ async def _install_skills_sh_skill(
status=503,
)
workspace = workspace_path.expanduser().resolve()
workspace.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["DISABLE_TELEMETRY"] = "1"
command = (
@@ -439,7 +449,15 @@ async def _install_skillhub_skill(
}
workspace = workspace_path.expanduser().resolve()
skills_root = workspace / "skills"
workspace.mkdir(parents=True, exist_ok=True)
try:
skills_root = require_path_within(
workspace / "skills",
workspace,
message="skills directory must stay inside the workspace",
)
except WorkspaceBoundaryError as exc:
raise SkillsMarketplaceError(str(exc), status=403) from exc
skills_root.mkdir(parents=True, exist_ok=True)
target = skills_root / skill_id