{"record":{"id":"e4bfd4fb9e9aa320","repo":"siyuan-note/siyuan","slug":"encrypted-box-db-not-opened-for-box-database","errorCode":null,"errorMessage":"encrypted box db not opened for box ","messagePattern":"encrypted box db not opened for box ","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/sql/database.go","lineNumber":1527,"sourceCode":"\t}\n\tif nil == db {\n\t\treturn nil\n\t}\n\treturn db.QueryRow(query, args...)\n}\n\n// queryForBox 按 box 路由查询多行。加密笔记本用独立 db，否则用全局 db。boxID 为空走全局。\n// 加密笔记本未解锁时返回错误——绝不回退全局库。\nfunc queryForBox(boxID, query string, args ...any) (*sql.Rows, error) {\n\tquery = strings.TrimSpace(query)\n\tif \"\" == query {\n\t\treturn nil, errors.New(\"statement is empty\")\n\t}\n\tif boxDB := GetEncryptedDB(boxID); boxDB != nil {\n\t\treturn boxDB.Query(query, args...)\n\t}\n\tif IsEncryptedBoxFn != nil && IsEncryptedBoxFn(boxID) {\n\t\treturn nil, errors.New(\"encrypted box db not opened for box \" + boxID)\n\t}\n\tif nil == db {\n\t\treturn nil, errors.New(\"database is nil\")\n\t}\n\treturn db.Query(query, args...)\n}\n\nfunc queryForBoxContext(ctx context.Context, boxID, query string, args ...any) (*sql.Rows, error) {\n\tquery = strings.TrimSpace(query)\n\tif \"\" == query {\n\t\treturn nil, errors.New(\"statement is empty\")\n\t}\n\tif boxDB := GetEncryptedDB(boxID); boxDB != nil {\n\t\treturn boxDB.QueryContext(ctx, query, args...)\n\t}\n\tif IsEncryptedBoxFn != nil && IsEncryptedBoxFn(boxID) {\n\t\treturn nil, errors.New(\"encrypted box db not opened for box \" + boxID)\n\t}","sourceCodeStart":1509,"sourceCodeEnd":1545,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/sql/database.go#L1509-L1545","documentation":"queryForBox routes a query to the notebook's dedicated encrypted database when the box is an encrypted notebook. If the box is marked encrypted (IsEncryptedBoxFn) but its DB handle is not currently open (GetEncryptedDB returns nil), the query fails closed instead of silently reading the global database, protecting plaintext leakage.","triggerScenarios":"Calling QueryBacklinkRefDefsInBox, QueryEmptyContentEmbedBlocksInBox, QueryRootBlockByConditionInBox, queryNames, queryAliases, or queryDocTitles with a boxID of an encrypted notebook that is locked/not yet unlocked.","commonSituations":"Queries issued before the user unlocked the encrypted notebook; a race where the encrypted DB was closed but the box is still flagged encrypted; referencing a box by stale ID after workspace reload.","solutions":["Unlock / open the encrypted notebook so its DB handle is registered before running box-scoped queries","Check IsEncryptedBoxFn(boxID) && GetEncryptedDB(boxID)==nil before querying and prompt the user to unlock","Retry after the box DB is opened"],"exampleFix":"// before\nrows, err := sql.QueryDocTitles(boxID)\n// after\nif sql.IsEncryptedBoxFn(boxID) && sql.GetEncryptedDB(boxID) == nil {\n    return errors.New(\"unlock the encrypted notebook first\")\n}\nrows, err := sql.QueryDocTitles(boxID)","handlingStrategy":"validation","validationCode":"if sql.IsEncryptedBoxFn(boxID) && sql.GetEncryptedDB(boxID) == nil {\n    return errors.New(\"encrypted notebook is locked; unlock before querying\")\n}","typeGuard":null,"tryCatchPattern":"rows, err := sql.QueryDocTitles(boxID)\nif err != nil && strings.HasPrefix(err.Error(), \"encrypted box db not opened\") {\n    return promptUserToUnlock(boxID)\n}","preventionTips":["Ensure encrypted notebooks are unlocked before indexing/search jobs run","Check the encrypted-box state before issuing box-scoped SQL","Handle workspace reload races where DB handles close before queued queries"],"tags":["sqlite","encrypted-notebook","database","fail-closed"],"backgroundTag":"database-query-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}