{"record":{"id":"fd7d01a7fa3e817a","repo":"siyuan-note/siyuan","slug":"encrypted-notebooks-do-not-support-this-operation-fd7d01","errorCode":null,"errorMessage":"Encrypted notebooks do not support this operation","messagePattern":"Encrypted notebooks do not support this operation","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"kernel/model/heading.go","lineNumber":218,"sourceCode":"\t\t\tlogging.LogWarnf(\"remove empty dir [%s] failed: %s\", subDir, removeErr)\n\t\t}\n\t}\n\n\tif nil == treenode.GetBlockTree(targetID) {\n\t\t// 目标块不存在时忽略处理\n\t\treturn\n\t}\n\n\ttargetTree, _ := LoadTreeByBlockID(targetID)\n\tif nil == targetTree {\n\t\t// 目标块不存在时忽略处理\n\t\treturn\n\t}\n\n\t// 禁止跨加密边界：Doc2Heading 会合并 srcTree 和 targetTree 的内容，\n\t// 不同加密笔记本各有独立 DEK，跨边界合并会导致密文用错 DEK 损坏数据\n\tif !IsSameCryptoBoundary(srcTree.Box, targetTree.Box) {\n\t\terr = errors.New(Conf.Language(313))\n\t\treturn\n\t}\n\n\tpivot := treenode.GetNodeInTree(targetTree, targetID)\n\tif nil == pivot {\n\t\terr = ErrBlockNotFound\n\t\treturn\n\t}\n\n\t// 生成文档历史 https://github.com/siyuan-note/siyuan/issues/14359\n\tgenerateOpTypeHistory(srcTree, HistoryOpUpdate)\n\n\t// 移动前先删除引用 https://github.com/siyuan-note/siyuan/issues/7819\n\tsql.DeleteRefsTreeQueue(srcTree)\n\tsql.DeleteRefsTreeQueue(targetTree)\n\n\tif ast.NodeListItem == pivot.Type {\n\t\tpivot = pivot.LastChild","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/heading.go#L200-L236","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Keep Doc2Heading within the same notebook, or ensure both source and target are in non-encrypted notebooks.","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.","As an API client, verify IsSameCryptoBoundary(srcBox, targetBox) returns true before calling Doc2Heading."],"exampleFix":"// before\nawait post('/api/filetree/doc2Heading', { srcID: docInEncryptedBox, targetID: docInPlainBox, after: true })\n\n// after — only convert within the same crypto boundary\nif (!isSameCryptoBoundary(srcBox, targetBox)) {\n  throw new Error('Cross-boundary Doc2Heading is blocked to prevent DEK mismatch corruption')\n}\nawait post('/api/filetree/doc2Heading', { srcID, targetID, after: true })","handlingStrategy":"validation","validationCode":"// Verify source and target are in the same crypto boundary before Doc2Heading\nfunction isSameCryptoBoundary(srcBoxID, targetBoxID) {\n  const srcEncrypted = notebooks.find(nb => nb.id === srcBoxID)?.encrypted ?? false\n  const targetEncrypted = notebooks.find(nb => nb.id === targetBoxID)?.encrypted ?? false\n  // Same boundary: both non-encrypted, or same encrypted notebook\n  if (!srcEncrypted && !targetEncrypted) return true\n  return srcBoxID === targetBoxID\n}\nif (isSameCryptoBoundary(srcBox, targetBox)) {\n  await post('/api/filetree/doc2Heading', { srcID, targetID, after })\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never perform Doc2Heading across different notebooks when either is encrypted.","Keep Doc2Heading within the same notebook to avoid crypto boundary issues.","Export and re-import content manually when cross-boundary transfer is needed."],"tags":["heading","doc-conversion","encryption","data-integrity","crypto-boundary"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}