siyuan-note/siyuan · error
Obsidian Vault path is unsafe
Error message
Obsidian Vault path is unsafe
What it means
`errObsidianVaultUnsafePath` is returned when the Vault root is rejected as unsafe: it is a symbolic link/reparse point (`isObsidianResolvedLink`), or `util.IsSensitivePath(abs)` matches a protected location. It maps to i18n key 338 and is a deliberate security guard against importing from linked or sensitive filesystem locations.
Solutions
- Pass the real (physically resolved) Vault directory instead of a symlink — resolve the link and use its target
- Move the Vault out of any sensitive/protected location `util.IsSensitivePath` blocks
- On Windows, use the real folder rather than a junction/reparse point
Example fix
// before
validateObsidianVaultRoot("/home/user/vault-link") // symlink -> errObsidianVaultUnsafePath
// after
validateObsidianVaultRoot("/home/user/real/MyVault") // resolved physical directory Defensive patterns
Strategy: validation
Validate before calling
const real = await fs.realpath(vaultPath);
const stat = await fs.lstat(vaultPath);
if (stat.isSymbolicLink()) {
throw new Error("Use the resolved physical path, not a symlink: " + real);
} Type guard
function isRealDirectory(stat: { isDirectory(): boolean; isSymbolicLink(): boolean }): boolean {
return stat.isDirectory() && !stat.isSymbolicLink();
} Try / catch
try {
await api.importObsidianVault(vaultPath);
} catch (e) {
if (isVaultUnsafePath(e)) {
// resolve symlink to its target and/or move out of the sensitive path, then retry
}
} Prevention
- Resolve symlinks/junctions to their physical target before importing
- Keep vaults outside protected/sensitive locations and outside the SiYuan workspace
- Avoid junction/reparse points on Windows vault locations
When it happens
Trigger: `validateObsidianVaultRoot` sees `info.Mode()&os.ModeSymlink != 0` or a resolved link, or the absolute path hits `util.IsSensitivePath` (e.g. inside the workspace, system directories).
Common situations: Vault accessed through a symlink to sync storage (Dropbox/iCloud junction); on Windows a junction/reparse point; user points at a protected path like the SiYuan workspace itself.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- : Vault root is a symbolic link or reparse point (wrapped…
- Obsidian Vault is unreadable
- Obsidian Vault path is not a directory
- resource escapes the data directory
- symlink escapes workspace
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/ef306ec13c446c9b.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/import_obsidian.go:232
func (err *obsidianUserError) Error() string {
return err.Cause.Error()
}
func (err *obsidianUserError) Unwrap() error {
return err.Cause
}
func newObsidianUserError(detailLanguage int, relPath string, cause error) error {
return &obsidianUserError{DetailLanguage: detailLanguage, RelPath: relPath, Cause: cause}
}
var (
obsidianTasksMu sync.Mutex
obsidianTasks = map[string]*obsidianTask{}
obsidianActive string
errObsidianVaultUnreadable = errors.New("Obsidian Vault is unreadable")
errObsidianVaultNotDirectory = errors.New("Obsidian Vault path is not a directory")
errObsidianVaultUnsafePath = errors.New("Obsidian Vault path is unsafe")
errObsidianVaultConfigMissing = errors.New("Obsidian Vault config directory is missing")
errObsidianVaultMarkdownMissing = errors.New("Obsidian Vault has no readable Markdown")
errObsidianSourceChanged = errors.New("Obsidian source file changed")
obsidianBlockIDPattern = regexp.MustCompile(`(?m)(?:^|[ \t])\^([A-Za-z0-9-]+)[ \t]*$`)
obsidianQuotePattern = regexp.MustCompile(`^((?:[ \t]*>[ \t]?)+)(.*)$`)
obsidianListItemPattern = regexp.MustCompile(`^([ \t]*(?:[-+*]|\d+[.)])[ \t]+)(.*)$`)
obsidianFootnotePattern = regexp.MustCompile(`(?m)\[\^[^\]\r\n]+\]`)
)
func StartObsidianVaultAnalysis(localPath string) (*ObsidianVaultTask, error) {
var replacedTaskID string
obsidianTasksMu.Lock()
if obsidianActive != "" {
if active := obsidianTasks[obsidianActive]; active != nil && !isObsidianTerminalState(active.State) {
if !isObsidianPreImportState(active.State) {
obsidianTasksMu.Unlock()
return nil, errors.New(Conf.Language(329))View on GitHub (pinned to 9f775e8a12)