{"record":{"id":"c0cab84244bd478f","repo":"siyuan-note/siyuan","slug":"cannot-change-master-password-while-encrypted-note","errorCode":null,"errorMessage":"cannot change master password while encrypted notebooks are unlocked (DEKs in memory), lock them first","messagePattern":"cannot change master password while encrypted notebooks are unlocked \\(DEKs in memory\\), lock them first","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1721,"sourceCode":"//\tPhase 2: 切换全局 verifier\n//\tPhase 3: 写入各 box conf + backup\n//\tPhase 4: 清除 manifest\n//\n// 注意：必须在所有加密笔记本都已 Unmount 的状态下调用（DEK 不在内存），否则新旧 KEK 切换会让缓存与磁盘不一致。\nfunc ChangeMasterPassword(oldPassword, newPassword string) error {\n\tif len(newPassword) == 0 {\n\t\treturn errors.New(\"new password must not be empty\")\n\t}\n\n\tnotebookCryptoMu.Lock()\n\tdefer notebookCryptoMu.Unlock()\n\n\t// 改密期间不能有已 Mount 的加密笔记本（DEK 在内存），否则新旧 KEK 切换会让缓存与磁盘不一致\n\tcachedDEKsLock.RLock()\n\tdekCount := len(cachedDEKs)\n\tcachedDEKsLock.RUnlock()\n\tif dekCount > 0 {\n\t\treturn errors.New(\"cannot change master password while encrypted notebooks are unlocked (DEKs in memory), lock them first\")\n\t}\n\n\toldKEK, err := deriveKEK(oldPassword)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer zeroAndClear(oldKEK)\n\n\tnc := currentNotebookCrypto()\n\n\tparams, validErr := util.ValidateArgon2Params(nc.KDFParams)\n\tif validErr != nil {\n\t\treturn validErr\n\t}\n\tnewKEK := util.DeriveKey(newPassword, nc.MasterSalt, params)\n\tdefer zeroAndClear(newKEK)\n\tnewHistoryKEKs, err := rewrapHistoryKEKs(oldKEK, newKEK, nc.HistoryKEKs)\n\tif err != nil {","sourceCodeStart":1703,"sourceCodeEnd":1739,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/crypto.go#L1703-L1739","documentation":"ChangeMasterPassword refuses to run while one or more encrypted notebooks are unlocked, because their DEKs are in memory. Switching the KEK under cached DEKs would leave the in-memory cache inconsistent with the newly wrapped envelopes on disk, so the operation is aborted and the user must lock all encrypted notebooks first.","triggerScenarios":"Calling ChangeMasterPassword while cachedDEKs is non-empty — i.e. any encrypted notebook is in an unlocked/mounted state, or a previous lock operation failed to evict its DEK.","commonSituations":"User attempts a master-password change from settings while an encrypted notebook is open; a background job or plugin holds a mounted encrypted notebook; a stale DEK left by a failed unlock/lock cycle.","solutions":["Lock every encrypted notebook (LockEncryptedBox / UI lock) so DEKs are zeroed and removed from the cache, then retry the password change","Restart the kernel/app to guarantee a clean in-memory state if a stale DEK is suspected","Close background jobs/plugins that may re-mount encrypted notebooks during the change"],"exampleFix":"// before\nawait lockEncryptedBoxes(); await changeMasterPassword(oldPw, newPw)\n// after\nfor (const box of encryptedBoxes) { if (!isBoxLocked(box.id)) await lockBox(box.id) }\nawait changeMasterPassword(oldPw, newPw)","handlingStrategy":"validation","validationCode":"if (encryptedBoxes.some(b => !b.locked)) { throw new Error(\"lock all encrypted notebooks before changing the master password\") }","typeGuard":null,"tryCatchPattern":"try { await changeMasterPassword(oldPw, newPw) } catch (e) { if (e.message.includes(\"lock them first\")) { await lockAllEncryptedBoxes(); retryChange() } }","preventionTips":["Lock all encrypted notebooks before opening the password-change dialog","Disable/complete background jobs that mount encrypted boxes during the change","Restart the kernel if a stale DEK cache is suspected after a failed lock"],"tags":["encryption","state","key-rotation"],"backgroundTag":"invalid-state-transition","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"}