{"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":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":2123,"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":2105,"sourceCodeEnd":2141,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/crypto.go#L2105-L2141","documentation":"GetDEKIfUnlocked in kernel/model/crypto.go returns the per-notebook data-encryption key (DEK) only when the encrypted notebook has been unlocked in the current session. The kernel throws this error when the notebook is encrypted but no unlocking passphrase has been supplied yet (or the cached DEK was evicted after relock), so no key material can be handed to the caller. It is a deliberate guard to avoid silently operating on an encrypted notebook without its key.","triggerScenarios":"Calling GetDEKIfUnlocked(boxID) (or any caller such as sync/upsertIndexes, asset encryption, history, export paths) on a notebook where IsEncryptedBox(boxID) is true while isBoxUnlockedForAccess(boxID) is false, or where boxID is missing from cachedDEKs after the notebook was re-locked.","commonSituations":"Kernel restarted and the encrypted notebook was never unlocked via the unlock API before sync, search, export, or asset upload; a plugin/script drives the HTTP API without performing the unlock step; a background job races a user re-locking the notebook.","solutions":["Unlock the notebook first (call the encrypted-notebook unlock API / UI prompt with the passphrase) before retrying the operation","Check isBoxUnlockedForAccess equivalent via GetDEKIfUnlocked before scheduling background operations on encrypted notebooks","Re-open/re-cache the DEK by unlocking; if the DEK was evicted while 'unlocked', unlock again to repopulate cachedDEKs"],"exampleFix":"// before\ndek, err := model.GetDEKIfUnlocked(boxID)\nif err != nil { return err }\n// after\nif !model.IsEncryptedBox(boxID) {\n    // non-encrypted path\n} else if dek, err = model.GetDEKIfUnlocked(boxID); err != nil {\n    return fmt.Errorf(\"notebook %s must be unlocked: %w\", boxID, err)\n}","handlingStrategy":"try-catch","validationCode":"if model.IsEncryptedBox(boxID) {\n    if _, err := model.GetDEKIfUnlocked(boxID); err != nil {\n        return promptUserToUnlock(boxID)\n    }\n}","typeGuard":"func isNotebookOperable(boxID string) bool {\n    return !model.IsEncryptedBox(boxID) || func() bool {\n        _, err := model.GetDEKIfUnlocked(boxID)\n        return err == nil\n    }()\n}","tryCatchPattern":"dek, err := model.GetDEKIfUnlocked(boxID)\nif err != nil {\n    if strings.Contains(err.Error(), \"locked, please unlock\") {\n        return unlockAndRetry(boxID, op)\n    }\n    return err\n}","preventionTips":["Always run the unlock flow before scheduling sync/export/search jobs on encrypted notebooks","After kernel restart, treat every encrypted notebook as locked until explicitly unlocked","Retry operations once after a successful unlock; do not loop retries while the notebook stays locked"],"tags":["encryption","notebook-locked","go"],"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"}