siyuan-note/siyuan · error
path belongs to encrypted notebook
Error message
path belongs to encrypted notebook [%s]: %s
What it means
authorizePath (kernel/mcp/tools/file.go) additionally rejects paths that resolve inside an encrypted notebook (box) directory, returning the owning box ID. MCP file tools cannot read or write under encrypted boxes because that would leak ciphertext semantics or corrupt the encrypted format; use the block-level MCP tools after unlocking instead.
Solutions
- Target a non-encrypted notebook's directory instead
- Use the block MCP tools (after unlocking the notebook) rather than raw file access for content inside encrypted boxes
- Pre-check the box's encryption state via model.IsEncryptedBox/isBoxUnlocked-equivalent APIs before building the path
- Exclude encrypted box directories from directory copies and archive contents before the operation
Example fix
// before
await call("write_file", {path: "data/20240101120000-abc/file.txt"}) // encrypted box
// after
await call("write_file", {path: "data/assets/file.txt"}) // non-encrypted location Defensive patterns
Strategy: validation
Validate before calling
function isEncryptedBoxPath(p, encryptedBoxIds) {
return encryptedBoxIds.some(id => p === `data/${id}` || p.startsWith(`data/${id}/`));
} Try / catch
try { await call("write_file", {path}) } catch (e) { if (String(e).startsWith("path belongs to encrypted notebook")) { console.error("use block tools for encrypted boxes, path:", path); } throw e; } Prevention
- Keep an up-to-date list of encrypted box IDs and exclude their directories
- Use block-level MCP tools (after unlock) for encrypted content
- Filter encrypted boxes out of directory copies and archives
When it happens
Trigger: Any MCP file read/write/copy/archive-extract whose final path resolves under data/<boxID>/ where <boxID> is an encrypted notebook, e.g. writing an asset into data/202401...-/ directly.
Common situations: Scripts composing data/<boxID>/ paths manually from a notebook ID; copying directories that contain encrypted boxes; extracting archives that embed encrypted-box paths.
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 escapes workspace
- refuse to write decrypted asset inside workspace
- Access to encrypted notebook data is not supported via this…
- access to sensitive workspace file is forbidden
- accessing assets in encrypted notebook
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/0406d11f9797372a.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/mcp/tools/file.go:107
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 只覆盖调用方给出的路径,
// 容器路径合法不代表其后代合法:递归遍历、复制、解压、删除、重命名都必须对每一个后代路径再次调用本函数。
func authorizeFinalPath(abs string) error {
return authorizePath(abs, abs)
}View on GitHub (pinned to 9f775e8a12)