siyuan-note/siyuan · error
access to sensitive workspace file is forbidden
Error message
access to sensitive workspace file is forbidden: %s
What it means
The MCP file tools share the HTTP file API's sensitive-file blacklist via util.IsForbiddenAbsPath. Paths such as conf/conf.json, data/snippets/conf.json, data/templates, and data/.siyuan/publishAccess.json are hard-blocked so the MCP server can never read or overwrite credentials, snippets config, templates, or publish authorization state. authorizePath returns this error after the workspace-containment and symlink checks pass.
Solutions
- Edit these files through the proper channels (kernel settings API, UI settings) instead of raw file access
- Exclude the sensitive paths (conf/conf.json, data/snippets/conf.json, data/templates, data/.siyuan/publishAccess.json) from bulk copy/traversal/extraction inputs
- If a backup is needed, use the kernel's own backup/export features rather than reading the raw file via MCP
Example fix
// before
copyMcpFile("/workspace/conf/conf.json", "/tmp/backup.json")
// after (use the config API instead)
conf := fetchKernelAPI("/api/system/getConf")
writeFile("/tmp/backup.json", conf) Defensive patterns
Strategy: validation
Validate before calling
const SENSITIVE = ['conf/conf.json','data/snippets/conf.json','data/templates','data/.siyuan/publishAccess.json'];
const rel = path.relative(WORKSPACE_DIR, target);
if (SENSITIVE.some(s => rel === s || rel.startsWith(s + path.sep))) {
throw new Error('target is a forbidden sensitive path');
} Try / catch
try {
await callMcpTool('readFile', { path: target });
} catch (e) {
if (String(e.message).includes('sensitive workspace file is forbidden')) {
// reroute to kernel settings API instead of raw file access
}
} Prevention
- Exclude conf/, data/snippets/, data/templates/, data/.siyuan/ from bulk MCP file operations
- Use the kernel settings/backup APIs for these files
- Filter blacklist paths out of copy/extraction inputs before calling the tools
When it happens
Trigger: Any MCP file tool call (resolvePath, authorizeFinalPath, authorizeArchiveEntry) whose target — including an archive extraction destination or a recursive traversal descendant — is exactly one of the blacklisted sensitive paths or resides inside them (e.g. data/templates itself).
Common situations: Trying to back up or edit conf/conf.json through MCP file tools; bulk-copying data/ which sweeps in data/templates; extracting a zip whose entries target data/.siyuan/publishAccess.json; scripts that treat the whole workspace as readable.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- path escapes templates dir
- symlink escapes workspace
- archive entry escapes destination
- asset path is sensitive
- asset path resolves outside notebook assets directory
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/9ec997bd3fea3ddf.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/mcp/tools/file.go:116
// 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)
}
// authorizeSubtree 校验路径及其全部后代,任一后代被拒绝即整体拒绝。删除和重命名是目录级操作,
// 只校验目录本身会让受保护的后代被删除或搬出黑名单范围(例如 file.delete("conf"))。
// 使用 Lstat:删除和重命名不会跟随符号链接,与 os.RemoveAll、os.Rename 的语义保持一致。
func authorizeSubtree(abs string) error {
if err := authorizeFinalPath(abs); err != nil {
return err
}
info, err := os.Lstat(abs)View on GitHub (pinned to 9f775e8a12)