{"record":{"id":"3037bb5cc2857080","repo":"siyuan-note/siyuan","slug":"conf-language-12","errorCode":null,"errorMessage":"Conf.Language(12)","messagePattern":"Conf\\.Language\\(12\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":1265,"sourceCode":"\t\t\t// 验证解析后的路径仍在 <boxID>/assets/ 或全局 data/assets/ 下\n\t\t\texpectedPrefix := filepath.Join(util.DataDir, \"assets\")\n\t\t\tif boxID != \"\" {\n\t\t\t\texpectedPrefix = filepath.Join(util.DataDir, boxID, \"assets\")\n\t\t\t}\n\t\t\tif !gulu.File.IsSubPath(expectedPrefix, realP) {\n\t\t\t\treturn \"\", fmt.Errorf(\"symlink [%s] resolves outside assets directory: [%s]\", p, realP)\n\t\t\t}\n\t\t}\n\t\treturn p, nil\n\t}\n\t// 非加密 box 的资源可能回退到全局 data/assets（兼容旧笔记本结构）\n\tif deferredPath, deferredErr := deferredAssetPath(relativePath, boxID, true); deferredErr != nil || deferredPath != \"\" {\n\t\treturn deferredPath, deferredErr\n\t}\n\tif !IsEncryptedBox(boxID) {\n\t\treturn GetAssetAbsPathWithOpt(relativePath, false)\n\t}\n\treturn \"\", fmt.Errorf(Conf.Language(12), relativePath)\n}\n\n// GetAssetAbsPathWithOpt 与 GetAssetAbsPath 一致，但可通过 includeEncrypted 控制是否遍历加密 box。\n// serveAssets 传 true（下游 serveEncryptedAsset 会按锁定状态 fail-closed），其他调用方传 false（安全跳过）。\nfunc GetAssetAbsPathWithOpt(relativePath string, includeEncrypted bool) (string, error) {\n\trelativePath = strings.TrimSpace(relativePath)\n\tif idx := strings.Index(relativePath, \"?\"); idx >= 0 {\n\t\trelativePath = relativePath[:idx]\n\t}\n\n\tabsPath, err := getAssetAbsPath(relativePath, includeEncrypted)\n\tif err == nil && absPath != \"\" {\n\t\treturn absPath, nil\n\t}\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}","sourceCodeStart":1247,"sourceCodeEnd":1283,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/assets.go#L1247-L1283","documentation":"GetAssetAbsPathInBox fails to resolve an asset file path inside a specific notebook (box). The localized message Conf.Language(12) formats as \"Query asset failed [%s]\" with the relative asset path. It is thrown when deferred resolution failed and the box is encrypted, so direct filesystem lookup is intentionally skipped because encrypted-notebook assets are isolated and must not leak outside the encryption boundary.","triggerScenarios":"Calling GetAssetAbsPathInBox(relativePath, boxID) where deferredAssetPath returns nothing (or errors that are treated as not-found) and IsEncryptedBox(boxID) is true, so the function returns fmt.Errorf(Conf.Language(12), relativePath) instead of searching the encrypted box's files directly.","commonSituations":"Resolving an image or file annotation path for a doc in an encrypted notebook while the asset record still points at the encrypted box; exporters (prepareExportAssets) or size probes (GetAssetImgSizeInBox) hitting encrypted-box assets whose on-disk names differ from the logical asset path.","solutions":["Check IsEncryptedBox(boxID) before calling and route through the encrypted-asset read path (e.g. deferredAssetPath / serveEncryptedAsset) instead of direct path resolution","Verify the asset actually exists in the encrypted box's storage; if it was moved or renamed, update the doc's asset links","If the box was encrypted after the asset was added, re-index the notebook so the deferred asset mapping is up to date","Handle the error as 'asset unavailable in encrypted box' and surface a user-facing message rather than retrying the same lookup"],"exampleFix":"// before\npath, err := model.GetAssetAbsPathInBox(relPath, boxID)\nif err != nil { log.Fatal(err) }\n// after\nif model.IsEncryptedBox(boxID) {\n    path, err = model.DeferredAssetPath(relPath, boxID) // encrypted-aware resolution\n} else {\n    path, err = model.GetAssetAbsPath(relPath)\n}\nif err != nil { return fmt.Errorf(\"asset %s unavailable: %w\", relPath, err) }","handlingStrategy":"validation","validationCode":"if model.IsEncryptedBox(boxID) {\n    // use encrypted-aware resolution instead of GetAssetAbsPathInBox\n}","typeGuard":null,"tryCatchPattern":"path, err := model.GetAssetAbsPathInBox(rel, boxID)\nif err != nil {\n    return fmt.Errorf(\"asset %s unavailable in box %s: %w\", rel, boxID, err)\n}","preventionTips":["Check IsEncryptedBox before direct asset path resolution","Use deferredAssetPath/encrypted-aware APIs for encrypted notebooks","Keep encrypted notebook indexes up to date after edits"],"tags":["go","assets","encrypted-notebook","path-resolution"],"backgroundTag":"resource-not-found","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"}