{"record":{"id":"a86c37051d7abf19","repo":"siyuan-note/siyuan","slug":"exporting-encrypted-and-normal-notebook-resources","errorCode":null,"errorMessage":"exporting encrypted and normal notebook resources together is not supported","messagePattern":"exporting encrypted and normal notebook resources together is not supported","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/export.go","lineNumber":946,"sourceCode":"\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 {\n\t\t\treturn numberErr\n\t\t}\n\t\ttree = exportTree(tree, false, false, true,\n\t\t\tblockRefMode, Conf.Export.BlockEmbedMode, Conf.Export.FileAnnotationRefMode,","sourceCodeStart":928,"sourceCodeEnd":964,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/export.go#L928-L964","documentation":"Returned by exportResourcesEncryptedBox after the loop when at least one resource came from an encrypted notebook (encryptedBoxID set) AND at least one came from a normal (non-encrypted) source (hasNormalResource true). Mixing encrypted and plaintext resources in a single export is refused because the encrypted path requires holding one box's read-lock and decrypting, while the normal path does not — combining them would leak which resources are encrypted and complicate the lock scope.","triggerScenarios":"POST /api/export/exportResources with resourcePaths containing both data/<encrypted-box>/assets/* entries and entries from normal notebooks or the global assets folder. The loop sets encryptedBoxID and hasNormalResource, then the post-loop check fires.","commonSituations":"User selects a mix of encrypted-notebook assets and normal-notebook/global assets and triggers a single resource export. A plugin naively batches all referenced assets without distinguishing encrypted sources.","solutions":["Partition the resource list: call ExportResources once for the encrypted-box assets and once for the normal assets.","Filter the selection so it is entirely encrypted or entirely normal before exporting.","If a single archive is required, export each group separately and merge the zips afterward."],"exampleFix":"// before — encrypted + normal mixed\nmodel.ExportResources([]string{\n    \"data/encBox/assets/secret.png\",   // encrypted\n    \"data/notebook/assets/public.png\", // normal\n}, name)\n// after — split by encryption status\nmodel.ExportResources([]string{\"data/encBox/assets/secret.png\"}, name)\nmodel.ExportResources([]string{\"data/notebook/assets/public.png\"}, name)","handlingStrategy":"validation","validationCode":"// Ensure resources are not a mix of encrypted and normal\nhasEnc, hasNormal := false, false\nfor _, p := range resourcePaths {\n    boxID := ExtractBoxIDFromAssetsPath(filepath.Join(util.WorkspaceDir, p))\n    if boxID != \"\" && IsEncryptedBox(boxID) {\n        hasEnc = true\n    } else {\n        hasNormal = true\n    }\n}\nif hasEnc && hasNormal {\n    return errors.New(\"partition encrypted and normal resources into separate exports\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Partition the resource list by encryption status before exporting.","Never batch encrypted and plaintext assets in one ExportResources call.","Merge the separately-produced zips afterward if a single archive is required."],"tags":["encryption","export","resources","access-control","mixed-sources"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}