Oct 23, 2026·5 min read·4 visits
obsidian-tc memory tools fail to verify note ownership before materializing files and perform non-atomic database state changes, resulting in potential note body destruction and memory graph desynchronization.
In obsidian-tc versions 1.0.1 through 1.31.3, memory lifecycle tools fail to validate note ownership prior to rematerializing Markdown files and execute non-atomic SQLite database mutations. An authorized caller can overwrite arbitrary note bodies on disk and cause persistent desynchronization between the database memory graph and physical vault files.
obsidian-tc is a governed Model Context Protocol (MCP) server designed for Obsidian vaults. It exposes memory management tools to AI agents by serializing knowledge entities in a SQLite database and materializing corresponding Markdown notes on disk within vault structures.
In versions 1.0.1 through 1.31.3, the memory toolsuite (create_entity, link_entities, rename_entity, unlink_entities, and delete_entity) exposes two critical flaws during note processing. First, the materialization routine lacks validation to verify whether an existing note at a computed file path is owned by the target entity being modified. Second, state mutations in SQLite occur prior to fallible filesystem operations without transactional rollback mechanisms.
This flaw is tracked as CVE-2026-115478 and GHSA-r3g7-r4hr-m258. It carries a CVSS v4.0 base score of 7.1 (High) with vector CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N. Authorized users or agents can overwrite hand-authored or foreign notes and induce persistent graph state desynchronization.
The root cause lies in packages/server/src/memory/materialize.ts and packages/server/src/tools/m5/memory-lifecycle-tools.ts. When a lifecycle tool invokes materializeEntity, the relative file path is derived as memory/<type>/<name>.md. Before writing content, the function inspects existing files to retain top-level YAML frontmatter but fails to verify the note's obsidian_tc_id frontmatter attribute.
If a file exists at the calculated path—such as a user-authored note or an orphaned note created by a separate entity whose name sanitized to the identical path—materializeEntity regenerates the body content using SQLite state. Because the database contains no body text for unowned entities, the function overwrites and deletes the existing note body while retaining only top-level frontmatter.
In addition, memory lifecycle tools execute non-atomic mutations. SQL database rows are modified or inserted before disk file I/O operations take place. If a filesystem operation fails due to permission errors or path ACL refusals, the database updates persist while disk writes fail, leaving the memory graph out of sync with disk files.
Prior to the patch, materializeEntity in packages/server/src/memory/materialize.ts checked for file existence solely to read frontmatter without evaluating ownership against input.id.
// Pre-patch implementation in materialize.ts
let preserved: Frontmatter | null = null;
const ex = noteExists(abs);
if (ex.exists && ex.type === "file") preserved = parseNote(readNote(abs).raw, rel).frontmatter;
const content = renderEntityNote({ ...input, preserved });
writeNoteAtomic(abs, content, true);The fix implemented in PR #978 and commit 99f7914ca0fbeed92e7c4d39cc8c3e5830dab133 introduces readNoteOwner and assertNoteOwnership to validate that obsidian_tc_id matches the target entity ID.
// Post-patch implementation in materialize.ts
function readNoteOwner(abs: string): {
exists: boolean;
ownerId: string | null;
frontmatter: Frontmatter | null;
} {
const ex = noteExists(abs);
if (!ex.exists || ex.type !== "file") return { exists: false, ownerId: null, frontmatter: null };
const frontmatter = parseNote(readNote(abs).raw).frontmatter;
const ownerId =
frontmatter && typeof frontmatter.obsidian_tc_id === "string"
? frontmatter.obsidian_tc_id
: null;
return { exists: true, ownerId, frontmatter };
}
export function materializeEntity(input: MaterializeInput): {
vaultPath: string;
contentHash: string;
} {
const rel = entityNotePath(input.folder, input.entityType, input.name);
const abs = resolveVaultPath(input.root, rel);
enforcePathAcl(input.acl, "write", rel, input.root, input.grantedScopes);
const owner = readNoteOwner(abs);
if (owner.exists && owner.ownerId !== input.id)
throw ownershipError(rel, input.id, owner.ownerId);
const preserved = owner.frontmatter;
const content = renderEntityNote({ ...input, preserved });
writeNoteAtomic(abs, content, true);
}Furthermore, lifecycle handlers in packages/server/src/tools/m5/memory-lifecycle-tools.ts were updated to run ownership pre-checks before executing transactions (inWriteTransaction) and to catch materialization errors, rolling back inserted rows via deleteEntity or deleteRelation when file routines fail.
Exploitation requires low-privileged network access to invoke memory lifecycle tools through the Model Context Protocol interface. An attacker or malfunctioning LLM agent issues structured RPC requests targeted at entity creation or modification functions.
In a data destruction attack, an authorized caller targets a path containing user content, such as memory/person/admin_notes.md. By calling create_entity with entityType: "person" and name: "admin_notes", the server derives the target path, skips ownership verification, and overwrites the target file body with an empty template while preserving frontmatter headers.
In a graph state desynchronization attack, an attacker submits a rename_entity request where target directory access is restricted at the filesystem layer. The server updates the internal SQLite entity record to the new name and retired status. When the subsequent file write fails, the database retains the modified state while disk notes remain unchanged, corrupting future graph lookup queries.
The impact affects data integrity and system availability, classified under CWE-863 (Incorrect Authorization) and CWE-459 (Incomplete Cleanup). Low-privileged API users can destroy text content inside vault notes outside their entity boundaries without holding explicit write permissions for those resources.
Operational risks involve state divergence between SQLite and Markdown files on disk. Automated agents depending on graph accuracy will receive inconsistent query results or encounter exceptions when referenced files do not match stored database state.
CVSS v4.0 evaluation rates Vulnerable System Integrity as High (VI:H) due to note body deletion and Vulnerable System Availability as Low (VA:L) due to state desynchronization. Confidentiality remains unaffected (VC:N). CISA KEV and threat intelligence sources confirm no active exploitation in the wild.
Upgrade obsidian-tc to version 1.31.4 or higher. The updated version enforces ownership validation via assertNoteOwnership and transactional database rollbacks.
If immediate patching cannot be deployed, enable snapshot functionality by setting snapshots.enabled: true in obsidian-tc.config.json. Pre-write snapshots allow administrators to recover overwritten files using restore_note tools.
Additionally, configure strict path access control lists by specifying writePaths: ["memory/**"] in configuration files. Restricting write scopes prevents memory operations from accessing or overwriting hand-authored notes outside designated subdirectories.
| Attribute | Detail |
|---|---|
| CWE ID | CWE-459, CWE-863 |
| CVSS v4.0 Score | 7.1 (HIGH) |
| CVSS v4.0 Vector | CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N |
| Attack Vector | Network |
| Privileges Required | Low (PR:L) |
| Exploit Status | None (No public exploit available) |
| CISA KEV Status | Not Listed |
An insufficient verification of data authenticity vulnerability in SiYuan prior to version 3.7.4 allows an attacker to overwrite arbitrary installed marketplace packages (plugins, widgets, themes, and templates). By providing a mismatched package name and package archive repository URL during a Bazaar installation request, arbitrary file write into trusted plugin directories can be achieved, enabling persistence and execution across application restarts.
A high-severity authorization bypass vulnerability in obsidian-tc allows clients to bypass Human-In-The-Loop (HITL) confirmation prompts over Streamable HTTP deployments. Due to incomplete validation of returned prompt responses, an attacker echoing a signed requestState token can force execution of destructive vault operations even if a human user declines or cancels the prompt.
A Denial of Service (DoS) vulnerability in pypdf versions prior to 6.21.0 allows remote attackers to cause high CPU and memory resource exhaustion by supplying PDF files containing oversized or deeply nested arrays, dictionaries, or content streams.
An uncontrolled resource consumption and inefficient algorithmic complexity vulnerability in pypdf prior to version 6.21.0 allows remote attackers to trigger application-level denial-of-service (DoS) via crafted PDF form field hierarchies.
Nuxt versions 3.11.0 through 3.21.3 and 4.0.0 through 4.4.3 contain an interpretation conflict between the route rules matching engine and the core page routing mechanism. By submitting percent-encoded request path segments, unauthenticated attackers can bypass route rules, including appMiddleware authorization guards, and access rendered server-side page payloads.
A Cross-Site Scripting (XSS) vulnerability exists in Nuxt when configured with pages disabled (pages: false). ASCII C0 control characters prefixing javascript: URIs bypass internal protocol classification in the fallback router, allowing unauthenticated attackers to execute arbitrary script in the victim's origin context upon user interaction.