siyuan-note/siyuan · error

encrypted box db not opened for box

Error message

encrypted box db not opened for box 

What it means

Thrown by sql.CheckReadonlyStatementInBox when the target notebook is configured as encrypted but its per-box SQLCipher database connection has not been opened yet. SiYuan keeps encrypted notebooks in separate SQLCipher databases registered in a process map (encryptedDBs); until UnlockBox calls sql.OpenEncryptedDB(boxID, dek), GetEncryptedDB returns nil and the validator refuses to run rather than silently validating the statement against the wrong (global siyuan.db) connection.

Source

Thrown at kernel/sql/stmt_validate.go:181

// 注意:若字符串里在语法上还有第二条及以后的语句,本函数只针对「首条」对应的 stmt 做判断,
// 不会拒绝多语句。与 CheckSingleStatement 组合即可得到「单条 + 只读」策略。
// 仅允许 SELECT 和 WITH 查询,避免 SQLite 将 ATTACH、DETACH 和事务控制语句标记为只读后放行。
func CheckReadonlyStatement(stmt string) error {
	return checkReadonlyStatement(stmt, db)
}

// CheckAssetContentReadonlyStatement 在资源文件内容数据库连接上检查 SQL 是否只读。
func CheckAssetContentReadonlyStatement(stmt string) error {
	return checkReadonlyStatement(stmt, assetContentDB)
}

// CheckReadonlyStatementInBox 在指定笔记本对应的数据库连接上检查 SQL 是否只读。
func CheckReadonlyStatementInBox(stmt, boxID string) error {
	targetDB := db
	if boxDB := GetEncryptedDB(boxID); nil != boxDB {
		targetDB = boxDB
	} else if IsEncryptedBoxFn != nil && IsEncryptedBoxFn(boxID) {
		return errors.New("encrypted box db not opened for box " + boxID)
	}
	return checkReadonlyStatement(stmt, targetDB)
}

func checkReadonlyStatement(stmt string, targetDB *sql.DB) error {
	if strings.TrimSpace(stmt) == "" {
		return errors.New("SQL statement is empty")
	}
	if !isReadonlyQueryStatement(stmt) {
		return errors.New("SQL statement is not a read-only query")
	}
	if nil == targetDB {
		return errors.New("database is nil")
	}
	ctx := context.Background()
	conn, err := targetDB.Conn(ctx)
	if err != nil {
		return err

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Unlock the encrypted notebook in the SiYuan UI first (UnlockBox opens the SQLCipher db via sql.OpenEncryptedDB), then re-issue the SQL query
  2. Verify the boxID with /api/notebook/lsbook and confirm the box really is the one you want; a mistyped ID pointing at a locked encrypted box triggers this
  3. If automating via API/MCP, perform the unlock step for encrypted boxes before issuing any box-scoped SQL
  4. Query only non-encrypted boxes with raw SQL, or use search APIs that do not require an open box db connection

Example fix

// before
err := sql.CheckReadonlyStatementInBox(stmt, boxID)

// after
if sql.GetEncryptedDB(boxID) == nil && sql.IsEncryptedBoxFn != nil && sql.IsEncryptedBoxFn(boxID) {
    // unlock the box first: UnlockBox derives the dek and calls sql.OpenEncryptedDB(boxID, dek)
    return fmt.Errorf("box %s is locked; unlock it before querying", boxID)
}
err := sql.CheckReadonlyStatementInBox(stmt, boxID)
Defensive patterns

Strategy: validation

Validate before calling

if sql.IsEncryptedBoxFn != nil && sql.IsEncryptedBoxFn(boxID) && sql.GetEncryptedDB(boxID) == nil {
    return fmt.Errorf("encrypted box %s is locked; unlock it before running SQL", boxID)
}
err := sql.CheckReadonlyStatementInBox(stmt, boxID)

Try / catch

err := sql.CheckReadonlyStatementInBox(stmt, boxID)
if err != nil {
    if strings.HasPrefix(err.Error(), "encrypted box db not opened") {
        // unlock the box, then retry once
    }
}

Prevention

When it happens

Trigger: Calling a SQL API that routes by boxID while the box is encrypted but locked: /api/query with a stmt referencing the encrypted box (kernel/api/sql.go:70), /api/search with box-filtered SQL (kernel/api/search.go:446), MCP execute-sql with boxID (kernel/mcp/tools/sql.go:81), or document statistics (kernel/model/document_stat.go:151). IsEncryptedBoxFn(boxID) returns true but GetEncryptedDB(boxID) is nil, i.e. the box was never unlocked in this kernel session.

Common situations: Kernel restarted so previously unlocked encrypted boxes are locked again; automation scripts (CLI/MCP) run against a workspace before unlocking the notebook; workspace synced to a new device and SQL jobs run before first unlock; a wrong boxID that happens to belong to an encrypted box.

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/780096015262a7da. Report an issue: GitHub.