BACK TO SEARCH
es617/obsidian-sync-mcpcritical

MCP server for Obsidian — access your vault from any AI agent, even when your machine is off. Powered by Self-hosted LiveSync.

This MCP server provides AI agents with read/write access to an Obsidian vault, either locally via filesystem or remotely via CouchDB (powered by Self...

purpose: This MCP server provides AI agents with read/writethreat: network exposed
TypeScript · 21 · Jun 7, 2026 · Jun 8, 2026 · GITHUB ↗
RISK SCORE
0/ 100 risk
high findings+50
medium findings+75
capped at100
VULNERABILITY ANALYSIS · 7 findings in 7 blocks2 HIGH · 5 MEDIUM
HIGH1 finding
src/vault-local.ts:112
112async listNotesWithMtime(folder?: string): Promise<NoteListing[]> {
113    if (folder && !folder.endsWith("/") && !folder.endsWith("\\")) folder += "/";
114    const searchDir = folder ? await this.safePath(folder) : this.root;
115    const entries: string[] = [];
116    try {
117        for await (const entry of glob("**/*.md", { cwd: searchDir })) {
118            const full = folder ? `${folder}${entry}` : entry;
119            if (full.startsWith(".obsidian/") || full.includes("/.obsidian/")) continue;
120            entries.push(full);
121        }
122    } catch {
123        return [];
124    }
125    const results = await Promise.all(
126        entries.map(async (p) => {
127            try {
128                const s = await stat(resolve(this.root, p));
129                return { path: p, mtime: s.mtimeMs };
130            } catch {
131                return { path: p, mtime: 0 };
132            }
133        }),
134    );
135    return results.sort((a, b) => a.path.localeCompare(b.path));
136}
src/main.ts:268src/tools.ts:102

// Exploitable via list_notes tool with a crafted folder parameter. Requires network exposure or compromised LLM.

EXPLAINThe listNotesWithMtime method uses safePath to resolve the folder parameter, but then uses glob with cwd set to the resolved searchDir. However, the returned paths are constructed by concatenating the original folder string (which may contain '../') with the glob result. This allows an attacker to list files outside the vault root by providing a folder like '../' or '.../'. The safePath check on the folder parameter only ensures the resolved directory is within the vault, but the subsequent path construction uses the unsanitized folder string, enabling traversal in the returned paths. Additionally, the stat call uses resolve(this.root, p) which could be exploited if p contains traversal sequences.
IMPACTAn attacker could list markdown files outside the intended vault directory, potentially reading sensitive files if combined with other tools.
FIXUse the resolved full path (searchDir) for glob and construct paths relative to the vault root. Avoid concatenating the original folder string with glob results. Instead, compute relative paths from the resolved directory.
HIGH1 finding
src/vault-local.ts:126
126const results = await Promise.all(
127    entries.map(async (p) => {
128        try {
129            const s = await stat(resolve(this.root, p));
130            return { path: p, mtime: s.mtimeMs };
131        } catch {
132            return { path: p, mtime: 0 };
133        }
134    }),
135);
src/main.ts:268src/tools.ts:102

// Exploitable via list_notes tool with a crafted folder parameter. Requires network exposure or compromised LLM.

EXPLAINAfter glob returns entries, the code resolves paths using resolve(this.root, p). If the folder parameter contained traversal sequences, the glob entries may include paths that escape the vault root. The stat call then accesses files outside the intended directory.
IMPACTAn attacker could read metadata (mtime) of files outside the vault, potentially leaking information about the filesystem.
FIXEnsure that glob results are constrained to the vault root by using the resolved searchDir and stripping any traversal from the returned paths. Alternatively, validate that the resolved path starts with the vault root.
MEDIUM1 finding
src/vault-local.ts:47
47async writeNote(path: string, content: string): Promise<boolean> {
48    const fullPath = await this.safePath(path);
49    try {
50        await mkdir(dirname(fullPath), { recursive: true });
51        await writeFile(fullPath, content, "utf-8");
52        return true;
53    } catch {
54        return false;
55    }
56}
src/main.ts:268src/tools.ts:58

// Exploitable only if an attacker can create symlinks in the vault (e.g., via a compromised LLM or if the vault is shared). Requires network exposure or compromised LLM.

EXPLAINThe safePath method resolves symlinks and checks that the resolved path is within the vault root. However, if a symlink exists inside the vault that points outside, the check passes because the symlink itself is within the vault. An attacker could create a symlink (if they have write access to the vault) and then use writeNote to write to an arbitrary location via that symlink. This is a classic symlink attack.
IMPACTAn attacker with the ability to create symlinks in the vault (e.g., via a compromised LLM or if the vault is writable by other means) could write arbitrary files outside the vault, potentially leading to code execution or system compromise.
FIXAfter resolving the path, verify that the resolved path does not traverse outside the vault root. Additionally, consider disallowing symlinks or checking that the resolved path is not a symlink pointing outside.
MEDIUM1 finding
src/vault-local.ts:38
38async readNote(path: string): Promise<string | null> {
39    const fullPath = await this.safePath(path);
40    try {
41        return await readFile(fullPath, "utf-8");
42    } catch {
43        return null;
44    }
45}
src/main.ts:268src/tools.ts:40

// Exploitable only if an attacker can create symlinks in the vault. Requires network exposure or compromised LLM.

EXPLAINSimilar to writeNote, the safePath method resolves symlinks but does not prevent reading through a symlink that points outside the vault. If a symlink exists inside the vault pointing to an external file, readNote can read that file.
IMPACTAn attacker could read arbitrary files on the system if they can create a symlink in the vault pointing to the target file.
FIXAfter resolving the path, verify that the resolved path is within the vault root. Consider disallowing symlinks or checking that the resolved path does not escape.
MEDIUM1 finding
src/vault-local.ts:58
58async deleteNote(path: string): Promise<boolean> {
59    const fullPath = await this.safePath(path);
60    try {
61        await unlink(fullPath);
62        return true;
63    } catch {
64        return false;
65    }
66}
src/main.ts:268src/tools.ts:254

// Exploitable only if an attacker can create symlinks in the vault. Requires network exposure or compromised LLM.

EXPLAINThe deleteNote method uses safePath which resolves symlinks. If a symlink inside the vault points to an external file, deleteNote can delete that external file.
IMPACTAn attacker could delete arbitrary files on the system if they can create a symlink in the vault pointing to the target file.
FIXAfter resolving the path, verify that the resolved path is within the vault root. Consider disallowing symlinks or checking that the resolved path does not escape.
MEDIUM1 finding
src/vault-local.ts:68
68async moveNote(from: string, to: string): Promise<boolean> {
69    const fromPath = await this.safePath(from);
70    const toPath = await this.safePath(to);
71    try {
72        await mkdir(dirname(toPath), { recursive: true });
73        await rename(fromPath, toPath);
74        return true;
75    } catch (e: any) {
76        if (e.code === "EXDEV") {
77            // Cross-device: fall back to copy-delete
78            const content = await this.readNote(from);
79            if (content === null) return false;
80            const wrote = await this.writeNote(to, content);
81            if (!wrote) return false;
82            return await this.deleteNote(from);
83        }
84        return false;
85    }
86}
src/main.ts:268src/tools.ts:269

// Exploitable only if an attacker can create symlinks in the vault. Requires network exposure or compromised LLM.

EXPLAINThe moveNote method uses safePath for both from and to paths. If a symlink exists inside the vault pointing outside, an attacker could move a file from outside the vault to another location (potentially also outside) by using the symlink as the from path. The rename operation can move files across filesystem boundaries, and the fallback copy-delete also uses readNote and writeNote which have the same symlink vulnerability.
IMPACTAn attacker could move arbitrary files on the system if they can create symlinks in the vault.
FIXAfter resolving the path, verify that the resolved path is within the vault root. Consider disallowing symlinks or checking that the resolved path does not escape.
MEDIUM1 finding
src/vault-local.ts:88
88async getMetadata(path: string): Promise<NoteInfo | null> {
89    const fullPath = await this.safePath(path);
90    try {
91        const [content, s] = await Promise.all([
92            readFile(fullPath, "utf-8"),
93            stat(fullPath),
94        ]);
95        return {
96            path,
97            size: s.size,
98            ctime: s.birthtimeMs,
99            mtime: s.mtimeMs,
100            ...parseFrontmatterAndLinks(content),
101        };
102    } catch {
103        return null;
104    }
105}
src/main.ts:268src/tools.ts:289

// Exploitable only if an attacker can create symlinks in the vault. Requires network exposure or compromised LLM.

EXPLAINThe getMetadata method uses safePath and then reads the file content and stats. If a symlink inside the vault points to an external file, getMetadata can read that file's content and metadata.
IMPACTAn attacker could read arbitrary files on the system if they can create a symlink in the vault pointing to the target file.
FIXAfter resolving the path, verify that the resolved path is within the vault root. Consider disallowing symlinks or checking that the resolved path does not escape.
6/8/2026
Findings are produced by automated LLM analysis and may include false positives or miss issues. Verify independently before acting.