siyuan-note/siyuan · critical

no encrypted key material for box

Error message

no encrypted key material for box

What it means

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.

Solutions

  1. Reload the notebook's BoxEncryption from disk (conf.json) instead of using a stale cached value
  2. Restore the BoxCrypt section or the notebook crypto backup for this box from a good backup
  3. Confirm the notebook is actually encrypted before attempting unlock

Example fix

// before
boxEnc := cachedBoxEnc[boxID] // may be nil
model.UnlockBox(boxID, pw, boxEnc)
// after
boxEnc := loadBoxEncryptionFromConf(boxID)
if boxEnc == nil || len(boxEnc.WrappedDEK) == 0 {
    return fmt.Errorf("box %s is missing encrypted key material", boxID)
}
model.UnlockBox(boxID, pw, boxEnc)
Defensive patterns

Strategy: validation

Validate before calling

if boxEnc == nil || len(boxEnc.WrappedDEK) == 0 {
    return errors.New("box is missing wrapped DEK; cannot unlock")
}

Type guard

func unlockable(b *conf.BoxEncryption) bool {
    return b != nil && len(b.WrappedDEK) > 0
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/ecab7b95d651445b. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/crypto.go:1404

	wasUnlocked := IsBoxUnlocked(boxID)
	if err = unlockBoxHeld(boxID, password, boxEnc); err != nil {
		return false, err
	}
	alreadyMount, err = mountBox(boxID)
	if err != nil && !wasUnlocked {
		lockBoxWithPreparationHeld(boxID, nil)
	}
	return alreadyMount, err
}

func unlockBoxHeld(boxID string, password string, boxEnc *conf.BoxEncryption) (err error) {
	if _, busy := boxLock.Load(boxID); busy {
		return errors.New(Conf.language(239))
	}
	if boxEnc == nil || len(boxEnc.WrappedDEK) == 0 {
		setEncryptedBoxState(boxID, EncryptedBoxStateError)
		return errors.New("no encrypted key material for box")
	}
	if IsBoxUnlocked(boxID) {
		if GetEncryptedBoxState(boxID) == EncryptedBoxStateError {
			return errors.New(Conf.Language(316))
		}
		setEncryptedBoxState(boxID, EncryptedBoxStateUnlocked)
		return nil
	}

	setEncryptedBoxState(boxID, EncryptedBoxStateUnlocking)
	// 获取 box 写锁,与 LockBox/unmount0 串行化,防止并发锁/解锁导致 db/DEK 状态不一致
	acquireBoxWriteLock(boxID)
	finalState := EncryptedBoxStateLocked
	defer func() {
		releaseBoxWriteLock(boxID)
		setEncryptedBoxState(boxID, finalState)
	}()

View on GitHub (pinned to 9f775e8a12)