{"record":{"id":"d21750adeba9cd22","repo":"siyuan-note/siyuan","slug":"path-belongs-to-encrypted-notebook-s","errorCode":null,"errorMessage":"path belongs to encrypted notebook [%s]","messagePattern":"path belongs to encrypted notebook \\[(.+?)\\]","errorType":"http","errorClass":null,"httpStatus":200,"severity":"error","filePath":"kernel/api/archive.go","lineNumber":35,"sourceCode":"package api\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"path/filepath\"\n\n\t\"github.com/88250/gulu\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/siyuan-note/logging\"\n\t\"github.com/siyuan-note/siyuan/kernel/model\"\n\t\"github.com/siyuan-note/siyuan/kernel/util\"\n)\n\n// rejectEncryptedArchivePath 检查路径是否落入加密笔记本目录（含 symlink 绕过），是则返回错误。\nfunc rejectEncryptedArchivePath(absPath string) error {\n\tboxID := model.ExtractBoxIDFromAssetsPath(absPath)\n\tif boxID != \"\" && model.IsEncryptedBox(boxID) {\n\t\treturn fmt.Errorf(\"path belongs to encrypted notebook [%s]\", boxID)\n\t}\n\tif resolved := util.ResolveLongestExistingParent(absPath); resolved != absPath {\n\t\tboxID = model.ExtractBoxIDFromAssetsPath(resolved)\n\t\tif boxID != \"\" && model.IsEncryptedBox(boxID) {\n\t\t\treturn fmt.Errorf(\"symlink resolves into encrypted notebook [%s]\", boxID)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc zip(c *gin.Context) {\n\tret := gulu.Ret.NewResult()\n\tdefer c.JSON(http.StatusOK, ret)\n\n\targ, ok := util.JsonArg(c, ret)\n\tif !ok {\n\t\treturn\n\t}","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/api/archive.go#L17-L53","documentation":"Thrown by rejectEncryptedArchivePath (kernel/api/archive.go), a security guard on the archive/zip API. Before packaging anything, the kernel maps the absolute path to a notebook id with model.ExtractBoxIDFromAssetsPath and checks model.IsEncryptedBox. If the path sits inside an encrypted notebook, the zip request is refused outright because the archive API is not encryption-aware and would copy ciphertext out of the protected store.","triggerScenarios":"Calling the archive zip endpoint with a path under an encrypted notebook's directory (its assets or notebook folder), regardless of whether the notebook is currently unlocked; scripting a bulk zip of the whole workspace while at least one encrypted notebook exists under the tree.","commonSituations":"Encrypted-notebook feature enabled and an export/backup tool or plugin zips workspace paths; automated backup scripts that walk data/ and hit the encrypted notebook dir; users trying to hand-archive an encrypted notebook via the generic archive API instead of the dedicated export flow.","solutions":["Export the encrypted notebook through the dedicated export APIs, which acquire the box lease and decrypt content properly (they route through holdEncryptedBoxRequest)","Move or keep the files you need to archive in a non-encrypted notebook","Scope the zip path to exclude the encrypted notebook's directory and archive the remaining notebooks","Do not attempt to zip encrypted notebook paths directly; the guard is intentional and has no override flag"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function isEncryptedBoxId(boxId: string, notebooks: { id: string; encrypted: boolean }[]): boolean {\n  return notebooks.some(n => n.id === boxId && n.encrypted);\n}\n// before archiving a path:\nconst box = boxIdFromAssetsPath(absPath);\nconst nb = await fetchGet('/api/notebook/lsNotebooks');\nif (box && isEncryptedBoxId(box, nb.data.notebooks)) {\n  throw new Error('skip encrypted notebook in archive job');\n}","typeGuard":"const isEncryptedNotebookPath = (absPath: string, encryptedBoxIds: Set<string>): boolean => {\n  const m = absPath.match(/data\\/([0-9]{14}[^\\/]*)\\/assets\\//); // box id from assets path\n  return !!m && encryptedBoxIds.has(m[1]);\n};","tryCatchPattern":"if (res.code === -1 && res.msg.startsWith('path belongs to encrypted notebook')) {\n  // exclude the encrypted notebook's subtree from the archive scope and re-run\n}","preventionTips":["Maintain the encrypted-notebook id set and filter archive jobs against it up front","Use the dedicated export flow for encrypted notebooks; never zip their directories directly","Document that the archive API is not encryption-aware so automation authors do not rediscover this"],"tags":["security","encryption","archive","notebook","siyuan"],"backgroundTag":"encrypted-path-access-denied","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}