{"record":{"id":"ee2ed112b303d1dd","repo":"siyuan-note/siyuan","slug":"path-escapes-workspace-s-ee2ed1","errorCode":null,"errorMessage":"path escapes workspace: %s","messagePattern":"path escapes workspace: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/tools/file.go","lineNumber":103,"sourceCode":"\t\tContent: []ContentItem{{Type: \"text\", Text: \"unknown action '\" + action + \"', expected one of: [list, read, write, delete, rename, copy, grep, find, stat]\"}},\n\t\tIsError: true,\n\t}, nil\n}\n\nfunc resolvePath(rel string) (string, error) {\n\trel = filepath.Clean(strings.ReplaceAll(rel, \"/\", string(os.PathSeparator)))\n\tabs := filepath.Join(util.WorkspaceDir, rel)\n\tif err := authorizePath(abs, rel); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn abs, nil\n}\n\n// authorizePath 校验单个最终路径是否允许访问，display 仅用于错误信息：顶层调用传工作区相对路径，\n// 递归遍历、目录拷贝和压缩包解压传最终路径本身。\nfunc authorizePath(abs, display string) error {\n\tif !gulu.File.IsSubPath(util.WorkspaceDir, abs) {\n\t\treturn fmt.Errorf(\"path escapes workspace: %s\", display)\n\t}\n\t// 拒绝加密笔记本目录：MCP 文件工具不能读写加密 box 下的文件（防止密文泄漏或明文破坏加密格式）\n\tif boxID, encrypted := rejectEncryptedPath(abs); encrypted {\n\t\treturn fmt.Errorf(\"path belongs to encrypted notebook [%s]: %s\", boxID, display)\n\t}\n\t// 防止 symlink 逃逸工作区：解析符号链接后再次检查\n\tif resolved := util.ResolveLongestExistingParent(abs); resolved != abs && !gulu.File.IsSubPath(util.WorkspaceDir, resolved) {\n\t\treturn fmt.Errorf(\"symlink escapes workspace: %s\", display)\n\t}\n\t// 禁止访问敏感文件（conf/conf.json、data/snippets/conf.json、data/templates、data/.siyuan/publishAccess.json），\n\t// 与 HTTP 文件 API 共用同一黑名单（见 kernel/util/path_guard.go 的 IsForbiddenAbsPath）\n\tif util.IsForbiddenAbsPath(abs) {\n\t\treturn fmt.Errorf(\"access to sensitive workspace file is forbidden: %s\", display)\n\t}\n\treturn nil\n}\n\n// authorizeFinalPath 对即将打开或创建的最终路径做授权。resolvePath 只覆盖调用方给出的路径，","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/mcp/tools/file.go#L85-L121","documentation":"authorizePath (kernel/mcp/tools/file.go) is the MCP file-tool security gate: it rejects any absolute path outside the SiYuan workspace directory. The error names the displayed path. This prevents MCP clients from reading or writing arbitrary filesystem locations.","triggerScenarios":"Any MCP file tool call (via resolvePath, authorizeFinalPath, authorizeArchiveEntry) whose target resolves outside util.WorkspaceDir — absolute paths like /etc/passwd, ../ traversal out of the workspace, or archive entries containing \"..\" path components.","commonSituations":"Clients passing OS-absolute paths instead of workspace-relative ones; extracting malicious archives (zip-slip) via MCP; symlinks whose targets leave the workspace (a related sibling error covers symlink escape).","solutions":["Use paths relative to the workspace root, e.g. \"data/notebooks/...\"","Normalize the requested path and confirm it stays under the workspace before the call","Reject archive entries containing \"..\" or absolute components before extraction","Access external files by moving them into the workspace first"],"exampleFix":"// before\nconst path = \"/etc/hosts\"\n// after\nconst path = \"data/assets/hosts.txt\" // workspace-relative","handlingStrategy":"validation","validationCode":"const path = require(\"path\");\nfunction isInsideWorkspace(wsDir, p) {\n  const abs = path.resolve(wsDir, p);\n  return abs === wsDir || abs.startsWith(wsDir + path.sep);\n}","typeGuard":"const safePath = (p) => !p.includes(\"..\") && !path.isAbsolute(p) ? p : null;","tryCatchPattern":"try { await call(\"read_file\", {path}) } catch (e) { if (String(e).startsWith(\"path escapes workspace\")) { console.error(\"use workspace-relative paths, got:\", path); } throw e; }","preventionTips":["Always use workspace-relative paths","Sanitize archive entries (zip-slip)","Resolve and check paths client-side before calls"],"tags":["mcp","security","path-traversal","filesystem"],"backgroundTag":"path-traversal-blocked","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}