siyuan-note/siyuan · error

cannot change master password while encrypted notebooks are

Error message

cannot change master password while encrypted notebooks are unlocked (DEKs in memory), lock them first

What it means

Thrown by ChangeMasterPassword when cachedDEKs is non-empty, meaning at least one encrypted notebook is currently unlocked (its DEK is in memory). Changing the master password re-wraps all WrappedDEKs with a new KEK; if a notebook is unlocked, its cached DEK would become inconsistent with the on-disk WrappedDEK after the KEK switch. This guard prevents that inconsistency.

Source

Thrown at kernel/model/crypto.go:1696

//	Phase 2: 切换全局 verifier
//	Phase 3: 写入各 box conf + backup
//	Phase 4: 清除 manifest
//
// 注意:必须在所有加密笔记本都已 Unmount 的状态下调用(DEK 不在内存),否则新旧 KEK 切换会让缓存与磁盘不一致。
func ChangeMasterPassword(oldPassword, newPassword string) error {
	if len(newPassword) == 0 {
		return errors.New("new password must not be empty")
	}

	notebookCryptoMu.Lock()
	defer notebookCryptoMu.Unlock()

	// 改密期间不能有已 Mount 的加密笔记本(DEK 在内存),否则新旧 KEK 切换会让缓存与磁盘不一致
	cachedDEKsLock.RLock()
	dekCount := len(cachedDEKs)
	cachedDEKsLock.RUnlock()
	if dekCount > 0 {
		return errors.New("cannot change master password while encrypted notebooks are unlocked (DEKs in memory), lock them first")
	}

	oldKEK, err := deriveKEK(oldPassword)
	if err != nil {
		return err
	}
	defer zeroAndClear(oldKEK)

	Conf.m.Lock()
	nc := Conf.NotebookCrypto
	Conf.m.Unlock()

	params, validErr := util.ValidateArgon2Params(nc.KDFParams)
	if validErr != nil {
		return validErr
	}
	newKEK := util.DeriveKey(newPassword, nc.MasterSalt, params)
	defer zeroAndClear(newKEK)

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Lock all encrypted notebooks first (call LockBox or use the UI lock action), then retry ChangeMasterPassword.
  2. If using the API, call getEncryptedNotebookStatus to verify all encrypted notebooks show a locked state before calling changeMasterPassword.
  3. Reduce AutoLockMinutes so notebooks auto-lock sooner, reducing the window where this guard blocks password changes.
Defensive patterns

Strategy: validation

Validate before calling

// Check that no encrypted notebooks are unlocked before changing password:
// (This mirrors the internal check in ChangeMasterPassword)
// At the API level, call getEncryptedNotebookStatus first and verify
// all boxes show locked state. There is no public 'lock all' API, so
// the user must lock each notebook individually via the UI or API.

Prevention

When it happens

Trigger: Called via changeMasterPassword API while one or more encrypted notebooks are in the Unlocked state (DEK cached in memory). The check acquires cachedDEKsLock.RLock, reads len(cachedDEKs), and rejects if > 0.

Common situations: User unlocks a notebook, then tries to change the master password without locking it first. Auto-lock hasn't triggered yet. A second device synced new notebooks that are auto-unlocked on startup. User forgot to lock after a session.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/c0cab84244bd478f. Report an issue: GitHub.