{"record":{"id":"58ebd3b080652937","repo":"siyuan-note/siyuan","slug":"asset-path-resolves-outside-assets-directory-s","errorCode":null,"errorMessage":"asset path resolves outside assets directory: %s","messagePattern":"asset path resolves outside assets directory: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":971,"sourceCode":"\t}\n\tif assetDirIndex > 0 {\n\t\tnotebookRoot := filepath.Join(util.DataDir, parts[0])\n\t\tresolvedDataDir, dataEvalErr := filepath.EvalSymlinks(util.DataDir)\n\t\tresolvedNotebookRoot, notebookEvalErr := filepath.EvalSymlinks(notebookRoot)\n\t\tif dataEvalErr != nil || notebookEvalErr != nil ||\n\t\t\t!gulu.File.IsSubPath(resolvedDataDir, resolvedNotebookRoot) ||\n\t\t\t!gulu.File.IsSubPath(resolvedNotebookRoot, resolvedRoot) {\n\t\t\terr = fmt.Errorf(\"notebook asset path resolves outside notebook directory: %s\", assetPath)\n\t\t\treturn\n\t\t}\n\t}\n\tresolvedPath, evalErr := filepath.EvalSymlinks(absPath)\n\tif evalErr != nil {\n\t\terr = fmt.Errorf(\"resolve asset [%s] failed: %w\", absPath, evalErr)\n\t\treturn\n\t}\n\tif !gulu.File.IsSubPath(resolvedRoot, resolvedPath) {\n\t\terr = fmt.Errorf(\"asset path resolves outside assets directory: %s\", assetPath)\n\t\treturn\n\t}\n\n\trelativePath = filepath.ToSlash(dataRelativePath)\n\treturn\n}\n\n// ResolveUnusedDataAssetPath 解析 data 相对资源路径，并确认目标当前未被引用。\nfunc ResolveUnusedDataAssetPath(assetPath string) (relativePath, absPath string, err error) {\n\trelativePath, absPath, err = ResolveDataAssetPath(assetPath)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif unusedAssetsContainPath(relativePath, absPath, UnusedAssets(false)) {\n\t\treturn\n\t}\n\terr = fmt.Errorf(\"asset is not unused: %s\", relativePath)","sourceCodeStart":953,"sourceCodeEnd":989,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/assets.go#L953-L989","documentation":"Thrown by ResolveDataAssetPath (kernel/model/assets.go:971) after successfully resolving symlinks on both the assets root and the asset file: if the resolved file path no longer lives under the resolved assets root, the request is rejected. This catches symlinks inside the assets directory that point outside it, and is the core anti-traversal guard for global assets, explicitly tested by TestResolveDataAssetPath (`assets/linked/outside.png`).","triggerScenarios":"Calling ResolveDataAssetPath with a path like `assets/linked/outside.png` where `data/assets/linked` is a symlink to a directory outside `data/assets/`. The lexical path passes earlier checks, but after EvalSymlinks the real target is outside assets, so this fires.","commonSituations":"A user symlinked `data/assets/old` to an external folder to save space or import old data; a malicious document references a crafted asset path; a backup tool created junction points/symlinks that leave the assets tree.","solutions":["Inspect symlinks under the assets root: `find <DataDir>/assets -type l -ls`.","Replace any escaping symlink with a copy of the file inside `data/assets/`, or remove the symlink.","If importing external assets, copy them into `data/assets/` rather than symlinking across directory boundaries.","Treat unexpected triggers as a security event and audit the originating document's asset references."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Ensure a global asset symlink target stays inside the assets root before resolving.\nfull := filepath.Join(util.DataDir, filepath.FromSlash(p))\nreal, err1 := filepath.EvalSymlinks(full)\nroot, err2 := filepath.EvalSymlinks(filepath.Join(util.DataDir, \"assets\"))\nif err1 == nil && err2 == nil && !gulu.File.IsSubPath(root, real) {\n    return errors.New(\"asset symlink escapes assets directory\")\n}","typeGuard":null,"tryCatchPattern":"if _, _, err := model.ResolveDataAssetPath(p); err != nil && strings.Contains(err.Error(), \"resolves outside assets directory\") {\n    // a symlink under data/assets points outside; audit and remove the link, do not bypass\n}","preventionTips":["Do not place symlinks inside data/assets that point outside it.","Copy external media into data/assets rather than linking across boundaries.","Audit `find <DataDir>/assets -type l` periodically on shared/multi-user workspaces."],"tags":["assets","symlinks","path-traversal","security","filesystem"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}