{"record":{"id":"6bbea653715b8650","repo":"siyuan-note/siyuan","slug":"symlink-s-resolves-outside-workspace-s","errorCode":null,"errorMessage":"symlink [%s] resolves outside workspace: [%s]","messagePattern":"symlink \\[(.+?)\\] resolves outside workspace: \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/assets.go","lineNumber":1245,"sourceCode":"\t\treturn \"\", fmt.Errorf(\"[%s] is not an asset path (must start with assets/)\", relativePath)\n\t}\n\tif boxID != \"\" && !ast.IsNodeIDPattern(boxID) {\n\t\treturn \"\", fmt.Errorf(\"[%s] is not a box id\", boxID)\n\t}\n\n\tif boxID == \"\" {\n\t\treturn GetAssetAbsPathWithOpt(relativePath, false)\n\t}\n\n\tp := filepath.Join(util.DataDir, boxID, 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// 解析符号链接/目录联接，防止软链接跳出资产根目录\n\t\tif realP, evalErr := filepath.EvalSymlinks(p); evalErr == nil && realP != p {\n\t\t\tif !gulu.File.IsSubPath(util.WorkspaceDir, realP) {\n\t\t\t\treturn \"\", fmt.Errorf(\"symlink [%s] resolves outside workspace: [%s]\", p, realP)\n\t\t\t}\n\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)","sourceCodeStart":1227,"sourceCodeEnd":1263,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/assets.go#L1227-L1263","documentation":"When the constructed asset path exists, GetAssetAbsPathInBox evaluates symlinks/junctions with filepath.EvalSymlinks and requires the real path to remain inside the workspace. This error is returned when a symlink inside <boxID>/assets/ resolves to a location outside the workspace — a containment check that defeats links escaping the data tree.","triggerScenarios":"An asset in data/<boxID>/assets/ is a symlink (or sits under a symlinked directory) pointing outside the workspace, e.g. to the user's home directory, an external drive, or another application's data; calling GetAssetAbsPathInBox with a boxID resolves that file and hits the check.","commonSituations":"Convenience symlinks to assets stored outside SiYuan; restoring a workspace archive that recorded absolute symlink targets from another machine; cloud-sync clients materializing links whose targets were never synced.","solutions":["Copy the target file into data/<boxID>/assets/ and replace the symlink with a real file","If sharing content, move the referenced files under the workspace and update documents' links","Remove the dangling or external symlink; resync the notebook so real files are present"],"exampleFix":"// before: link out of workspace\nln -s ~/Pictures/img.png data/box/assets/img.png\n// after: file inside workspace\ncp ~/Pictures/img.png data/box/assets/img.png && rm data/box/assets/img.png.old","handlingStrategy":"validation","validationCode":"if real, err := filepath.EvalSymlinks(p); err == nil && !gulu.File.IsSubPath(util.WorkspaceDir, real) {\n\treturn fmt.Errorf(\"symlink target outside workspace\")\n}","typeGuard":null,"tryCatchPattern":"abs, err := model.GetAssetAbsPathInBox(ref, boxID)\nif err != nil && strings.Contains(err.Error(), \"resolves outside workspace\") {\n\t// copy the target into data/<boxID>/assets/ and replace the symlink, then retry\n}","preventionTips":["Store assets as regular files inside the workspace; avoid out-of-workspace symlinks","After syncing/restoring workspaces, scan for symlinks with external targets","Document to users that externally linked assets must be copied into assets/"],"tags":["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"}