{"record":{"id":"ecab7b95d651445b","repo":"siyuan-note/siyuan","slug":"no-encrypted-key-material-for-box","errorCode":null,"errorMessage":"no encrypted key material for box","messagePattern":"no encrypted key material for box","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"kernel/model/crypto.go","lineNumber":1375,"sourceCode":"\n\twasUnlocked := IsBoxUnlocked(boxID)\n\tif err = unlockBoxHeld(boxID, password, boxEnc); err != nil {\n\t\treturn false, err\n\t}\n\talreadyMount, err = mountBox(boxID)\n\tif err != nil && !wasUnlocked {\n\t\tlockBoxWithPreparationHeld(boxID, nil)\n\t}\n\treturn alreadyMount, err\n}\n\nfunc unlockBoxHeld(boxID string, password string, boxEnc *conf.BoxEncryption) (err error) {\n\tif _, busy := boxLock.Load(boxID); busy {\n\t\treturn errors.New(Conf.language(239))\n\t}\n\tif boxEnc == nil || len(boxEnc.WrappedDEK) == 0 {\n\t\tsetEncryptedBoxState(boxID, EncryptedBoxStateError)\n\t\treturn errors.New(\"no encrypted key material for box\")\n\t}\n\tif IsBoxUnlocked(boxID) {\n\t\tif GetEncryptedBoxState(boxID) == EncryptedBoxStateError {\n\t\t\treturn errors.New(Conf.Language(316))\n\t\t}\n\t\tsetEncryptedBoxState(boxID, EncryptedBoxStateUnlocked)\n\t\treturn nil\n\t}\n\n\tsetEncryptedBoxState(boxID, EncryptedBoxStateUnlocking)\n\t// 获取 box 写锁，与 LockBox/unmount0 串行化，防止并发锁/解锁导致 db/DEK 状态不一致\n\tacquireBoxWriteLock(boxID)\n\tfinalState := EncryptedBoxStateLocked\n\tdefer func() {\n\t\treleaseBoxWriteLock(boxID)\n\t\tsetEncryptedBoxState(boxID, finalState)\n\t}()\n","sourceCodeStart":1357,"sourceCodeEnd":1393,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/crypto.go#L1357-L1393","documentation":"Thrown by unlockBoxHeld (crypto.go:1375) when the provided boxEnc is nil or its WrappedDEK is empty, meaning there is no encrypted key material to unwrap a DEK from. The function sets the box state to EncryptedBoxStateError and returns, so the notebook is flagged as broken rather than silently treated as unlocked/unencrypted. This usually indicates the notebook's encryption metadata (conf BoxCrypt or the per-notebook backup) is missing or was wiped.","triggerScenarios":"UnlockBox/UnlockAndMountBox called with a boxEnc that GetBoxEncryption returned as nil or with empty WrappedDEK. Note the HTTP handler checks this earlier (returns 319), so reaching here means a direct Go caller passed a nil/empty boxEnc, or the box's conf and backup both lack WrappedDEK.","commonSituations":"Notebook conf.json lost its BoxCrypt field (corruption/bad sync) and no per-notebook backup exists. Caller passed nil explicitly. Notebook directory partially deleted, leaving Encrypted=true but no key material.","solutions":["Restore the notebook's BoxCrypt (WrappedDEK) from its per-notebook key backup or from conf.json backup, then retry.","If the key material is unrecoverable and the data is expendable, remove the notebook and recreate it.","Do not call unlockBoxHeld with a nil boxEnc; fetch it via GetBoxEncryption and handle nil at the call site."],"exampleFix":"// before\nmodel.UnlockBox(boxID, password, nil)\n\n// after\nboxCrypt, err := model.GetBoxEncryption(boxID)\nif err != nil || boxCrypt == nil || len(boxCrypt.WrappedDEK) == 0 {\n    return fmt.Errorf(\"no key material for notebook %s\", boxID)\n}\nmodel.UnlockBox(boxID, password, boxCrypt)","handlingStrategy":"validation","validationCode":"// Ensure key material exists before unlocking.\nboxCrypt, err := model.GetBoxEncryption(boxID)\nif err != nil {\n    return fmt.Errorf(\"cannot read encryption metadata: %w\", err)\n}\nif boxCrypt == nil || len(boxCrypt.WrappedDEK) == 0 {\n    return fmt.Errorf(\"no encrypted key material for notebook %s; restore its backup\", boxID)\n}\nmodel.UnlockBox(boxID, password, boxCrypt)","typeGuard":"func hasWrappedDEK(b *conf.BoxEncryption) bool { return b != nil && len(b.WrappedDEK) > 0 }","tryCatchPattern":"if err := model.UnlockBox(boxID, password, boxCrypt); err != nil {\n    if err.Error() == \"no encrypted key material for box\" {\n        respond(c, \"notebook key material missing; restore the notebook backup or remove the notebook\")\n        return\n    }\n    respond(c, err.Error())\n}","preventionTips":["Always fetch boxEnc via GetBoxEncryption and check for nil/empty before calling UnlockBox.","Keep per-notebook key backups intact so WrappedDEK can be restored.","Do not partially delete a notebook directory (it can leave Encrypted=true with no key material)."],"tags":["encryption","notebook-crypto","key-material","data-loss","config-corruption"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}