{"record":{"id":"3cf6837602f87bc2","repo":"siyuan-note/siyuan","slug":"asset-path-s-is-outside-assets-directory-s","errorCode":null,"errorMessage":"asset path [%s] is outside assets directory [%s]","messagePattern":"asset path \\[(.+?)\\] is outside assets directory \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":2827,"sourceCode":"\t\treturn nil\n\t})\n\tif walkErr != nil {\n\t\tif os.IsNotExist(walkErr) {\n\t\t\treturn\n\t\t}\n\t\treturn nil, fmt.Errorf(\"walk global assets [%s] failed: %w\", dataAssetsAbsPath, walkErr)\n\t}\n\treturn\n}\n\nfunc assetPathMapKey(assetsDirPath, assetPath string, isDir bool) (ret string, err error) {\n\trelPath, err := filepath.Rel(assetsDirPath, assetPath)\n\tif err != nil {\n\t\treturn\n\t}\n\trelPath = filepath.ToSlash(relPath)\n\tif relPath == \".\" || relPath == \"..\" || strings.HasPrefix(relPath, \"../\") || path.IsAbs(relPath) {\n\t\terr = fmt.Errorf(\"asset path [%s] is outside assets directory [%s]\", assetPath, assetsDirPath)\n\t\treturn\n\t}\n\n\tret = path.Join(\"assets\", relPath)\n\tif isDir {\n\t\tret += \"/\"\n\t}\n\treturn\n}\n\n// copyBoxAssetsToDataAssets 将笔记本路径下所有（包括子文档）的 assets 复制一份到 data/assets 中。\nfunc copyBoxAssetsToDataAssets(boxID string) error {\n\tboxLocalPath := filepath.Join(util.DataDir, boxID)\n\treturn copyAssetsToDataAssets(boxLocalPath)\n}\n\n// copyDocAssetsToDataAssets 将文档路径下所有（包括子文档）的 assets 复制一份到 data/assets 中。\nfunc copyDocAssetsToDataAssets(boxID, parentDocPath string) error {","sourceCodeStart":2809,"sourceCodeEnd":2845,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/assets.go#L2809-L2845","documentation":"assetPathMapKey computes an asset's relative path inside an assets directory and rejects any path that is not strictly contained in it: equal to '.', '..', starting with '../', or absolute. This is a path-traversal guard ensuring every asset key maps under 'assets/'. filepath.Rel produced a relative path that escapes the assets directory, so the entry is refused instead of generating a key outside the assets tree.","triggerScenarios":"An asset path passed to assetPathMapKey (during asset map building for cleanup/refresh) resolves to a location outside the assets dir, e.g. a symlink under data/assets pointing elsewhere, or an absolute path supplied that is not a descendant of assetsDirPath.","commonSituations":"A user placed a symlink inside data/assets pointing to files elsewhere on disk; storage layouts where assets dirs are linked; custom tooling that feeds absolute asset paths into the asset-map pipeline.","solutions":["Locate the offending path reported in the message and check whether it is a symlink escaping the assets directory; remove or relocate the symlink.","Ensure only files physically under the assets directory are referenced; copy external files into data/assets instead of linking them.","If you call this internal pipeline yourself, pass paths that are descendants of assetsDirPath (verify with filepath.Rel == no '../' prefix).","Report it as an internal invariant violation if the path came from the kernel's own walk, since walked entries should always be inside the dir."],"exampleFix":"// before: symlink inside assets escaping the directory\nln -s /home/user/other-files data/assets/linked\n// after: copy the content instead of linking\ncp -r /home/user/other-files data/assets/other-files","handlingStrategy":"validation","validationCode":"function isInsideAssets(assetPath, assetsDir) {\n  const rel = path.relative(assetsDir, assetPath);\n  return rel !== \"\" && rel !== \"..\" && !rel.startsWith(\"..\" + path.sep) && !path.isAbsolute(rel);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never symlink external files into data/assets; copy them instead.","Validate any externally supplied asset path is a descendant of the assets directory before use.","Keep workspace assets on one filesystem to avoid odd relative-path resolutions.","Audit data/assets for symlinks when importing notebooks from other machines."],"tags":["assets","path-traversal","filesystem"],"backgroundTag":"path-traversal-blocked","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}