{"record":{"id":"99d8e2f12f2dc6db","repo":"siyuan-note/siyuan","slug":"exporting-non-asset-files-from-encrypted-notebooks","errorCode":null,"errorMessage":"exporting non-asset files from encrypted notebooks is not supported","messagePattern":"exporting non-asset files from encrypted notebooks is not supported","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/export.go","lineNumber":937,"sourceCode":"}\n\n// exportResourcesEncryptedBox 校验资源导出是否跨越加密边界，并返回唯一允许的加密来源 boxID。\nfunc exportResourcesEncryptedBox(resourcePaths []string) (encryptedBoxID string, err error) {\n\thasNormalResource := false\n\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 {","sourceCodeStart":919,"sourceCodeEnd":955,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/export.go#L919-L955","documentation":"Returned by exportResourcesEncryptedBox when a resource path resolves into an encrypted notebook (ExtractBoxIDFromAssetsPath returns a boxID that IsEncryptedBox confirms) but the path is NOT under that notebook's assets/ directory (gulu.File.IsSubPath fails). SiYuan only supports exporting asset files from encrypted notebooks via the managed assets folder; exporting arbitrary .sy or other files from an encrypted box is refused to avoid leaking ciphertext structure.","triggerScenarios":"POST /api/export/exportResources with a path like data/<encrypted-box>/2023...sy or a file under the notebook root rather than under data/<encrypted-box>/assets/. The user (or a plugin) selected a document or internal file from an encrypted notebook instead of an asset.","commonSituations":"User tries to export a .sy doc or a non-asset file from an encrypted notebook via the resource-export endpoint instead of the document-export endpoint. Plugin iterates notebook files generically without filtering to assets/.","solutions":["Only pass asset paths (under data/<box>/assets/) of encrypted notebooks to ExportResources.","To export a document from an encrypted notebook, use ExportSYs/ExportMarkdownHTML/ExportDocx (the doc-level export path with the read-lock guard) instead.","Filter resource paths at the caller to the encrypted box's assets/ subtree before calling."],"exampleFix":"// before — passing a .sy path from an encrypted box\nmodel.ExportResources([]string{\"data/20230101abc/20230101xyz.sy\"}, name)\n// after — export the document via the doc-level path, or pass only assets\nmodel.ExportResources([]string{\"data/20230101abc/assets/image.png\"}, name)","handlingStrategy":"validation","validationCode":"// Ensure encrypted-notebook resources are strictly under that box's assets/ dir\nfor _, p := range resourcePaths {\n    full := filepath.Join(util.WorkspaceDir, p)\n    boxID := ExtractBoxIDFromAssetsPath(full)\n    if boxID != \"\" && IsEncryptedBox(boxID) {\n        assetsPath := filepath.Join(util.DataDir, boxID, \"assets\")\n        if !gulu.File.IsSubPath(assetsPath, full) {\n            return fmt.Errorf(\"non-asset path in encrypted box %s: %s\", boxID, p)\n        }\n    }\n}","typeGuard":"// isEncryptedBoxAsset reports whether p is under the encrypted box's assets/ folder.\nfunc isEncryptedBoxAsset(p string) bool {\n    boxID := ExtractBoxIDFromAssetsPath(p)\n    if boxID == \"\" || !IsEncryptedBox(boxID) {\n        return false\n    }\n    return gulu.File.IsSubPath(filepath.Join(util.DataDir, boxID, \"assets\"), p)\n}","tryCatchPattern":null,"preventionTips":["Only pass data/<encrypted-box>/assets/* paths to ExportResources.","Use the document-export endpoints for encrypted .sy files, not the resource endpoint.","Filter resource selections to the assets/ subtree of encrypted notebooks."],"tags":["encryption","export","resources","assets","access-control"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}