{"record":{"id":"ef306ec13c446c9b","repo":"siyuan-note/siyuan","slug":"obsidian-vault-path-is-unsafe","errorCode":null,"errorMessage":"Obsidian Vault path is unsafe","messagePattern":"Obsidian Vault path is unsafe","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/import_obsidian.go","lineNumber":232,"sourceCode":"func (err *obsidianUserError) Error() string {\n\treturn err.Cause.Error()\n}\n\nfunc (err *obsidianUserError) Unwrap() error {\n\treturn err.Cause\n}\n\nfunc newObsidianUserError(detailLanguage int, relPath string, cause error) error {\n\treturn &obsidianUserError{DetailLanguage: detailLanguage, RelPath: relPath, Cause: cause}\n}\n\nvar (\n\tobsidianTasksMu                 sync.Mutex\n\tobsidianTasks                   = map[string]*obsidianTask{}\n\tobsidianActive                  string\n\terrObsidianVaultUnreadable      = errors.New(\"Obsidian Vault is unreadable\")\n\terrObsidianVaultNotDirectory    = errors.New(\"Obsidian Vault path is not a directory\")\n\terrObsidianVaultUnsafePath      = errors.New(\"Obsidian Vault path is unsafe\")\n\terrObsidianVaultConfigMissing   = errors.New(\"Obsidian Vault config directory is missing\")\n\terrObsidianVaultMarkdownMissing = errors.New(\"Obsidian Vault has no readable Markdown\")\n\terrObsidianSourceChanged        = errors.New(\"Obsidian source file changed\")\n\n\tobsidianBlockIDPattern  = regexp.MustCompile(`(?m)(?:^|[ \\t])\\^([A-Za-z0-9-]+)[ \\t]*$`)\n\tobsidianQuotePattern    = regexp.MustCompile(`^((?:[ \\t]*>[ \\t]?)+)(.*)$`)\n\tobsidianListItemPattern = regexp.MustCompile(`^([ \\t]*(?:[-+*]|\\d+[.)])[ \\t]+)(.*)$`)\n\tobsidianFootnotePattern = regexp.MustCompile(`(?m)\\[\\^[^\\]\\r\\n]+\\]`)\n)\n\nfunc StartObsidianVaultAnalysis(localPath string) (*ObsidianVaultTask, error) {\n\tvar replacedTaskID string\n\tobsidianTasksMu.Lock()\n\tif obsidianActive != \"\" {\n\t\tif active := obsidianTasks[obsidianActive]; active != nil && !isObsidianTerminalState(active.State) {\n\t\t\tif !isObsidianPreImportState(active.State) {\n\t\t\t\tobsidianTasksMu.Unlock()\n\t\t\t\treturn nil, errors.New(Conf.Language(329))","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/import_obsidian.go#L214-L250","documentation":"`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.","triggerScenarios":"`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).","commonSituations":"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.","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"],"exampleFix":"// before\nvalidateObsidianVaultRoot(\"/home/user/vault-link\") // symlink -> errObsidianVaultUnsafePath\n// after\nvalidateObsidianVaultRoot(\"/home/user/real/MyVault\") // resolved physical directory","handlingStrategy":"validation","validationCode":"const real = await fs.realpath(vaultPath);\nconst stat = await fs.lstat(vaultPath);\nif (stat.isSymbolicLink()) {\n  throw new Error(\"Use the resolved physical path, not a symlink: \" + real);\n}","typeGuard":"function isRealDirectory(stat: { isDirectory(): boolean; isSymbolicLink(): boolean }): boolean {\n  return stat.isDirectory() && !stat.isSymbolicLink();\n}","tryCatchPattern":"try {\n  await api.importObsidianVault(vaultPath);\n} catch (e) {\n  if (isVaultUnsafePath(e)) {\n    // resolve symlink to its target and/or move out of the sensitive path, then retry\n  }\n}","preventionTips":["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"],"tags":["obsidian","import","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"}