{"record":{"id":"5bd6e6a88bc1e08e","repo":"siyuan-note/siyuan","slug":"symlink-s-resolves-outside-data-assets-s","errorCode":null,"errorMessage":"symlink [%s] resolves outside data/assets: [%s]","messagePattern":"symlink \\[(.+?)\\] resolves outside data/assets: \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":1303,"sourceCode":"}\n\nfunc getAssetAbsPath(relativePath string, includeEncrypted bool) (absPath string, err error) {\n\trelativePath = filepath.ToSlash(relativePath)\n\t// 在 data 文件夹下搜索，主要是 data/assets 文件夹\n\tp := filepath.Join(util.DataDir, relativePath)\n\tif gulu.File.IsExist(p) {\n\t\tif !gulu.File.IsSubPath(util.WorkspaceDir, p) {\n\t\t\treturn \"\", fmt.Errorf(\"[%s] is not sub path of workspace\", p)\n\t\t}\n\t\t// 解析符号链接，验证真实路径仍在 data/assets/ 下\n\t\tif realP, evalErr := filepath.EvalSymlinks(p); evalErr == nil && realP != p {\n\t\t\tassetsRoot := util.GetDataAssetsAbsPath()\n\t\t\trealAssetsRoot, rootEvalErr := filepath.EvalSymlinks(assetsRoot)\n\t\t\tif rootEvalErr != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"resolve assets root [%s] failed: %w\", assetsRoot, rootEvalErr)\n\t\t\t}\n\t\t\tif !gulu.File.IsSubPath(realAssetsRoot, realP) {\n\t\t\t\treturn \"\", fmt.Errorf(\"symlink [%s] resolves outside data/assets: [%s]\", p, realP)\n\t\t\t}\n\t\t\t// 安全校验使用解析后的路径，返回原路径以便下游与 DataDir 保持同一路径形式\n\t\t\treturn p, nil\n\t\t}\n\t\treturn p, nil\n\t}\n\n\t// 在文档同级 assets 文件夹下搜索\n\tif !strings.HasPrefix(relativePath, \"assets/\") {\n\t\treturn \"\", nil\n\t}\n\tnotebooks, err := ListNotebooks()\n\tif err != nil {\n\t\treturn \"\", errors.New(Conf.Language(0))\n\t}\n\tfor _, notebook := range notebooks {\n\t\tif !includeEncrypted && IsEncryptedBox(notebook.ID) {\n\t\t\tcontinue // 加密笔记本的资源不参与全局路径解析（孤岛，资源不跨边界）","sourceCodeStart":1285,"sourceCodeEnd":1321,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/assets.go#L1285-L1321","documentation":"After resolving the asset symlink, getAssetAbsPath verifies the real target path is still a sub-path of the resolved data/assets root. The error \"symlink [%s] resolves outside data/assets: [%s]\" is returned when a symlinked asset points outside data/assets — a defense against symlink-based escapes from the assets sandbox.","triggerScenarios":"getAssetAbsPath finds a file p that is a symlink whose EvalSymlinks target realP is not under the real data/assets directory — e.g. data/assets/foo.png -> /home/user/secret.png or -> a path in another notebook outside assets.","commonSituations":"Users manually symlinking shared assets from outside the workspace into data/assets; docker/container setups where assets are mounted elsewhere and linked; backup/restore tools recreating symlinks with wrong targets.","solutions":["Replace the symlink with a real copy of the target file inside data/assets","If sharing assets across notebooks, move them into data/assets and reference via the standard assets/ path","Recreate the symlink so its target lives inside data/assets (e.g. data/assets/shared/foo.png -> data/assets/foo.png)","On containerized setups, mount the shared assets directory at data/assets instead of symlinking to it"],"exampleFix":"// before (filesystem)\n// ln -s /home/user/photos/cat.png <workspace>/data/assets/cat.png\n// after (filesystem)\n// cp /home/user/photos/cat.png <workspace>/data/assets/cat.png","handlingStrategy":"validation","validationCode":"real, _ := filepath.EvalSymlinks(candidate)\nif !strings.HasPrefix(real, realAssetsRoot) {\n    return errors.New(\"symlink target must stay inside data/assets\")\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"resolves outside data/assets\") {\n    return fmt.Errorf(\"replace symlink %s with a real copy inside data/assets\", relPath)\n}","preventionTips":["Copy shared files into data/assets instead of symlinking from outside","In containers, mount shared assets at data/assets rather than linking","Audit data/assets for symlinks after restores or migrations"],"tags":["go","security","symlink","path-traversal"],"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"}