{"record":{"id":"0bf99ac08cb91838","repo":"siyuan-note/siyuan","slug":"exporting-resources-across-encrypted-notebook-boun","errorCode":null,"errorMessage":"exporting resources across encrypted notebook boundaries is not supported","messagePattern":"exporting resources across encrypted notebook boundaries is not supported","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/export.go","lineNumber":942,"sourceCode":"\tfor _, resourcePath := range resourcePaths {\n\t\tresourceFullPath := filepath.Join(util.WorkspaceDir, resourcePath)\n\t\tif !util.IsAbsPathInWorkspace(resourceFullPath) {\n\t\t\treturn \"\", errors.New(\"resource path [\" + resourcePath + \"] is not in workspace\")\n\t\t}\n\t\tboxID := ExtractBoxIDFromAssetsPath(resourceFullPath)\n\t\tif boxID == \"\" || !IsEncryptedBox(boxID) {\n\t\t\thasNormalResource = true\n\t\t\tcontinue\n\t\t}\n\n\t\tassetsPath := filepath.Join(util.DataDir, boxID, \"assets\")\n\t\tif !gulu.File.IsSubPath(assetsPath, resourceFullPath) {\n\t\t\treturn \"\", errors.New(\"exporting non-asset files from encrypted notebooks is not supported\")\n\t\t}\n\t\tif encryptedBoxID == \"\" {\n\t\t\tencryptedBoxID = boxID\n\t\t} else if encryptedBoxID != boxID {\n\t\t\treturn \"\", errors.New(\"exporting resources across encrypted notebook boundaries is not supported\")\n\t\t}\n\t}\n\tif encryptedBoxID != \"\" && hasNormalResource {\n\t\treturn \"\", errors.New(\"exporting encrypted and normal notebook resources together is not supported\")\n\t}\n\treturn\n}\n\nfunc ExportPreview(id string, fillCSSVar bool) (retStdHTML string) {\n\tif exportErr := withExportReadLockByBlockID(id, func() error {\n\t\tblockRefMode := Conf.Export.BlockRefMode\n\t\tbt := getExportBlockTree(id)\n\t\tif nil == bt {\n\t\t\treturn nil\n\t\t}\n\n\t\ttree := prepareExportTree(bt)\n\t\tif numberErr := applyHeadingNumbersForExport(tree, bt, false); nil != numberErr {","sourceCodeStart":924,"sourceCodeEnd":960,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/export.go#L924-L960","documentation":"Returned by exportResourcesEncryptedBox when the resource list contains assets from two or more *different* encrypted notebooks (encryptedBoxID is already set and a later path yields a different encrypted boxID). Cross-notebook encrypted export is refused because each encrypted box has its own DEK and read-lock scope, and the unified export path can only hold one box's lock.","triggerScenarios":"POST /api/export/exportResources where resourcePaths mixes assets from data/boxA/assets/ and data/boxB/assets/, both boxA and boxB being encrypted notebooks. Detected when the second distinct encrypted boxID is encountered in the loop.","commonSituations":"User multi-selects assets across two encrypted notebooks in the UI and chooses 'export resources'. A plugin batches assets from multiple encrypted boxes into one export call.","solutions":["Split the export into separate ExportResources calls, one per encrypted notebook.","If the assets must be in one archive, first export each box separately then combine the resulting zips outside SiYuan.","Filter the selection so all encrypted assets come from a single box."],"exampleFix":"// before — mixing two encrypted boxes in one call\nmodel.ExportResources([]string{\n    \"data/boxA/assets/a.png\",\n    \"data/boxB/assets/b.png\",\n}, name)\n// after — one encrypted box per call\nmodel.ExportResources([]string{\"data/boxA/assets/a.png\"}, name)\nmodel.ExportResources([]string{\"data/boxB/assets/b.png\"}, name)","handlingStrategy":"validation","validationCode":"// Ensure all encrypted resources belong to a single box\nvar firstEncBox string\nfor _, p := range resourcePaths {\n    full := filepath.Join(util.WorkspaceDir, p)\n    boxID := ExtractBoxIDFromAssetsPath(full)\n    if boxID == \"\" || !IsEncryptedBox(boxID) {\n        continue\n    }\n    if firstEncBox == \"\" {\n        firstEncBox = boxID\n    } else if firstEncBox != boxID {\n        return errors.New(\"resources span multiple encrypted notebooks; split the export\")\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["One encrypted notebook per ExportResources call.","Split multi-box selections before exporting.","Combine resulting zips outside SiYuan if a single archive is needed."],"tags":["encryption","export","resources","access-control","multi-notebook"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}