{"record":{"id":"4814b0d58c143996","repo":"siyuan-note/siyuan","slug":"encrypted-notebook-is-locked-please-unlock-it-fir-4814b0","errorCode":null,"errorMessage":"encrypted notebook is locked, please unlock it first","messagePattern":"encrypted notebook is locked, please unlock it first","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":2093,"sourceCode":"func IsEncryptedAssetPath(absPath string) bool {\n\tboxID := ExtractBoxIDFromAssetsPath(absPath)\n\treturn boxID != \"\" && IsEncryptedBox(boxID)\n}\n\n// GetDEKIfUnlocked 返回已解锁加密笔记本的 DEK（副本）。\n// 非加密笔记本返回 (nil, nil)——filesys 据此原样读写，对普通笔记本透明。\n// 加密但未解锁（DEK 不在内存）返回 (nil, error)——filesys 的加解密函数遇 error 后拒绝读写，\n// 避免加密笔记本在未解锁状态下静默以明文落盘（深度防御，见 issue #18034）。\nfunc GetDEKIfUnlocked(boxID string) ([]byte, error) {\n\tif boxID != \"\" && !ast.IsNodeIDPattern(boxID) {\n\t\treturn nil, errors.New(\"invalid notebook ID\")\n\t}\n\tif !IsEncryptedBox(boxID) {\n\t\treturn nil, nil\n\t}\n\trepairEncryptedBoxStateFromDEK(boxID)\n\tif !isBoxUnlockedForAccess(boxID) {\n\t\treturn nil, errors.New(\"encrypted notebook is locked, please unlock it first\")\n\t}\n\tcachedDEKsLock.RLock()\n\tdefer cachedDEKsLock.RUnlock()\n\tdek, ok := cachedDEKs[boxID]\n\tif !ok {\n\t\treturn nil, errors.New(\"encrypted notebook is locked, please unlock it first\")\n\t}\n\tret := make([]byte, len(dek))\n\tcopy(ret, dek)\n\treturn ret, nil\n}\n\n// HoldBoxReadLock 获取 box 读锁，防止 LockBox 在持锁期间清除缓存/临时文件。\n// 调用方完成解密输出后必须调 ReleaseBoxReadLock。\nfunc HoldBoxReadLock(boxID string) {\n\tif !IsEncryptedBox(boxID) {\n\t\tacquireBoxReadLock(boxID)\n\t\treturn","sourceCodeStart":2075,"sourceCodeEnd":2111,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L2075-L2111","documentation":"Returned by GetDEKIfUnlocked when the box is recognized as encrypted but isBoxUnlockedForAccess returns false — meaning the notebook is in a locked state (auto-locked by idle timeout, manually locked via LockBox, or freshly started app with no DEK in memory). This is the fail-closed path from issue #18034: the function refuses to hand out a DEK so filesys will not silently write plaintext to an encrypted notebook.","triggerScenarios":"Any asset read/write, block-tree access, or file copy that calls GetDEKIfUnlocked after the auto-lock timer fires or after LockBox was called. Also occurs on a fresh kernel restart where the encrypted notebook has not yet been unlocked in this session.","commonSituations":"User left the app idle past the auto-lock interval and then tried to open an asset or export a doc. App was restarted and the encrypted notebook hasn't been unlocked yet. LockBox was triggered programmatically (e.g., by a sync or publish flow) while a long-running export was queued.","solutions":["Prompt the user to unlock the encrypted notebook via UnlockBox (provide password), then retry the operation.","If building automation, call UnlockBox(boxID, password) before any asset/doc operation and check its error before proceeding.","Increase or disable the auto-lock timeout in notebook crypto settings if the lock fires too aggressively for the workflow."],"exampleFix":"// before\ndek, err := model.GetDEKIfUnlocked(boxID)\nif err != nil { return err }\n// after\ndek, err := model.GetDEKIfUnlocked(boxID)\nif err != nil {\n    if unlockErr := model.UnlockBox(boxID, password); unlockErr != nil {\n        return unlockErr\n    }\n    dek, err = model.GetDEKIfUnlocked(boxID)\n    if err != nil { return err }\n}","handlingStrategy":"try-catch","validationCode":"// Pre-check unlock state before calling GetDEKIfUnlocked\nif model.IsEncryptedBox(boxID) && !model.IsBoxUnlocked(boxID) {\n    // prompt user to unlock\n}","typeGuard":null,"tryCatchPattern":"dek, err := model.GetDEKIfUnlocked(boxID)\nif err != nil {\n    if strings.Contains(err.Error(), \"locked\") {\n        // prompt password, then:\n        if unlockErr := model.UnlockBox(boxID, password); unlockErr != nil {\n            return unlockErr\n        }\n        dek, err = model.GetDEKIfUnlocked(boxID)\n    }\n    if err != nil {\n        return err\n    }\n}","preventionTips":["Wrap all encrypted-notebook asset operations in HoldBoxReadLock/ReleaseBoxReadLock.","Keep the notebook unlocked for the duration of a multi-step export, then explicitly lock.","Check isBoxUnlockedForAccess before scheduling long-running background jobs."],"tags":["encryption","notebook-lock","dek","fail-closed","crypto"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}