siyuan-note/siyuan · error
path escapes workspace
Error message
path escapes workspace: %s
What it means
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.
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
Example fix
// before const path = "/etc/hosts" // after const path = "data/assets/hosts.txt" // workspace-relative
Defensive patterns
Strategy: validation
Validate before calling
const path = require("path");
function isInsideWorkspace(wsDir, p) {
const abs = path.resolve(wsDir, p);
return abs === wsDir || abs.startsWith(wsDir + path.sep);
} Type guard
const safePath = (p) => !p.includes("..") && !path.isAbsolute(p) ? p : null; Try / catch
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; } Prevention
- Always use workspace-relative paths
- Sanitize archive entries (zip-slip)
- Resolve and check paths client-side before calls
When it happens
Trigger: 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.
Common situations: 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).
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
- path belongs to encrypted notebook
- path [ ] escapes box directory
- path [ ] must not contain '..
- access to sensitive workspace file is forbidden
- archive entry resolves outside destination
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/ee2ed112b303d1dd.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/mcp/tools/file.go:103
Content: []ContentItem{{Type: "text", Text: "unknown action '" + action + "', expected one of: [list, read, write, delete, rename, copy, grep, find, stat]"}},
IsError: true,
}, nil
}
func resolvePath(rel string) (string, error) {
rel = filepath.Clean(strings.ReplaceAll(rel, "/", string(os.PathSeparator)))
abs := filepath.Join(util.WorkspaceDir, rel)
if err := authorizePath(abs, rel); err != nil {
return "", err
}
return abs, nil
}
// authorizePath 校验单个最终路径是否允许访问,display 仅用于错误信息:顶层调用传工作区相对路径,
// 递归遍历、目录拷贝和压缩包解压传最终路径本身。
func authorizePath(abs, display string) error {
if !gulu.File.IsSubPath(util.WorkspaceDir, abs) {
return fmt.Errorf("path escapes workspace: %s", display)
}
// 拒绝加密笔记本目录:MCP 文件工具不能读写加密 box 下的文件(防止密文泄漏或明文破坏加密格式)
if boxID, encrypted := rejectEncryptedPath(abs); encrypted {
return fmt.Errorf("path belongs to encrypted notebook [%s]: %s", boxID, display)
}
// 防止 symlink 逃逸工作区:解析符号链接后再次检查
if resolved := util.ResolveLongestExistingParent(abs); resolved != abs && !gulu.File.IsSubPath(util.WorkspaceDir, resolved) {
return fmt.Errorf("symlink escapes workspace: %s", display)
}
// 禁止访问敏感文件(conf/conf.json、data/snippets/conf.json、data/templates、data/.siyuan/publishAccess.json),
// 与 HTTP 文件 API 共用同一黑名单(见 kernel/util/path_guard.go 的 IsForbiddenAbsPath)
if util.IsForbiddenAbsPath(abs) {
return fmt.Errorf("access to sensitive workspace file is forbidden: %s", display)
}
return nil
}
// authorizeFinalPath 对即将打开或创建的最终路径做授权。resolvePath 只覆盖调用方给出的路径,View on GitHub (pinned to 9f775e8a12)