{"record":{"id":"e5d14cb98fa1d847","repo":"siyuan-note/siyuan","slug":"encrypted-notebook-locked-please-unlock-it-first","errorCode":null,"errorMessage":"encrypted notebook locked, please unlock it first","messagePattern":"encrypted notebook locked, please unlock it first","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/mount.go","lineNumber":550,"sourceCode":"\t\t\tCheckUpdate(true)\n\t\t}()\n\t}\n\n\tif !gulu.File.IsDir(localPath) {\n\t\treturn false, errors.New(\"can not open file, just support open folder only\")\n\t}\n\n\tfor _, box := range Conf.GetOpenedBoxes() {\n\t\tif box.ID == boxID {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\t// 加密笔记本必须先通过 UnlockBox 解出 DEK，否则拒绝挂载。Mount 本身不接收密码，\n\t// 前端流程为：先调 /api/notebook/unlockBox 解锁，再调 openNotebook 挂载。\n\t// 使用 IsEncryptedBox 统一判定（含 backup fallback，不依赖 conf 完整性）。\n\tif IsEncryptedBox(boxID) && !IsBoxUnlocked(boxID) {\n\t\treturn false, errors.New(\"encrypted notebook locked, please unlock it first\")\n\t}\n\n\tbox := &Box{ID: boxID}\n\tboxConf := box.GetConf()\n\tboxConf.Closed = false\n\tif err := box.SaveConf(boxConf); err != nil {\n\t\tlogging.LogErrorf(\"save box conf [%s] failed: %s\", boxID, err)\n\t}\n\tif boxConf.Encrypted {\n\t\tmarkRuntimeEncryptedBox(boxID)\n\t\tmountedEncryptedBoxes.Store(boxID, true)\n\t}\n\tif _, ensureErr := EnsureBoxDoc(boxID); nil != ensureErr {\n\t\tlogging.LogErrorf(\"ensure box document [%s] failed: %s\", boxID, ensureErr)\n\t}\n\n\t// 缓存根一级的文档树展开\n\tfiles, _, _ := ListDocTree(box.ID, \"/\", util.SortModeUnassigned, false, false, Conf.FileTree.MaxListCount)","sourceCodeStart":532,"sourceCodeEnd":568,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/mount.go#L532-L568","documentation":"Encrypted notebooks store their data encrypted under a DEK wrapped by the user's password. Mount never receives a password, so it requires the box to already be unlocked via UnlockBox (/api/notebook/unlockBox); IsEncryptedBox (including backup fallback) is true while IsBoxUnlocked is false, and mounting is refused. The intended frontend flow is: unlockBox first, then openNotebook.","triggerScenarios":"Calling /api/notebook/openNotebook (Mount) on an encrypted notebook after kernel restart or before ever entering the password; calling Mount concurrently with a flow that re-locked the box; a plugin opening the notebook directly instead of going through the unlock dialog.","commonSituations":"Automated scripts or plugins that call openNotebook without knowing the notebook is encrypted; session loss after kernel reboot (DEK is memory-only); attempting to mount an encrypted notebook whose unlock was cancelled by the user.","solutions":["Call /api/notebook/unlockBox with the notebook password first, then call openNotebook.","Check lock state beforehand (IsEncryptedBox/IsBoxUnlocked equivalents via API) and route to the unlock UI when locked.","After a kernel restart, expect all encrypted notebooks to be locked again and re-run the unlock flow.","If the password is forgotten, use the documented recovery flow rather than bypassing the lock."],"exampleFix":"// before: mounting an encrypted notebook directly\nawait fetchPost(\"/api/notebook/openNotebook\", { notebook: boxID });\n// -> encrypted notebook locked, please unlock it first\n\n// after: unlock before mount\nawait fetchPost(\"/api/notebook/unlockBox\", { notebook: boxID, password });\nawait fetchPost(\"/api/notebook/openNotebook\", { notebook: boxID });","handlingStrategy":"try-catch","validationCode":"// query state before mounting\nconst boxes = (await fetchPost(\"/api/notebook/lsNotebooks\", {})).data.notebooks;\n// if the notebook is encrypted and not yet unlocked, unlockBox must run first\n\nasync function ensureOpen(boxID: string, password: string): Promise<void> {\n  await fetchPost(\"/api/notebook/unlockBox\", { notebook: boxID, password });\n  await fetchPost(\"/api/notebook/openNotebook\", { notebook: boxID });\n}","typeGuard":null,"tryCatchPattern":"try {\n  await fetchPost(\"/api/notebook/openNotebook\", { notebook: boxID });\n} catch (e) {\n  if (String(e).includes(\"encrypted notebook locked\")) {\n    await promptUnlockDialog(boxID); // collects password, calls unlockBox, then openNotebook\n  }\n}","preventionTips":["Treat every encrypted notebook as locked after each kernel restart","Always follow the unlockBox -> openNotebook sequence in plugins and scripts","Surface the unlock dialog instead of failing silently when openNotebook returns this error"],"tags":["encryption","notebook","authentication","unlock"],"backgroundTag":"authentication-required","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"}