{"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":"validation","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/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/import_obsidian.go#L214-L250","documentation":"A sentinel base error for any Vault path deemed unsafe for import. Like the unreadable sentinel, it is wrapped with a descriptive suffix (symbolic link/reparse point, sensitive path, or workspace containment) via fmt.Errorf %w. The obsidianVaultErrorLanguage helper maps it to i18n key 338. It is the parent for errors 777, 778, and 779.","triggerScenarios":"Calling startObsidianVaultAnalysis with a Vault root that is a symlink or Windows reparse point (line 573), a path flagged by util.IsSensitivePath (line 576), or a path that contains or is contained within the SiYuan workspace directory (line 580).","commonSituations":"Selecting a symlinked folder as the Vault; pointing at a system-sensitive path (e.g. /etc, a Windows system directory) that IsSensitivePath blocks; selecting the SiYuan workspace data directory itself or a parent/child of it as the Vault, which would cause recursive or destructive reads.","solutions":["Use the real physical directory of the Vault, not a symlink or junction — resolve the link to its target first.","Avoid selecting the SiYuan workspace folder or any folder that overlaps it; the Vault must be outside the workspace tree.","Move or copy the Vault to a neutral directory if it currently sits inside or contains the workspace.","Check the wrapped suffix to identify which safety rule fired (symlink, sensitive, or containment)."],"exampleFix":"// before: symlinked vault\nlocalPath := \"/home/user/vault-link\" // -> symlink, errObsidianVaultUnsafePath\n\n// after: resolve to real target\nreal, err := filepath.EvalSymlinks(localPath)\nif err != nil { return err }\n// pass real to startObsidianVaultAnalysis","handlingStrategy":"validation","validationCode":"// Resolve symlinks and check sensitivity/containment before calling the API\nfunc ensureSafeVaultPath(localPath string) error {\n    abs, err := filepath.Abs(filepath.Clean(localPath))\n    if err != nil { return err }\n    info, err := os.Lstat(abs)\n    if err != nil { return err }\n    if info.Mode()&os.ModeSymlink != 0 { return errors.New(\"Vault root is a symlink; resolve it first\") }\n    if util.IsSensitivePath(abs) { return errors.New(\"Vault path is sensitive\") }\n    ws, _ := filepath.Abs(filepath.Clean(util.WorkspaceDir))\n    if gulu.File.IsSubPath(ws, abs) || gulu.File.IsSubPath(abs, ws) || sameObsidianPath(abs, ws) {\n        return errors.New(\"Vault overlaps the SiYuan workspace\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if _, err := model.StartObsidianVaultAnalysis(localPath); err != nil {\n    if errors.Is(err, errObsidianVaultUnsafePath) {\n        // inspect the suffix: symlink, sensitive, or containment\n        return fmt.Errorf(\"Vault path is unsafe (%s); use a real directory outside the workspace\", err)\n    }\n}","preventionTips":["Resolve all symlinks in the path before importing.","Keep the Vault outside the SiYuan workspace directory tree.","Avoid system-sensitive paths; place Vaults under user home."],"tags":["obsidian","import","security","validation","sentinel-error","symlink"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}