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

  1. Use paths relative to the workspace root, e.g. "data/notebooks/..."
  2. Normalize the requested path and confirm it stays under the workspace before the call
  3. Reject archive entries containing ".." or absolute components before extraction
  4. 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

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


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)