{"record":{"id":"75dbdc88657b96d8","repo":"siyuan-note/siyuan","slug":"cannot-disable-encrypted-notebook-feature-while-en","errorCode":null,"errorMessage":"cannot disable encrypted notebook feature while encrypted notebooks exist, remove them first","messagePattern":"cannot disable encrypted notebook feature while encrypted notebooks exist, remove them first","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/crypto.go","lineNumber":1099,"sourceCode":"\tConf.Save()\n\tIncSync()\n\treturn nil\n}\n\n// DisableEncryptedNotebook 关闭加密笔记本功能。前置：不能有加密笔记本存在，\n// 且不能有依赖当前密钥备份的已删除笔记本历史（否则禁用并删除备份会让这些历史永久锁死，违反 §19）。\n// 清除全局加密配置（MasterSalt/KEKVerifier），KEK/DEK 不再可用。\nfunc DisableEncryptedNotebook() error {\n\tnotebookCryptoMu.Lock()\n\tdefer notebookCryptoMu.Unlock()\n\n\t// 检查是否还有加密笔记本（含 conf 损坏但存在备份的）\n\tids, listErr := listAllEncryptedBoxIDs()\n\tif listErr != nil {\n\t\treturn fmt.Errorf(\"list encrypted notebooks failed: %w\", listErr)\n\t}\n\tif len(ids) > 0 {\n\t\treturn errors.New(\"cannot disable encrypted notebook feature while encrypted notebooks exist, remove them first\")\n\t}\n\t// 检查历史目录中是否存在已删除加密笔记本的历史快照：其恢复仍依赖当前 MasterSalt/KEKVerifier，\n\t// 删除备份前必须先清除这些历史（详见设计 §19）\n\thasHistory, historyErr := scanEncryptedNotebookHistory()\n\tif historyErr != nil {\n\t\treturn fmt.Errorf(\"check encrypted notebook history failed: %w\", historyErr)\n\t}\n\tif hasHistory {\n\t\treturn errors.New(Conf.Language(323))\n\t}\n\n\tConf.m.Lock()\n\tConf.NotebookCrypto.Enabled = false\n\tConf.NotebookCrypto.MasterSalt = nil\n\tConf.NotebookCrypto.KEKVerifier = nil\n\tConf.NotebookCrypto.VerifierNonce = nil\n\tConf.m.Unlock()\n","sourceCodeStart":1081,"sourceCodeEnd":1117,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/crypto.go#L1081-L1117","documentation":"DisableEncryptedNotebook refuses to turn the feature off while any encrypted notebook still exists: \"cannot disable encrypted notebook feature while encrypted notebooks exist, remove them first\". Disabling clears MasterSalt/KEKVerifier and deletes the key backup, which would permanently lock any remaining encrypted notebooks, so the guard is a deliberate data-safety precondition, not a fault.","triggerScenarios":"Calling DisableEncryptedNotebook when listAllEncryptedBoxIDs returns at least one notebook whose conf.BoxConf.Encrypted is true — including notebooks whose main conf is damaged but that still carry the encrypted flag/backup.","commonSituations":"User tries to disable encryption via API/script while encrypted notebooks remain; leftover encrypted test notebooks in the workspace; forgotten notebooks in the trash/other locations still flagged encrypted.","solutions":["Remove all encrypted notebooks first (delete them in the UI or delete their directories, emptying dependent history), then call DisableEncryptedNotebook again","Decrypt/copy the notebook contents out to ordinary notebooks before removal if the data must be kept, since keys are discarded on disable","If a leftover encrypted notebook is already deleted from the UI, ensure its directory is gone from the workspace data folder so enumeration no longer sees it"],"exampleFix":"// before\nmodel.DisableEncryptedNotebook() // fails while encrypted notebooks exist\n// after\nfor _, boxID := range remainingEncryptedBoxIDs() {\n    model.RemoveBox(boxID) // or export/decrypt contents first\n}\nerr := model.DisableEncryptedNotebook() // now succeeds","handlingStrategy":"validation","validationCode":"// Guard before disabling: no encrypted notebooks may remain\nids, err := listAllEncryptedBoxIDs()\nif err == nil && len(ids) > 0 { /* remove/export these notebooks first */ }","typeGuard":"func isEncryptedNotebooksRemain(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"while encrypted notebooks exist\")\n}","tryCatchPattern":"if err := model.DisableEncryptedNotebook(); err != nil {\n    if isEncryptedNotebooksRemain(err) { /* enumerate and delete/decrypt remaining notebooks, then retry */ }\n}","preventionTips":["Before disabling, list encrypted notebooks and export/decrypt any data you need — disabling discards the keys permanently","Also clear encrypted-notebook history (deleted-notebook snapshots) or the disable will fail on the next guard","Keep test notebooks unencrypted or remove them after experiments","Automate the remove-then-disable sequence in scripts instead of calling disable unconditionally"],"tags":["encryption","precondition","state","data-loss"],"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"}