{"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":"warning","filePath":"kernel/model/assets.go","lineNumber":1103,"sourceCode":"\t}\n\tif assetDirIndex > 0 {\n\t\tnotebookRoot := filepath.Join(util.DataDir, parts[0])\n\t\tresolvedDataDir, dataEvalErr := ResolveRealPath(util.DataDir)\n\t\tresolvedNotebookRoot, notebookEvalErr := ResolveRealPath(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 := ResolveAssetPathWithMissingLeaf(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":1085,"sourceCodeEnd":1121,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/assets.go#L1085-L1121","documentation":"ResolveDataAssetPath resolves the asset path (following symlinks) and then verifies that the resolved real path still lives under the notebook/global assets root. This error is thrown when the final resolved location escapes the assets directory — the path only appeared to be inside assets/ before symlink resolution. It is a path-traversal containment check.","triggerScenarios":"Calling ResolveDataAssetPath with a path inside <box>/assets/ that is a symlink (or contains a symlinked parent directory) resolving to a location outside the resolved assets root, e.g. symlink to /etc/passwd or to another notebook's assets when the notebook root check passed but the asset root containment failed.","commonSituations":"Manually symlinking assets from outside the workspace into a notebook for convenience; restoring a workspace from a backup that captured symlinks; a malicious or corrupted document referencing a crafted asset path.","solutions":["Inspect the symlink chain: `readlink -f <workspace>/data/<path>` to see where it actually points","Move the real file into <workspace>/data/.../assets/ and replace the symlink with a regular file","If sharing assets across notebooks, copy the file rather than linking it, or reference the global data/assets/ location","Verify the link target is not on a bind-mount or network share whose path lies outside the workspace"],"exampleFix":"// before: symlink escapes assets\nln -s /var/data/img.png data/20250101120000-abc/assets/img.png\n// after: real file inside assets\ncp /var/data/img.png data/20250101120000-abc/assets/img.png","handlingStrategy":"validation","validationCode":"real, err := filepath.EvalSymlinks(absPath)\nif err == nil && !strings.HasPrefix(real, filepath.EvalSymlinks(assetRoot)) {\n\t// symlink escapes assets; do not call the resolver\n}","typeGuard":null,"tryCatchPattern":"rel, abs, err := model.ResolveDataAssetPath(p)\nif err != nil && strings.Contains(err.Error(), \"resolves outside assets directory\") {\n\t// copy the real file into assets/ and replace the symlink, then retry\n}","preventionTips":["Copy shared assets into the notebook instead of symlinking them from outside","Avoid symlinking directories above assets/ (e.g. linking the whole notebook dir)","Periodically audit assets dirs for symlinks: `find data -type l`"],"tags":["security","symlink","path-traversal","asset-resolution"],"backgroundTag":"path-traversal-blocked","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"}