{"record":{"id":"7c07fd179d619071","repo":"siyuan-note/siyuan","slug":"encrypted-notebook-is-locked-please-unlock-it-fir","errorCode":null,"errorMessage":"encrypted notebook is locked, please unlock it first","messagePattern":"encrypted notebook is locked, please unlock it first","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/box_lease.go","lineNumber":45,"sourceCode":"\nfunc boxLeaseMiddleware(c *gin.Context) {\n\tdefer releaseRequestBoxLeases(c)\n\tc.Next()\n}\n\nfunc holdEncryptedBoxRequest(c *gin.Context, boxID string) error {\n\tif boxID == \"\" || !model.IsEncryptedBox(boxID) {\n\t\treturn nil\n\t}\n\tleases := requestBoxLeases(c)\n\tfor _, heldBoxID := range leases {\n\t\tif heldBoxID == boxID {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif err := model.AcquireEncryptedBoxOperation(boxID); err != nil {\n\t\treturn errors.New(\"encrypted notebook is locked, please unlock it first\")\n\t}\n\tleases = append(leases, boxID)\n\tc.Set(requestBoxLeasesKey, leases)\n\treturn nil\n}\n\nfunc requestBoxLeases(c *gin.Context) []string {\n\tvalue, ok := c.Get(requestBoxLeasesKey)\n\tif !ok {\n\t\treturn nil\n\t}\n\tleases, _ := value.([]string)\n\treturn leases\n}\n\nfunc releaseEncryptedBoxRequest(c *gin.Context, boxID string) {\n\tleases := requestBoxLeases(c)\n\tfor i := len(leases) - 1; i >= 0; i-- {","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/api/box_lease.go#L27-L63","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before raw file ops on a notebook, ensure it is unlocked\nif (await isEncryptedBox(boxID) && !(await isBoxUnlocked(boxID))) {\n  await unlockBox(boxID, passphrase); // triggers DEK load\n}","typeGuard":null,"tryCatchPattern":"try { await rawFileOp(boxID, ...); }\ncatch (e) {\n  if (/locked, please unlock/.test(e.msg)) { await promptUserUnlock(boxID); /* retry once */ }\n  else throw e;\n}","preventionTips":["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."],"tags":["encryption","security","notebook","kernel"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}