{"record":{"id":"9f5a159ee9c24529","repo":"siyuan-note/siyuan","slug":"please-unlock-the-encrypted-notebook-first-9f5a15","errorCode":null,"errorMessage":"Please unlock the encrypted notebook first","messagePattern":"Please unlock the encrypted notebook first","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history.go","lineNumber":194,"sourceCode":"\trelPath := strings.TrimPrefix(filepath.ToSlash(historyPath), filepath.ToSlash(util.HistoryDir))\n\trelPath = strings.TrimPrefix(relPath, \"/\")\n\tpathParts := strings.SplitN(relPath, \"/\", 3)\n\tciphertext := util.IsCiphertext(data)\n\tif ciphertext {\n\t\tif len(pathParts) < 3 || !ast.IsNodeIDPattern(pathParts[1]) {\n\t\t\terr = errors.New(\"encrypted document history is missing notebook context\")\n\t\t\treturn\n\t\t}\n\t\thistBoxID := pathParts[1]\n\t\tif !IsEncryptedBox(histBoxID) {\n\t\t\terr = fmt.Errorf(\"encrypted document history has no matching notebook [%s]\", histBoxID)\n\t\t\treturn\n\t\t}\n\t\tHoldBoxReadLock(histBoxID)\n\t\tdefer ReleaseBoxReadLock(histBoxID)\n\t\tdek, dekErr := GetDEKIfUnlocked(histBoxID)\n\t\tif dekErr != nil {\n\t\t\terr = errors.New(Conf.Language(314))\n\t\t\treturn\n\t\t}\n\t\tdata, err = DecryptFile(histBoxID, pathParts[2], dek, data)\n\t\tif err != nil {\n\t\t\tlogging.LogErrorf(\"decrypt history [%s] failed: %s\", historyPath, err)\n\t\t\treturn\n\t\t}\n\t} else if len(pathParts) >= 2 && IsEncryptedBox(pathParts[1]) {\n\t\terr = fmt.Errorf(\"encrypted notebook document history is plaintext [%s]\", pathParts[1])\n\t\treturn\n\t}\n\tisLargeDoc = 1024*1024*1 <= len(data)\n\n\tluteEngine := NewLute()\n\thistoryTree, err := dataparser.ParseJSONWithoutFix(data, luteEngine.ParseOptions)\n\tif err != nil {\n\t\tlogging.LogErrorf(\"parse tree from file [%s] failed: %s\", historyPath, err)\n\t\treturn","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/history.go#L176-L212","documentation":"Thrown by GetDocHistoryContent when reading the content of an encrypted document's history snapshot. The history path is parsed to extract the notebook ID (histBoxID), which is confirmed to be encrypted (IsEncryptedBox). The function then acquires a read lock on the box and calls GetDEKIfUnlocked(histBoxID) to obtain the Data Encryption Key. If GetDEKIfUnlocked returns an error — meaning the notebook is encrypted but not currently unlocked (no DEK in memory) — the function returns Conf.Language(314) = \"Please unlock the encrypted notebook first\". The history content cannot be decrypted without the DEK.","triggerScenarios":"Calling the history content API (POST /api/history/getDocHistoryContent) for a history snapshot that belongs to an encrypted notebook, while that notebook is currently locked (the user has not entered the password to unlock it). The encrypted history .sy file is ciphertext and cannot be parsed without decrypting it with the DEK.","commonSituations":"User opens the history panel for a document in an encrypted notebook that is currently locked; the kernel was restarted (DEKs are session-only) and the user tries to view history before re-entering the notebook password; an API client tries to read encrypted history without first unlocking the notebook.","solutions":["Unlock the encrypted notebook first (enter the notebook password via the UI or the unlock API), then retry viewing the history content.","If the kernel was restarted, re-unlock the notebook — DEKs are not persisted across kernel restarts.","As an API client, call the notebook unlock endpoint with the correct password before requesting history content from encrypted notebooks."],"exampleFix":"// before\nconst history = await post('/api/history/getDocHistoryContent', { historyPath: encryptedDocHistoryPath })\n\n// after — unlock the encrypted notebook first\nawait post('/api/notebook/openNotebook', { notebook: encryptedBoxId, password: notebookPassword })\nconst history = await post('/api/history/getDocHistoryContent', { historyPath: encryptedDocHistoryPath })","handlingStrategy":"validation","validationCode":"// Unlock the encrypted notebook before reading its history content\nif (isEncryptedNotebook(histBoxID) && !isNotebookUnlocked(histBoxID)) {\n  await post('/api/notebook/unlockNotebook', { notebook: histBoxID, password })\n}\nconst history = await post('/api/history/getDocHistoryContent', { historyPath })","typeGuard":null,"tryCatchPattern":"// Catch the unlock-required error and prompt for password\ntry {\n  const history = await post('/api/history/getDocHistoryContent', { historyPath })\n} catch (e) {\n  if (e.message.includes('unlock the encrypted notebook')) {\n    const password = await promptForPassword()\n    await post('/api/notebook/unlockNotebook', { notebook: histBoxID, password })\n    const history = await post('/api/history/getDocHistoryContent', { historyPath })\n  } else {\n    throw e\n  }\n}","preventionTips":["Unlock encrypted notebooks at the start of each session before accessing their history.","After a kernel restart, re-enter passwords for all encrypted notebooks you need to access.","Track unlock state in the UI and prompt for password before history operations on encrypted notebooks."],"tags":["history","encryption","encrypted-notebook","unlock","dek"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}