{"record":{"id":"9ec997bd3fea3ddf","repo":"siyuan-note/siyuan","slug":"access-to-sensitive-workspace-file-is-forbidden","errorCode":null,"errorMessage":"access to sensitive workspace file is forbidden: %s","messagePattern":"access to sensitive workspace file is forbidden: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/tools/file.go","lineNumber":116,"sourceCode":"\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 只覆盖调用方给出的路径，\n// 容器路径合法不代表其后代合法：递归遍历、复制、解压、删除、重命名都必须对每一个后代路径再次调用本函数。\nfunc authorizeFinalPath(abs string) error {\n\treturn authorizePath(abs, abs)\n}\n\n// authorizeSubtree 校验路径及其全部后代，任一后代被拒绝即整体拒绝。删除和重命名是目录级操作，\n// 只校验目录本身会让受保护的后代被删除或搬出黑名单范围（例如 file.delete(\"conf\")）。\n// 使用 Lstat：删除和重命名不会跟随符号链接，与 os.RemoveAll、os.Rename 的语义保持一致。\nfunc authorizeSubtree(abs string) error {\n\tif err := authorizeFinalPath(abs); err != nil {\n\t\treturn err\n\t}\n\tinfo, err := os.Lstat(abs)","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/mcp/tools/file.go#L98-L134","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\ncopyMcpFile(\"/workspace/conf/conf.json\", \"/tmp/backup.json\")\n// after (use the config API instead)\nconf := fetchKernelAPI(\"/api/system/getConf\")\nwriteFile(\"/tmp/backup.json\", conf)","handlingStrategy":"validation","validationCode":"const SENSITIVE = ['conf/conf.json','data/snippets/conf.json','data/templates','data/.siyuan/publishAccess.json'];\nconst rel = path.relative(WORKSPACE_DIR, target);\nif (SENSITIVE.some(s => rel === s || rel.startsWith(s + path.sep))) {\n  throw new Error('target is a forbidden sensitive path');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await callMcpTool('readFile', { path: target });\n} catch (e) {\n  if (String(e.message).includes('sensitive workspace file is forbidden')) {\n    // reroute to kernel settings API instead of raw file access\n  }\n}","preventionTips":["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"],"tags":["security","filesystem","blacklist","path-validation"],"backgroundTag":"permission-denied","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"}