{"record":{"id":"a6c6941f62d3e07f","repo":"siyuan-note/siyuan","slug":"w-vault-root-is-a-symbolic-link-or-reparse-point","errorCode":null,"errorMessage":"%w: Vault root is a symbolic link or reparse point","messagePattern":"%w: Vault root is a symbolic link or reparse point","errorType":"error_code","errorClass":"errObsidianVaultUnsafePath","httpStatus":null,"severity":"error","filePath":"kernel/model/import_obsidian.go","lineNumber":574,"sourceCode":"}\n\nfunc validateObsidianVaultRoot(localPath string) (string, error) {\n\tif strings.TrimSpace(localPath) == \"\" {\n\t\treturn \"\", fmt.Errorf(\"%w: path is empty\", errObsidianVaultUnreadable)\n\t}\n\tabs, err := filepath.Abs(filepath.Clean(localPath))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"%w: normalize Vault path: %v\", errObsidianVaultUnreadable, err)\n\t}\n\tinfo, err := os.Lstat(abs)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"%w: read Vault root: %v\", errObsidianVaultUnreadable, err)\n\t}\n\tif !info.IsDir() {\n\t\treturn \"\", errObsidianVaultNotDirectory\n\t}\n\tif info.Mode()&os.ModeSymlink != 0 || isObsidianResolvedLink(abs) {\n\t\treturn \"\", fmt.Errorf(\"%w: Vault root is a symbolic link or reparse point\", errObsidianVaultUnsafePath)\n\t}\n\tif util.IsSensitivePath(abs) {\n\t\treturn \"\", fmt.Errorf(\"%w: selected Vault path is sensitive\", errObsidianVaultUnsafePath)\n\t}\n\tworkspace, _ := filepath.Abs(filepath.Clean(util.WorkspaceDir))\n\tif sameObsidianPath(abs, workspace) || gulu.File.IsSubPath(workspace, abs) || gulu.File.IsSubPath(abs, workspace) {\n\t\treturn \"\", fmt.Errorf(\"%w: Vault root and SiYuan workspace contain each other\", errObsidianVaultUnsafePath)\n\t}\n\tconfigPath := filepath.Join(abs, \".obsidian\")\n\tconfigInfo, statErr := os.Lstat(configPath)\n\tif statErr != nil {\n\t\tif os.IsNotExist(statErr) {\n\t\t\treturn \"\", errObsidianVaultConfigMissing\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"%w: read Vault config directory: %v\", errObsidianVaultUnreadable, statErr)\n\t}\n\tif !configInfo.IsDir() || configInfo.Mode()&os.ModeSymlink != 0 || isObsidianResolvedLink(configPath) {\n\t\treturn \"\", errObsidianVaultConfigMissing","sourceCodeStart":556,"sourceCodeEnd":592,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/import_obsidian.go#L556-L592","documentation":"Security guard: the vault root itself is a symbolic link (or a Windows reparse point that resolves elsewhere). The kernel refuses to import through a symlinked root to prevent path escape or double-counting, wrapping errObsidianVaultUnsafePath with 'Vault root is a symbolic link or reparse point'.","triggerScenarios":"validateObsidianVaultRoot at kernel/model/import_obsidian.go:574 detects info.Mode()&os.ModeSymlink != 0 or isObsidianResolvedLink(abs) on the chosen vault root.","commonSituations":"User selects a shortcut/symlink alias to the vault instead of the real directory; Windows library/junction reparse points; macOS Finder aliases backed by symlinks; vault symlinked into a synced folder.","solutions":["Select the real vault directory (the one containing .obsidian) instead of the symlink/alias","Resolve the symlink manually and pass its target as localPath","On Windows, avoid junction/reparse-point folders and choose the physical path","If a symlink is genuinely needed, restructure so the vault is the physical directory"],"exampleFix":"// before\nanalyzeObsidianVault(\"/Users/me/vault-link\") // symlink\n// after\ntarget, _ := filepath.EvalSymlinks(\"/Users/me/vault-link\")\nanalyzeObsidianVault(target)","handlingStrategy":"validation","validationCode":"info, err := os.Lstat(vaultPath)\nif err == nil && info.Mode()&os.ModeSymlink != 0 {\n    vaultPath, _ = filepath.EvalSymlinks(vaultPath)\n}","typeGuard":"func isRealDir(p string) bool {\n    info, err := os.Lstat(p)\n    return err == nil && info.IsDir() && info.Mode()&os.ModeSymlink == 0\n}","tryCatchPattern":"_, err := AnalyzeObsidianVault(vaultPath)\nif err != nil && strings.Contains(err.Error(), \"symbolic link or reparse point\") {\n    target, _ := filepath.EvalSymlinks(vaultPath)\n    AnalyzeObsidianVault(target)\n}","preventionTips":["Pick the physical vault directory, not aliases/shortcuts","Resolve symlinks before submitting the path","Avoid junction points on Windows for vault storage"],"tags":["obsidian-import","symlink","path-safety","security"],"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"}