agentscope-ai/agentscope · error · ValueError

Skill path {skill_path!r} resolves outside skills_dir.

Error message

Skill path {skill_path!r} resolves outside skills_dir.

What it means

Raised by LocalWorkspace.add_skill when the computed destination path inside skills_dir resolves (via realpath) outside the skills directory itself. This is a path-traversal guard: the skill's directory name, after symlink resolution, must stay within skills_dir + os.sep. It protects the agent's skill partition from escapes via symlinks or crafted directory names.

Source

Thrown at src/agentscope/workspace/_local_workspace.py:841

            counter = 1
            while agent_name in existing_agent_names:
                agent_name = f"{raw_name} ({counter})"
                counter += 1

            # Resolve directory name conflict
            base_dir = _sanitize_dir_name(raw_name)
            dir_name = base_dir
            counter = 1
            while dir_name in existing_dir_names:
                dir_name = f"{base_dir}_{counter}"
                counter += 1

            dest_path = os.path.join(skills_dir, dir_name)

            if not os.path.realpath(dest_path).startswith(
                os.path.realpath(skills_dir) + os.sep,
            ):
                raise ValueError(
                    f"Skill path {skill_path!r} resolves outside skills_dir.",
                )

            await asyncio.to_thread(
                shutil.copytree,
                skill_path,
                dest_path,
                dirs_exist_ok=False,
            )

            logger.info(
                "Copied skill '%s' (agent name: '%s') from %s to %s",
                raw_name,
                agent_name,
                skill_path,
                dest_path,
            )

View on GitHub (pinned to e90f1c7592)

Solutions

  1. Pass the canonical skills_dir: use os.path.realpath on the configured skills directory before constructing the workspace.
  2. Ensure the skill source directory is a real directory, not a symlink pointing outside skills_dir.
  3. Check dir_name derived from the skill for path separators or '..' and rename the skill folder to a plain directory name.
  4. Re-run add_skill after fixing so realpath(dest) starts with realpath(skills_dir) + os.sep.

Example fix

# before
ws = LocalWorkspace(skills_dir="/tmp/skills")  # /tmp -> /private/tmp symlink
await ws.add_skill("./my-skill")

# after
import os
ws = LocalWorkspace(skills_dir=os.path.realpath("/tmp/skills"))
await ws.add_skill("./my-skill")
Defensive patterns

Strategy: validation

Validate before calling

import os

def skill_dest_is_safe(skills_dir: str, skill_path: str) -> bool:
    root = os.path.realpath(skills_dir)
    dest = os.path.realpath(os.path.join(skills_dir, os.path.basename(skill_path.rstrip("/"))))
    return dest.startswith(root + os.sep)

Try / catch

try:
    await ws.add_skill(path)
except ValueError as e:
    if "resolves outside skills_dir" in str(e):
        raise ValueError(f"symlinked skill dir not allowed: {path}") from e
    raise

Prevention

When it happens

Trigger: Calling add_skill with a skill directory whose name resolves oddly (e.g. '..'-containing names), or when skills_dir itself is (or contains) a symlink such that realpath(dest_path) no longer shares the realpath(skills_dir) prefix; also a skills_dir that resolves to the filesystem root.

Common situations: Skills directory placed under a symlinked path (e.g. /tmp on macOS symlinked to /private/tmp) where skills_dir passed in is not the realpath, or a skill folder that is itself a symlink pointing elsewhere.

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