siyuan-note/siyuan · critical
encrypted notebook key material is missing
Error message
encrypted notebook key material is missing
What it means
encryptBoxMetadata seals an encrypted notebook's metadata (icon, sort, sortMode) using the notebook's key material stored in boxConf.BoxCrypt. This error means the BoxConf passed in is nil or has no BoxCrypt section, so there is no DEK-derived key material available to encrypt the metadata — an invariant required for encrypted notebooks.
Solutions
- Recreate the encrypted notebook through the proper creation flow so BoxCrypt (key material) is initialized
- Restore the boxCrypt section of <DataDir>/<boxID>/.siyuan/conf.json from a valid backup or the notebook crypto backup
- Ensure the DEK is unlocked/cached before operations that encrypt metadata
- Guard callers: check conf.BoxCrypt != nil before invoking encryption paths
Example fix
// before
err := encryptBoxMetadata(boxID, conf, dek) // conf.BoxCrypt == nil
// after
if conf == nil || conf.BoxCrypt == nil {
conf.BoxCrypt = initBoxCrypt(boxID, dek) // initialize key material first
}
err := encryptBoxMetadata(boxID, conf, dek) Defensive patterns
Strategy: type-guard
Validate before calling
// Go: verify key material exists before encryption paths
if conf == nil || conf.BoxCrypt == nil {
return errors.New("notebook crypt key material not initialized")
} Type guard
func hasBoxCrypt(c *conf.BoxConf) bool {
return c != nil && c.BoxCrypt != nil
} Try / catch
if err := encryptBoxMetadata(boxID, conf, dek); err != nil {
if strings.Contains(err.Error(), "key material is missing") {
// re-run encrypted notebook setup / restore boxCrypt from backup
}
return err
} Prevention
- Create encrypted notebooks only via the standard creation flow
- Do not hand-edit conf.json or remove the boxCrypt field
- Unlock the notebook (DEK cached) before metadata writes
- Keep the notebook crypto backup file intact for recovery
When it happens
Trigger: Calling encryptBoxMetadata (via reuseBoxMetadataIfUnchanged or createEncryptedBox, exercised in encrypted-replay tests) with a BoxConf lacking BoxCrypt, e.g. creating an encrypted notebook without completing key setup, or loading a conf where BoxCrypt was stripped/corrupted.
Common situations: Interrupted encrypted-notebook creation leaving conf.json without BoxCrypt; manually edited conf.json removing the boxCrypt field; copying a plaintext conf into an encrypted notebook; tests constructing BoxConf without key material.
Related errors
- encrypted attribute view snapshot has no matching notebook
- initialize encrypted notebook document failed
- Please unlock the encrypted notebook first
- accessing assets in encrypted notebook
- block [ ] is not a sortable document
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/0110d93adeadeb61.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/box_conf_crypto.go:37
import (
"errors"
"path/filepath"
"github.com/88250/gulu"
"github.com/siyuan-note/filelock"
"github.com/siyuan-note/siyuan/kernel/conf"
"github.com/siyuan-note/siyuan/kernel/util"
)
type encryptedBoxMetadata struct {
Icon string `json:"icon"`
Sort int `json:"sort"`
SortMode int `json:"sortMode"`
}
func encryptBoxMetadata(boxID string, boxConf *conf.BoxConf, dek []byte) error {
if boxConf == nil || boxConf.BoxCrypt == nil {
return errors.New("encrypted notebook key material is missing")
}
metadata := &encryptedBoxMetadata{
Icon: filterBoxIcon(boxConf.Icon),
Sort: boxConf.Sort,
SortMode: boxConf.SortMode,
}
plaintext, err := gulu.JSON.MarshalJSON(metadata)
if err != nil {
return err
}
key := util.DeriveSubKey(dek, "siyuan/box-metadata")
defer zeroAndClear(key)
boxConf.BoxCrypt.Metadata, err = util.EncryptWithAAD(key, plaintext, boxMetadataAAD(boxID))
return err
}
func decryptBoxMetadata(boxID string, boxConf *conf.BoxConf, dek []byte) error {
if boxConf == nil || boxConf.BoxCrypt == nil || len(boxConf.BoxCrypt.Metadata) == 0 {View on GitHub (pinned to 9f775e8a12)