siyuan-note/siyuan · error

encrypted notebook is locked, please unlock it first

Error message

encrypted notebook is locked, please unlock it first

What it means

Returned by GetDEKIfUnlocked when the box is recognized as encrypted but isBoxUnlockedForAccess returns false — meaning the notebook is in a locked state (auto-locked by idle timeout, manually locked via LockBox, or freshly started app with no DEK in memory). This is the fail-closed path from issue #18034: the function refuses to hand out a DEK so filesys will not silently write plaintext to an encrypted notebook.

Source

Thrown at kernel/model/crypto.go:2093

func IsEncryptedAssetPath(absPath string) bool {
	boxID := ExtractBoxIDFromAssetsPath(absPath)
	return boxID != "" && IsEncryptedBox(boxID)
}

// GetDEKIfUnlocked 返回已解锁加密笔记本的 DEK(副本)。
// 非加密笔记本返回 (nil, nil)——filesys 据此原样读写,对普通笔记本透明。
// 加密但未解锁(DEK 不在内存)返回 (nil, error)——filesys 的加解密函数遇 error 后拒绝读写,
// 避免加密笔记本在未解锁状态下静默以明文落盘(深度防御,见 issue #18034)。
func GetDEKIfUnlocked(boxID string) ([]byte, error) {
	if boxID != "" && !ast.IsNodeIDPattern(boxID) {
		return nil, errors.New("invalid notebook ID")
	}
	if !IsEncryptedBox(boxID) {
		return nil, nil
	}
	repairEncryptedBoxStateFromDEK(boxID)
	if !isBoxUnlockedForAccess(boxID) {
		return nil, errors.New("encrypted notebook is locked, please unlock it first")
	}
	cachedDEKsLock.RLock()
	defer cachedDEKsLock.RUnlock()
	dek, ok := cachedDEKs[boxID]
	if !ok {
		return nil, errors.New("encrypted notebook is locked, please unlock it first")
	}
	ret := make([]byte, len(dek))
	copy(ret, dek)
	return ret, nil
}

// HoldBoxReadLock 获取 box 读锁,防止 LockBox 在持锁期间清除缓存/临时文件。
// 调用方完成解密输出后必须调 ReleaseBoxReadLock。
func HoldBoxReadLock(boxID string) {
	if !IsEncryptedBox(boxID) {
		acquireBoxReadLock(boxID)
		return

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Prompt the user to unlock the encrypted notebook via UnlockBox (provide password), then retry the operation.
  2. If building automation, call UnlockBox(boxID, password) before any asset/doc operation and check its error before proceeding.
  3. Increase or disable the auto-lock timeout in notebook crypto settings if the lock fires too aggressively for the workflow.

Example fix

// before
dek, err := model.GetDEKIfUnlocked(boxID)
if err != nil { return err }
// after
dek, err := model.GetDEKIfUnlocked(boxID)
if err != nil {
    if unlockErr := model.UnlockBox(boxID, password); unlockErr != nil {
        return unlockErr
    }
    dek, err = model.GetDEKIfUnlocked(boxID)
    if err != nil { return err }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check unlock state before calling GetDEKIfUnlocked
if model.IsEncryptedBox(boxID) && !model.IsBoxUnlocked(boxID) {
    // prompt user to unlock
}

Try / catch

dek, err := model.GetDEKIfUnlocked(boxID)
if err != nil {
    if strings.Contains(err.Error(), "locked") {
        // prompt password, then:
        if unlockErr := model.UnlockBox(boxID, password); unlockErr != nil {
            return unlockErr
        }
        dek, err = model.GetDEKIfUnlocked(boxID)
    }
    if err != nil {
        return err
    }
}

Prevention

When it happens

Trigger: Any asset read/write, block-tree access, or file copy that calls GetDEKIfUnlocked after the auto-lock timer fires or after LockBox was called. Also occurs on a fresh kernel restart where the encrypted notebook has not yet been unlocked in this session.

Common situations: User left the app idle past the auto-lock interval and then tried to open an asset or export a doc. App was restarted and the encrypted notebook hasn't been unlocked yet. LockBox was triggered programmatically (e.g., by a sync or publish flow) while a long-running export was queued.

Related errors


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