{"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":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/crypto.go","lineNumber":1077,"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":1059,"sourceCodeEnd":1095,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L1059-L1095","documentation":"Thrown by DisableEncryptedNotebook (crypto.go:1077) as a hard precondition guard: the encrypted-notebook feature cannot be turned off while at least one encrypted notebook still exists. Disabling would clear the global MasterSalt/KEKVerifier and delete the key backup, which would permanently lock all remaining encrypted notebooks. The user must remove (or decrypt) every encrypted notebook first.","triggerScenarios":"POST /api/notebook/disableEncryptedNotebooks when listAllEncryptedBoxIDs returns len(ids) > 0. The scan detects encrypted notebooks via conf.json flags, per-notebook key backups, on-disk ciphertext markers, or runtime identity, so even a notebook whose conf is damaged but has a backup counts.","commonSituations":"User clicks 'disable encrypted notebooks' in settings before removing their encrypted notebook(s). An encrypted notebook whose conf.json was corrupted but whose backup still marks it encrypted, so it is still counted.","solutions":["Remove every encrypted notebook first (delete or convert to normal), then call DisableEncryptedNotebook again.","If a damaged notebook cannot be removed through the UI, locate it via listAllEncryptedBoxIDs error context and delete its directory and per-notebook backup manually after confirming the data is expendable.","Re-run disable only after listAllEncryptedBoxIDs returns an empty set."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before disable, confirm no encrypted notebooks remain.\nids, err := model.ListAllEncryptedBoxIDs()\nif err != nil {\n    return err\n}\nif len(ids) > 0 {\n    return fmt.Errorf(\"remove encrypted notebooks first: %v\", ids)\n}\n// safe to disable","typeGuard":null,"tryCatchPattern":"if err := model.DisableEncryptedNotebook(); err != nil {\n    if strings.Contains(err.Error(), \"encrypted notebooks exist, remove them first\") {\n        respond(c, \"remove all encrypted notebooks before disabling\")\n        return\n    }\n    respond(c, err.Error())\n}","preventionTips":["Remove or decrypt every encrypted notebook before clicking disable.","Surface the encrypted-notebook count in the disable confirmation UI.","Handle notebooks whose conf is damaged but that still have backups (they still count)."],"tags":["encryption","notebook-crypto","precondition","lifecycle"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}