{"record":{"id":"f9c390e75f91e57d","repo":"siyuan-note/siyuan","slug":"accessing-assets-in-encrypted-notebook-s-is-not","errorCode":null,"errorMessage":"accessing assets in encrypted notebook [%s] is not supported","messagePattern":"accessing assets in encrypted notebook \\[(.+?)\\] is not supported","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":1064,"sourceCode":"\tassetDirIndex := -1\n\tswitch {\n\tcase len(parts) > 1 && parts[0] == \"assets\":\n\t\tassetDirIndex = 0\n\tcase len(parts) > 2 && ast.IsNodeIDPattern(parts[0]):\n\t\tfor i := 1; i < len(parts)-1; i++ {\n\t\t\tif parts[i] == \"assets\" {\n\t\t\t\tassetDirIndex = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif assetDirIndex > 0 {\n\t\t\tboxConfPath := filepath.Join(util.DataDir, parts[0], \".siyuan\", \"conf.json\")\n\t\t\tif !filelock.IsExist(boxConfPath) {\n\t\t\t\terr = fmt.Errorf(\"asset path does not belong to a notebook: %s\", assetPath)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif IsEncryptedBox(parts[0]) {\n\t\t\t\terr = fmt.Errorf(\"accessing assets in encrypted notebook [%s] is not supported\", parts[0])\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\tif assetDirIndex < 0 {\n\t\terr = fmt.Errorf(\"path is not under an assets directory: %s\", assetPath)\n\t\treturn\n\t}\n\n\tassetRootParts := parts[:assetDirIndex+1]\n\tassetRoot := filepath.Join(util.DataDir, filepath.FromSlash(strings.Join(assetRootParts, \"/\")))\n\tif !gulu.File.IsSubPath(assetRoot, absPath) {\n\t\terr = fmt.Errorf(\"path is not a child of assets directory: %s\", assetPath)\n\t\treturn\n\t}\n\n\tresolvedRoot, evalErr := ResolveAssetPathWithMissingLeaf(assetRoot)\n\tif evalErr != nil {","sourceCodeStart":1046,"sourceCodeEnd":1082,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/assets.go#L1046-L1082","documentation":"ResolveDataAssetPath resolves a data-dir-relative asset path (e.g. `20240101120000-abc123/assets/foo.png`) to an absolute path while enforcing security checks. When the path points inside a notebook's own assets directory (not the global `assets/` folder), the notebook must be a normal (unencrypted) notebook; if `IsEncryptedBox(parts[0])` reports the notebook is encrypted, asset access is refused because encrypted notebook contents cannot be read as plaintext files. The notebook ID is interpolated into the message.","triggerScenarios":"Calling ResolveDataAssetPath (directly or via assetStat, deferredAssetPathFromFiles, PrepareAgentMessageImage, ResolveUnusedDataAssetPath) with a path of the form `<notebookID>/assets/<file>` where the notebook identified by parts[0] is an encrypted notebook (its .siyuan/conf.json marks it encrypted).","commonSituations":"Plugins, scripts, or AI/agent integrations referencing assets inside an encrypted notebook; tooling that enumerates notebook assets without filtering out encrypted boxes; moving an asset path between notebooks where the target is encrypted.","solutions":["Move the asset to a non-encrypted notebook or to the workspace-level `assets/` folder and reference it from there","Decrypt/open the notebook as a normal notebook if you legitimately need file-level asset access","Change the calling code to skip encrypted notebooks (check IsEncryptedBox before calling ResolveDataAssetPath)","If the notebook is not actually meant to be encrypted, inspect its .siyuan/conf.json — the box may have been misconfigured"],"exampleFix":"// before\nrel, abs, err := model.ResolveDataAssetPath(notebookID + \"/assets/pic.png\")\n// after\nif model.IsEncryptedBox(notebookID) {\n    return fmt.Errorf(\"skip encrypted notebook %s\", notebookID)\n}\nrel, abs, err := model.ResolveDataAssetPath(notebookID + \"/assets/pic.png\")","handlingStrategy":"try-catch","validationCode":"func canAccessAsset(boxID string) bool {\n    return !model.IsEncryptedBox(boxID)\n}\n// check before: strings.HasPrefix(assetPath, boxID+\"/assets/\") && !model.IsEncryptedBox(boxID)","typeGuard":"func isPlainNotebookAsset(assetPath, boxID string) bool {\n    return strings.HasPrefix(assetPath, boxID+\"/assets/\") && !model.IsEncryptedBox(boxID)\n}","tryCatchPattern":"rel, abs, err := model.ResolveDataAssetPath(assetPath)\nif err != nil {\n    if strings.Contains(err.Error(), \"accessing assets in encrypted notebook\") {\n        // fall back: copy asset to global assets/ dir first, or skip\n        return handleEncryptedBoxAsset(assetPath)\n    }\n    return err\n}","preventionTips":["Check model.IsEncryptedBox(notebookID) before touching any `<notebookID>/assets/...` path","Prefer the workspace-level `assets/` directory for files that must be file-accessed programmatically","Enumerate notebooks via conf and filter out encrypted boxes in any batch asset processing"],"tags":["go","filesystem","encryption","assets"],"backgroundTag":"unsupported-operation","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}