{"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":"error_code","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"kernel/model/crypto.go","lineNumber":1404,"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":1386,"sourceCodeEnd":1422,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/crypto.go#L1386-L1422","documentation":"unlockBoxHeld rejects the unlock when the supplied boxEnc is nil or has an empty WrappedDEK, marking the box state as Error. Without wrapped key material there is nothing for the KEK to unwrap, so the notebook cannot be decrypted. This is the pre-check counterpart of the decryptBoxCrypt error but raised before any key derivation.","triggerScenarios":"UnlockBox/UnlockAndMountBox/ChangeMasterPassword invoked with boxEnc nil, or a BoxEncryption whose WrappedDEK slice is empty — typically because conf.json has no BoxCrypt section for the notebook.","commonSituations":"Caller cached an old BoxEncryption before the notebook was encrypted; conf.json truncated or manually edited; notebook re-created without encryption metadata; sync dropped the crypto section.","solutions":["Reload the notebook's BoxEncryption from disk (conf.json) instead of using a stale cached value","Restore the BoxCrypt section or the notebook crypto backup for this box from a good backup","Confirm the notebook is actually encrypted before attempting unlock"],"exampleFix":"// before\nboxEnc := cachedBoxEnc[boxID] // may be nil\nmodel.UnlockBox(boxID, pw, boxEnc)\n// after\nboxEnc := loadBoxEncryptionFromConf(boxID)\nif boxEnc == nil || len(boxEnc.WrappedDEK) == 0 {\n    return fmt.Errorf(\"box %s is missing encrypted key material\", boxID)\n}\nmodel.UnlockBox(boxID, pw, boxEnc)","handlingStrategy":"validation","validationCode":"if boxEnc == nil || len(boxEnc.WrappedDEK) == 0 {\n    return errors.New(\"box is missing wrapped DEK; cannot unlock\")\n}","typeGuard":"func unlockable(b *conf.BoxEncryption) bool {\n    return b != nil && len(b.WrappedDEK) > 0\n}","tryCatchPattern":null,"preventionTips":["Always load BoxEncryption fresh from conf, never from long-lived caches","Restore missing BoxCrypt sections before attempting unlock","Check notebook encryption status before presenting unlock UI"],"tags":["encryption","key-management","config","go"],"backgroundTag":"missing-required-config-field","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"}