{"record":{"id":"a86818fdebd304e7","repo":"iflytek/astron-agent","slug":"path-must-stay-inside-the-skill-workspace","errorCode":null,"errorMessage":"Path must stay inside the Skill workspace","messagePattern":"Path must stay inside the Skill workspace","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"core/agent/service/plugin/skill_sandbox.py","lineNumber":531,"sourceCode":"        return PluginResponse(\n            result={\n                \"skill_id\": self.skill_id,\n                \"configured\": False,\n                \"message\": SCRIPT_SANDBOX_UNCONFIGURED_MESSAGE,\n            }\n        )\n\n    def _normalize_relative_path(self, value: Any, default: str) -> str:\n        path = str(value or default).strip().replace(\"\\\\\", \"/\")\n        if not path or path == \".\":\n            return \".\"\n        normalized = posixpath.normpath(path)\n        if (\n            normalized.startswith(\"/\")\n            or normalized == \"..\"\n            or normalized.startswith(\"../\")\n        ):\n            raise ValueError(\"Path must stay inside the Skill workspace\")\n        return normalized\n\n\nclass SkillSandboxConfig(BaseModel):\n    enabled: bool = False\n    workflow_id: str = \"\"\n    run_id: str = \"\"\n    node_id: str = \"\"\n    uid: str = \"\"\n    space_id: str = \"\"\n\n\nclass SandboxExecutionRequest(BaseModel):\n    skill_id: str\n    command: str\n    stdin: Any = None\n    working_dir: str = \".\"\n    output_dir: str = \"output\"","sourceCodeStart":513,"sourceCodeEnd":549,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/agent/service/plugin/skill_sandbox.py#L513-L549","documentation":"_normalize_relative_path normalizes a caller-supplied relative path with posixpath.normpath and rejects any result that is absolute ('/...'), equal to '..', or starting with '../'. It raises ValueError('Path must stay inside the Skill workspace') to block path-traversal out of the skill workspace directory.","triggerScenarios":"Passing a path like '/etc/passwd', '../secret', '..', or a Windows-style path that normalizes to escape the workspace (e.g. '..\\\\..\\\\x' becomes '../x') to any API that resolves paths relative to the skill workspace.","commonSituations":"Hardcoded config from another environment using absolute paths; user-controlled workflow parameters carrying '../'; mixing Windows backslash separators; copy-pasted container paths like '/home/user/skill/out'.","solutions":["Strip any workspace prefix and pass only a workspace-relative path (e.g. 'output' instead of '/home/user/skill/output')","Remove leading '../' segments and leading slashes from the configured path","Sanitize user-supplied paths before passing them into the skill request","Use forward slashes and relative segments only"],"exampleFix":"// before\nworking_dir = '/home/user/skill/output'\n// after\nworking_dir = 'output'","handlingStrategy":"validation","validationCode":"import posixpath\ndef assert_safe_relpath(p: str) -> str:\n    n = posixpath.normpath(str(p or '').strip().replace('\\\\', '/'))\n    if n.startswith('/') or n == '..' or n.startswith('../'):\n        raise ValueError('Path must stay inside the Skill workspace')\n    return n\nassert_safe_relpath(user_path)","typeGuard":"def is_safe_relpath(p: str) -> bool:\n    n = posixpath.normpath(str(p or '').strip().replace('\\\\', '/'))\n    return bool(n) and n != '.' and not n.startswith('/') and not n.startswith('..')","tryCatchPattern":"try:\n    rel = provider._normalize_relative_path(user_path, default='.')\nexcept ValueError:\n    rel = '.'\n    logger.warning('unsafe path rejected, falling back to workspace root')","preventionTips":["Always store workspace-relative paths in config","Sanitize any user-supplied path with normpath before submitting","Convert Windows backslashes to forward slashes early","Never prefix paths with the container workspace directory"],"tags":["path-traversal","validation","security","workspace"],"backgroundTag":"path-traversal-blocked","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-19T12:17:13.211Z"}