{"record":{"id":"e2f3158133d3c9ed","repo":"siyuan-note/siyuan","slug":"invalid-history-source-path","errorCode":null,"errorMessage":"invalid history source path","messagePattern":"invalid history source path","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/asset_relink_batch.go","lineNumber":461,"sourceCode":"\tif err != nil {\n\t\treturn err\n\t}\n\tp.result.HistoryPath = filepath.ToSlash(historyDir)\n\t// 所有候选源先备份；备份失败属于请求级错误，此时尚未修改任何引用。\n\tfor _, file := range files {\n\t\tif err = p.checkContext(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif file.before == nil {\n\t\t\tcontinue\n\t\t}\n\t\tcurrent, readErr := filelock.ReadFile(file.path)\n\t\tif readErr != nil || !bytes.Equal(current, file.before) {\n\t\t\treturn fmt.Errorf(\"source changed during scan: %s\", file.path)\n\t\t}\n\t\trel, relErr := filepath.Rel(util.DataDir, file.path)\n\t\tif relErr != nil || strings.HasPrefix(rel, \"..\") {\n\t\t\treturn errors.New(\"invalid history source path\")\n\t\t}\n\t\tdest := filepath.Join(historyDir, rel)\n\t\tif err = os.MkdirAll(filepath.Dir(dest), 0755); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err = gulu.File.WriteFileSafer(dest, file.before, 0644); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tindexHistoryDir(filepath.Base(historyDir), util.NewLute())\n\tchangedViews, reload := map[string]bool{}, map[string]bool{}\n\tdefer func() {\n\t\tfor _, tree := range p.trees {\n\t\t\tviewChanged := false\n\t\t\tast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {\n\t\t\t\tif entering && n.Type == ast.NodeAttributeView && changedViews[n.AttributeViewID] {\n\t\t\t\t\tviewChanged = true\n\t\t\t\t}","sourceCodeStart":443,"sourceCodeEnd":479,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/asset_relink_batch.go#L443-L479","documentation":"apply() snapshots a source asset into the history directory before relinking. After re-reading the file and confirming it is unchanged, it computes the file's path relative to the workspace data directory; if that relative path cannot be computed or escapes the data directory (a \"..\" prefix), it refuses to write history outside the workspace and throws this error.","triggerScenarios":"Calling run() (or the test TestAssetRelinkSourceChangedDuringScan) with an asset file whose path is not inside util.DataDir, e.g. a symlinked or absolute path that resolves outside the data directory, or when filepath.Rel fails due to mismatched path bases.","commonSituations":"Workspace moved or data directory relocated so cached file paths point outside it; symlinked assets pointing to external locations; running the relink batch with paths collected from a different workspace.","solutions":["Ensure all asset file paths passed into the relink batch are real paths under util.DataDir (use filepath.EvalSymlinks and relativize before queueing)","Verify the process is using the same DataDir as when the file list was built","Re-scan assets after moving or renaming the workspace so stale paths are refreshed"],"exampleFix":"// before\nrel, relErr := filepath.Rel(util.DataDir, file.path) // file.path points outside DataDir\n// after\nabs, _ := filepath.EvalSymlinks(file.path)\nrel, relErr := filepath.Rel(util.DataDir, abs)\nif relErr != nil || strings.HasPrefix(rel, \"..\") { skip the file }","handlingStrategy":"validation","validationCode":"rel, err := filepath.Rel(util.DataDir, path)\nif err != nil || strings.HasPrefix(rel, \"..\") { /* skip file, do not queue for history */ }","typeGuard":"func inDataDir(p string) bool { rel, err := filepath.Rel(util.DataDir, p); return err == nil && !strings.HasPrefix(rel, \"..\") }","tryCatchPattern":null,"preventionTips":["Always resolve symlinks with filepath.EvalSymlinks before queueing history files","Rebuild the file list from the current DataDir instead of caching absolute paths","Log skipped out-of-tree paths during scan for early detection"],"tags":["go","path-validation","history","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"}