{"record":{"id":"858adc403abf0fae","repo":"siyuan-note/siyuan","slug":"w-vault-root-is-a-symbolic-link-or-reparse-point-wrapped","errorCode":null,"errorMessage":"%w: Vault root is a symbolic link or reparse point (wrapped: Obsidian Vault path is unsafe)","messagePattern":"%w: Vault root is a symbolic link or reparse point \\(wrapped: Obsidian Vault path is unsafe\\)","errorType":"exception","errorClass":null,"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/9f775e8a12daef8255556097396f9b2739078892/kernel/model/import_obsidian.go#L556-L592","documentation":"validateObsidianVaultRoot wraps errObsidianVaultUnsafePath with 'Vault root is a symbolic link or reparse point' when the vault root itself is a symlink (ModeSymlink) or resolves through a link/reparse point (isObsidianResolvedLink). SiYuan refuses such roots to prevent path-traversal and data-safety issues during import.","triggerScenarios":"Selecting a vault directory that is a symlink, a Windows junction, or a macOS Finder alias; vaults placed under symlinked folders like Dropbox/ iCloud aliases that report as links.","commonSituations":"Users keeping the real vault elsewhere and linking it into Documents; Windows library junctions; synced-folder indirection setups.","solutions":["Select the real (physical) vault directory directly instead of the symlink/junction","Remove the symlink and pass the resolved target path (e.g. from realpath / fs.realpathSync)","If a junction is required for organization, point the import at the junction target, not the link"],"exampleFix":"// before\nanalyzeVault({ localPath: '/Users/me/Documents/MyVault' }); // symlink\n// after\nconst real = fs.realpathSync('/Users/me/Documents/MyVault');\nanalyzeVault({ localPath: real });","handlingStrategy":"validation","validationCode":"const real = await fs.promises.realpath(vaultPath);\nconst st = await fs.promises.lstat(vaultPath);\nif (st.isSymbolicLink()) throw new Error('Select the real vault folder, not a symlink');\nawait analyzeVault({ localPath: real });","typeGuard":"async function isRealDir(p) { try { return (await fs.promises.lstat(p)).isDirectory() && (await fs.promises.realpath(p)) === p; } catch { return false; } }","tryCatchPattern":"try { await analyzeVault(opts); } catch (e) { if (isVaultUnsafe(e) && /symbolic link/.test(String(e))) { opts.localPath = await fs.promises.realpath(opts.localPath); return analyzeVault(opts); } throw e; }","preventionTips":["Select the physical vault directory, not junctions or aliases","Resolve with realpath before calling the API","Keep the vault outside symlink-heavy synced folders where possible"],"tags":["obsidian","security","symlink","path-validation"],"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"}