siyuan-note/siyuan · critical

Encrypted notebooks do not support this operation

Error message

Encrypted notebooks do not support this operation

What it means

Thrown by Doc2Heading when the source document (srcTree) and target document (targetTree) reside in different cryptographic boundaries. IsSameCryptoBoundary(srcTree.Box, targetTree.Box) returns false. The code comment explains: Doc2Heading merges the content of srcTree and targetTree, and each encrypted notebook has its own Data Encryption Key (DEK). Merging across boundaries would cause ciphertext encrypted with one DEK to be interpreted under another, corrupting data. This is a critical data-integrity guard.

Source

Thrown at kernel/model/heading.go:218

			logging.LogWarnf("remove empty dir [%s] failed: %s", subDir, removeErr)
		}
	}

	if nil == treenode.GetBlockTree(targetID) {
		// 目标块不存在时忽略处理
		return
	}

	targetTree, _ := LoadTreeByBlockID(targetID)
	if nil == targetTree {
		// 目标块不存在时忽略处理
		return
	}

	// 禁止跨加密边界:Doc2Heading 会合并 srcTree 和 targetTree 的内容,
	// 不同加密笔记本各有独立 DEK,跨边界合并会导致密文用错 DEK 损坏数据
	if !IsSameCryptoBoundary(srcTree.Box, targetTree.Box) {
		err = errors.New(Conf.Language(313))
		return
	}

	pivot := treenode.GetNodeInTree(targetTree, targetID)
	if nil == pivot {
		err = ErrBlockNotFound
		return
	}

	// 生成文档历史 https://github.com/siyuan-note/siyuan/issues/14359
	generateOpTypeHistory(srcTree, HistoryOpUpdate)

	// 移动前先删除引用 https://github.com/siyuan-note/siyuan/issues/7819
	sql.DeleteRefsTreeQueue(srcTree)
	sql.DeleteRefsTreeQueue(targetTree)

	if ast.NodeListItem == pivot.Type {
		pivot = pivot.LastChild

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Keep Doc2Heading within the same notebook, or ensure both source and target are in non-encrypted notebooks.
  2. If you need content from an encrypted notebook in a plain notebook, export the content from the encrypted notebook and import it manually into the target.
  3. As an API client, verify IsSameCryptoBoundary(srcBox, targetBox) returns true before calling Doc2Heading.

Example fix

// before
await post('/api/filetree/doc2Heading', { srcID: docInEncryptedBox, targetID: docInPlainBox, after: true })

// after — only convert within the same crypto boundary
if (!isSameCryptoBoundary(srcBox, targetBox)) {
  throw new Error('Cross-boundary Doc2Heading is blocked to prevent DEK mismatch corruption')
}
await post('/api/filetree/doc2Heading', { srcID, targetID, after: true })
Defensive patterns

Strategy: validation

Validate before calling

// Verify source and target are in the same crypto boundary before Doc2Heading
function isSameCryptoBoundary(srcBoxID, targetBoxID) {
  const srcEncrypted = notebooks.find(nb => nb.id === srcBoxID)?.encrypted ?? false
  const targetEncrypted = notebooks.find(nb => nb.id === targetBoxID)?.encrypted ?? false
  // Same boundary: both non-encrypted, or same encrypted notebook
  if (!srcEncrypted && !targetEncrypted) return true
  return srcBoxID === targetBoxID
}
if (isSameCryptoBoundary(srcBox, targetBox)) {
  await post('/api/filetree/doc2Heading', { srcID, targetID, after })
}

Prevention

When it happens

Trigger: Calling POST /api/filetree/doc2Heading where srcID is in one notebook and targetID is in a different notebook, and at least one of the two notebooks is encrypted (or both are encrypted with different DEKs). Even two different encrypted notebooks are different crypto boundaries. Also fires for encrypted-to-plain and plain-to-encrypted crosses.

Common situations: User tries to drag a document from an encrypted notebook into a document in a plain notebook (or vice versa); user tries to merge documents across two different encrypted notebooks; an API client performs cross-notebook Doc2Heading without checking encryption boundaries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/fd7d01a7fa3e817a. Report an issue: GitHub.