{"record":{"id":"56763395daadc206","repo":"siyuan-note/siyuan","slug":"resource-escapes-the-data-directory-s","errorCode":null,"errorMessage":"resource escapes the data directory: %s","messagePattern":"resource escapes the data directory: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/asset_relink.go","lineNumber":489,"sourceCode":"\tfor offset := 0; offset < 1000; offset++ {\n\t\tdir := filepath.Join(util.HistoryDir, time.Now().Add(time.Duration(offset)*time.Second).Format(\"2006-01-02-150405\")+\"-\"+HistoryOpReplace)\n\t\tif err := os.Mkdir(dir, 0755); err == nil {\n\t\t\treturn dir, nil\n\t\t} else if !os.IsExist(err) {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\treturn \"\", errors.New(\"cannot allocate a replacement history directory\")\n}\n\nfunc validateRelinkStoragePath(abs string) error {\n\treal, err := filepath.EvalSymlinks(abs)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdataRoot, err := filepath.EvalSymlinks(util.DataDir)\n\tif err != nil || !gulu.File.IsSubPath(dataRoot, real) {\n\t\treturn fmt.Errorf(\"resource escapes the data directory: %s\", abs)\n\t}\n\trel, err := filepath.Rel(dataRoot, real)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfirst, _, _ := strings.Cut(filepath.ToSlash(rel), \"/\")\n\tif ast.IsNodeIDPattern(first) && IsEncryptedBox(first) {\n\t\treturn errors.New(\"encrypted resources are not supported\")\n\t}\n\treturn nil\n}\n","sourceCodeStart":471,"sourceCodeEnd":501,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/asset_relink.go#L471-L501","documentation":"The asset relink scan verifies that every resolved asset path (after symlink evaluation) still lives inside the workspace data directory. If the resolved real path is not a subpath of DataDir, or symlink evaluation fails, the resource is rejected so relinking never touches or rewrites references to files outside the workspace. This is a safety gate protecting the workspace boundary.","triggerScenarios":"Calling RelinkAssetWithContext/RelinkAssets or the scan functions (via resolveRelinkAsset, scanViews, scanAnnotation) with an oldPath whose absolute location is a symlink pointing outside data/, an escape like '../../outside.png', or a path whose EvalSymlinks fails.","commonSituations":"Assets symlinked from a shared folder outside the workspace; manually moved or hand-crafted .sy documents referencing paths with parent-directory escapes; cloud-sync setups where assets are junctions/symlinks to another volume; note-taking content pasted with absolute file paths later interpreted as assets.","solutions":["Move or copy the asset into the workspace data directory (e.g. data/assets/) so its real path is a subpath of DataDir","Remove symlinks pointing outside the workspace and replace them with real files inside data/","Fix the reference in the document so it uses a valid workspace-relative asset path","Check DataDir resolution (e.g. wrong workspace opened) so legitimate assets are not falsely seen as escaping"],"exampleFix":"// before\nrelinkOld := \"/mnt/shared/photos/logo.png\" // outside data dir\n// after\noldAbs := filepath.Join(util.DataDir, \"assets\", \"logo.png\")\nerr := filelock.Copy(\"/mnt/shared/photos/logo.png\", oldAbs) // bring asset inside data/\nrelinkOld := \"assets/logo.png\"","handlingStrategy":"validation","validationCode":"func isInsideDataDir(p string) bool {\n\tabs, err := filepath.Abs(p)\n\tif err != nil { return false }\n\treal, err := filepath.EvalSymlinks(abs)\n\tif err != nil { return false }\n\troot, err := filepath.EvalSymlinks(util.DataDir)\n\treturn err == nil && gulu.File.IsSubPath(root, real)\n}","typeGuard":"if !isInsideDataDir(oldPath) { return errors.New(\"path must resolve inside the workspace data directory\") }","tryCatchPattern":null,"preventionTips":["Keep assets as real files inside data/assets, avoid symlinks out of the workspace","Use workspace-relative asset paths in documents","Check EvalSymlinks resolution when using linked folders or cloud sync"],"tags":["go","path-validation","symlink","security"],"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"}