{"record":{"id":"17dd0b0fefe6fade","repo":"siyuan-note/siyuan","slug":"encrypted-notebook-has-no-valid-key-material","errorCode":null,"errorMessage":"encrypted notebook has no valid key material","messagePattern":"encrypted notebook has no valid key material","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"kernel/model/crypto.go","lineNumber":1951,"sourceCode":"\t// conf 中有完整的 BoxCrypt\n\tif confMarkedEncrypted && boxConf.BoxCrypt != nil && len(boxConf.BoxCrypt.WrappedDEK) > 0 {\n\t\treturn boxConf.BoxCrypt, nil\n\t}\n\n\t// fallback 到 backup\n\tbackup, err := readNotebookCryptBackup(boxID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif backup != nil && len(backup.WrappedDEK) > 0 {\n\t\tmarkRuntimeEncryptedBox(boxID)\n\t\treturn backup, nil\n\t}\n\n\t// backup 也不可用\n\tif confMarkedEncrypted || IsEncryptedBox(boxID) {\n\t\t// conf 标记为加密但密钥材料缺失 → 明确错误（而非误报\"未加密\"）\n\t\treturn nil, errors.New(\"encrypted notebook has no valid key material\")\n\t}\n\treturn nil, nil // 真正的非加密笔记本\n}\n\n// needWriteNotebookCryptBackup 检查是否需要写入/刷新 per-notebook backup。\n// backup 不存在、或内容与 crypt 不一致时返回 true。\nfunc needWriteNotebookCryptBackup(boxID string, crypt *conf.BoxEncryption) bool {\n\texisting, err := readNotebookCryptBackup(boxID)\n\tif err != nil || existing == nil {\n\t\treturn true\n\t}\n\treturn !bytes.Equal(existing.WrappedDEK, crypt.WrappedDEK) ||\n\t\t!bytes.Equal(existing.WrapNonce, crypt.WrapNonce) ||\n\t\t!bytes.Equal(existing.Metadata, crypt.Metadata) ||\n\t\texisting.Spec != crypt.Spec ||\n\t\texisting.CreatedAt != crypt.CreatedAt\n}\n","sourceCodeStart":1933,"sourceCodeEnd":1969,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/crypto.go#L1933-L1969","documentation":"GetBoxEncryption returns this error when conf.json marks the notebook as encrypted (or IsEncryptedBox agrees) but neither conf nor the per-notebook backup contains usable key material (WrappedDEK). It is an explicit integrity error: reporting 'not encrypted' would risk silently writing plaintext, so the kernel fails loudly instead.","triggerScenarios":"Calling GetBoxEncryption for a notebook whose conf.json has Encrypted=true but whose BoxCrypt.WrappedDEK is empty/invalid AND readNotebookCryptBackup returns nil or a backup without WrappedDEK.","commonSituations":"conf.json manually edited or partially written (interrupted save); a workspace restored where the notebook crypt backup was excluded; sync conflicts keeping Encrypted=true while stripping key material; an encrypted notebook copied into a workspace without its metadata files.","solutions":["Restore the notebook's conf.json and per-notebook crypt backup from a sync/backup snapshot taken before the corruption","Check whether a master-password migration is pending - restart SiYuan so recovery can rebuild conf from backup or migration entry","Verify the data/<boxID>/.siyuan/ directory actually contains the key-material files; re-copy the missing one from a healthy workspace copy","Do NOT set Encrypted=false to silence the error - that would expose plaintext writes; if the notebook is genuinely unusable, decrypt-recover its content from backup and re-create it"],"exampleFix":"// before: conf says encrypted, key material missing\nboxConf.Encrypted = true // BoxCrypt.WrappedDEK empty -> error: encrypted notebook has no valid key material\n// after: restore the box's .siyuan conf/backup (with WrappedDEK) from snapshot, or complete pending migration recovery via restart","handlingStrategy":"try-catch","validationCode":"// Check key material exists before treating a box as encrypted\nconfMarked := boxConf != nil && boxConf.Encrypted\nhasKeys := confMarked && boxConf.BoxCrypt != nil && len(boxConf.BoxCrypt.WrappedDEK) > 0\nif confMarked && !hasKeys {\n    // restore conf/backup from snapshot before calling GetBoxEncryption\n}","typeGuard":"func hasKeyMaterial(be *conf.BoxEncryption) bool {\n    return be != nil && len(be.WrappedDEK) > 0\n}","tryCatchPattern":"enc, err := model.GetBoxEncryption(boxID)\nif err != nil {\n    if err.Error() == \"encrypted notebook has no valid key material\" {\n        // DO NOT fall back to treating the box as plaintext; restore key material from backup/snapshot first\n    }\n}","preventionTips":["Include per-notebook crypt backups in any workspace copy/sync/restore flow","Never hand-edit conf.json Encrypted/BoxCrypt fields","Restart SiYuan after a crash so pending migration recovery can rebuild key material before other code reads it","Take snapshots before master-password changes so key material can be restored"],"tags":["go","encryption","key-management","data-integrity","corruption"],"backgroundTag":"missing-key-material","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"}