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 holdEncryptedBoxRequest when model.AcquireEncryptedBoxOperation(boxID) fails for an encrypted notebook. It means the notebook is encrypted and locked — no valid data-encryption key (DEK) is currently unlocked in memory — so the raw file/box API refuses to proceed. The lease system (box_lease.go) only allows the operation through once a DEK is unlocked; without it the request is rejected to prevent ciphertext leakage or plaintext corruption.
Source
Thrown at kernel/api/box_lease.go:45
func boxLeaseMiddleware(c *gin.Context) {
defer releaseRequestBoxLeases(c)
c.Next()
}
func holdEncryptedBoxRequest(c *gin.Context, boxID string) error {
if boxID == "" || !model.IsEncryptedBox(boxID) {
return nil
}
leases := requestBoxLeases(c)
for _, heldBoxID := range leases {
if heldBoxID == boxID {
return nil
}
}
if err := model.AcquireEncryptedBoxOperation(boxID); err != nil {
return errors.New("encrypted notebook is locked, please unlock it first")
}
leases = append(leases, boxID)
c.Set(requestBoxLeasesKey, leases)
return nil
}
func requestBoxLeases(c *gin.Context) []string {
value, ok := c.Get(requestBoxLeasesKey)
if !ok {
return nil
}
leases, _ := value.([]string)
return leases
}
func releaseEncryptedBoxRequest(c *gin.Context, boxID string) {
leases := requestBoxLeases(c)
for i := len(leases) - 1; i >= 0; i-- {View on GitHub (pinned to 251596fc0d)
Solutions
- Unlock the notebook in the SiYuan UI (Settings - Security / the notebook's lock icon) to load its DEK into memory, then retry.
- For automation, call the unlock API or ensure the passphrase is provided at startup before issuing raw file requests.
- Avoid raw file APIs (getFile/putFile/copyFile/renameFile/removeFile) on encrypted notebooks — use the encryption-aware APIs (upload, getBlockKramdown) which acquire the lease internally.
Defensive patterns
Strategy: validation
Validate before calling
// Before raw file ops on a notebook, ensure it is unlocked
if (await isEncryptedBox(boxID) && !(await isBoxUnlocked(boxID))) {
await unlockBox(boxID, passphrase); // triggers DEK load
} Try / catch
try { await rawFileOp(boxID, ...); }
catch (e) {
if (/locked, please unlock/.test(e.msg)) { await promptUserUnlock(boxID); /* retry once */ }
else throw e;
} Prevention
- Unlock encrypted notebooks at session start before any raw file access.
- Prefer encryption-aware APIs (upload, getBlockKramdown) over raw file APIs for encrypted boxes.
- Watch for lease expiry in long-running automation and re-unlock as needed.
When it happens
Trigger: Any HTTP API that funnels through holdEncryptedBoxRequest (raw file APIs on encrypted notebooks, export of encrypted content, etc.) when the user has not unlocked the notebook. model.AcquireEncryptedBoxOperation (model/crypto_lifecycle.go:140) returns an error when the box's DEK is not resident in memory — e.g. after a kernel restart, after lock timeout, or before the user has entered the passphrase.
Common situations: Kernel was just restarted and the encrypted notebook was not auto-unlocked. Passphrase not yet supplied in the current session. Long-running automation/CLI session where the unlock lease expired. Plugin trying to read raw files of an encrypted notebook without first triggering the unlock UI.
Related errors
- refuse to write decrypted asset inside workspace
- source is not an encrypted asset
- CLI does not support encrypted notebook [%s]
- CLI does not support files in encrypted notebooks
- path belongs to encrypted notebook [%s]: %s
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/7c07fd179d619071.
Report an issue: GitHub.