{"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":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1696,"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\tConf.m.Lock()\n\tnc := Conf.NotebookCrypto\n\tConf.m.Unlock()\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)","sourceCodeStart":1678,"sourceCodeEnd":1714,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L1678-L1714","documentation":"Thrown by ChangeMasterPassword when cachedDEKs is non-empty, meaning at least one encrypted notebook is currently unlocked (its DEK is in memory). Changing the master password re-wraps all WrappedDEKs with a new KEK; if a notebook is unlocked, its cached DEK would become inconsistent with the on-disk WrappedDEK after the KEK switch. This guard prevents that inconsistency.","triggerScenarios":"Called via changeMasterPassword API while one or more encrypted notebooks are in the Unlocked state (DEK cached in memory). The check acquires cachedDEKsLock.RLock, reads len(cachedDEKs), and rejects if > 0.","commonSituations":"User unlocks a notebook, then tries to change the master password without locking it first. Auto-lock hasn't triggered yet. A second device synced new notebooks that are auto-unlocked on startup. User forgot to lock after a session.","solutions":["Lock all encrypted notebooks first (call LockBox or use the UI lock action), then retry ChangeMasterPassword.","If using the API, call getEncryptedNotebookStatus to verify all encrypted notebooks show a locked state before calling changeMasterPassword.","Reduce AutoLockMinutes so notebooks auto-lock sooner, reducing the window where this guard blocks password changes."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Check that no encrypted notebooks are unlocked before changing password:\n// (This mirrors the internal check in ChangeMasterPassword)\n// At the API level, call getEncryptedNotebookStatus first and verify\n// all boxes show locked state. There is no public 'lock all' API, so\n// the user must lock each notebook individually via the UI or API.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Lock all encrypted notebooks before initiating a password change.","Use getEncryptedNotebookStatus to verify all boxes are locked before calling changeMasterPassword.","Design the UI to auto-lock all notebooks as a pre-step of the password-change flow."],"tags":["encryption","password-change","state-consistency","go"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}