{"id":"CVE-2026-115478","kind":"vulnerability","aliases":["CVE-2026-115478","GHSA-r3g7-r4hr-m258"],"related":[],"title":"obsidian-tc: Memory-lifecycle tools overwrite unowned notes or partially commit lifecycle changes","description":"### Summary\n`create_entity`, `link_entities`, `rename_entity`, `unlink_entities` and `delete_entity` regenerate a memory note's body from SQLite at the computed `memory/<type>/<name>.md` path. When a note already existed at that path and was not owned by the entity being written (a hand-authored note, or an orphan left by a different entity whose name sanitized to the same path), its entire body was overwritten on the next write naming that `(type, name)`, keeping only its frontmatter. Separately, several of these tools inserted or removed their SQLite row before the fallible filesystem step, so a refused note write could leave a relation gone, a retired status committed, or the wrong entity deleted.\n\n### Details\n- `create_entity` and `link_entities` inserted the SQL row or relation before materialization.\n- `rename_entity` seeded the destination path with a direct atomic write that bypassed the ownership check, and for a status-only rename could commit a retired status with the note write refused.\n- `unlink_entities` removed the relation row before discovering the source note's rematerialization would be refused.\n- `delete_entity`'s cascade could delete the target and its relations before a neighbour's note was found to be foreign.\n\nThe overwrite exists in every released version since `materializeEntity` shipped (`git show v1.0.1:packages/server/src/memory/materialize.ts`).\n\n### Impact\nAn authorized caller with memory-write scope (any agent using the M5 memory tools) can, without intending to, destroy the body of a vault note outside the entity it is writing, and can leave the memory graph inconsistent with the notes on disk. Snapshots-before-write (`restore_note`) mitigate the overwrite only where snapshots are enabled and the note had been written through obsidian-tc before.\n\n### Patches\nFixed in 1.31.4 at the shared primitive: `materializeEntity` refuses to write over a note whose `obsidian_tc_id` is missing or belongs to another entity; a new `assertNoteOwnership` pre-check runs before any SQLite mutation in the three lifecycle tools, so a refusal is a no-op; `create_entity` and `link_entities` roll back the row they had inserted. Verified by `test/m5-memory-ownership.test.ts`, which covers all five call sites and the rollback.\n\n### Workarounds\nNone that preserve the tools. Disabling the `write:memory` scope for callers prevents the write path entirely.\n\n### Credit\nFound by the maintainer while building `obsidian-tc memory import` (PR #978); the vulnerability class was independently confirmed by a cross-vendor adversarial review.\n\n### References\n- PR #978 (fix and test), CHANGELOG `## [1.31.4] ### Security`\n- `packages/server/src/memory/materialize.ts`, `packages/server/src/tools/m5/memory-lifecycle-tools.ts`\n\n---","summary":"The obsidian-tc MCP memory tools (create, link, rename, unlink and delete entity) regenerate a note's body from SQLite at a computed path, overwriting any existing note there that the entity does not own, such as a hand-authored note. They can also commit SQLite changes before a refused note write, leaving the memory graph inconsistent with the files on disk.","severity":"high","cvss":7.1,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"obsidian-tc","fixed":["1.31.4"],"affected":">= 1.0.1, <= 1.31.3","ecosystem":"npm"}],"ecosystems":["npm"],"tags":["agents-with-memory-write","ai-tooling","claude-code","cwe-459","cwe-863","mcp","npm","obsidian-tc"],"not_affected_if":["no-obsidian-tc-installed","no-write-memory-scope-granted","version-1.31.4-or-later"],"how_to_check":"Run npm ls obsidian-tc in the project that runs the MCP server and check whether the version is between 1.0.1 and 1.31.3 inclusive.","fix":"Upgrade obsidian-tc to 1.31.4 or later.\nNo workaround preserves the tools; disabling the write:memory scope for callers blocks the write path entirely.","status":"open","published_at":"2026-10-23T15:17:30+00:00","first_seen_at":"2026-10-23T19:11:31+00:00","triaged_at":"2026-10-23T20:55:01+00:00","updated_at":"2026-10-23T21:57:34+00:00","category":"ai-tooling","confidence":0.8,"triage":{"at":"2026-10-23T20:55:01Z","model":"claude-haiku-5-5","output":{"fix":"Upgrade obsidian-tc to 1.31.4 or later.\nNo workaround preserves the tools; disabling the write:memory scope for callers blocks the write path entirely.","reach":"unknown","impact":"medium","summary":"The obsidian-tc MCP memory tools (create, link, rename, unlink and delete entity) regenerate a note's body from SQLite at a computed path, overwriting any existing note there that the entity does not own, such as a hand-authored note. They can also commit SQLite changes before a refused note write, leaving the memory graph inconsistent with the files on disk.","category":"ai-tooling","who_cares":["obsidian-tc","mcp","claude-code","npm","agents-with-memory-write"],"confidence":0.8,"needs_auth":"yes","worst_case":"Authorized agent destroys bodies of unrelated vault notes and leaves memory graph inconsistent","analyst_note":"","how_to_check":"Run npm ls obsidian-tc in the project that runs the MCP server and check whether the version is between 1.0.1 and 1.31.3 inclusive.\nunknown","needs_analyst":false,"not_affected_if":["version-1.31.4-or-later","no-write-memory-scope-granted","no-obsidian-tc-installed"],"user_interaction":"no","needs_special_setup":"unknown","severity_if_unknown":"high"},"cost_usd":0.000905,"input_tokens":3741,"output_tokens":1061,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-115478':18A,26A '-2026':17A,25A '-459':42B '-863':44B '1.31.4':400C,523C '978':502C,518C 'adversari':514C 'agent':31B,337C 'agents-with-memory-writ':30B 'ai':36B 'ai-tool':35B 'alreadi':143C 'also':95B 'anoth':421C 'assertnoteownership':425C 'atom':252C 'author':91B,160C,329C 'back':453C 'belong':419C 'bodi':68B,131C,179C,349C 'build':495C 'bypass':255C 'call':466C 'caller':330C,483C 'cascad':295C 'chang':15A,98B 'changelog':522C 'check':258C,428C 'class':505C 'claud':39B 'claude-cod':38B 'code':40B 'commit':13A,96B,223C,267C 'comput':73B,136C 'confirm':508C 'could':215C,266C,296C 'cover':463C 'creat':57B,115C,230C,447C 'credit':489C 'cross':512C 'cross-vendor':511C 'cve':16A,24A 'cwe':41B,43B 'delet':62B,124C,228C,292C,297C 'destin':247C 'destroy':347C 'detail':229C 'differ':168C 'direct':251C 'disabl':477C 'discov':283C 'disk':113B,371C 'enabl':385C 'entir':178C,488C 'entiti':63B,82B,116C,118C,120C,122C,125C,154C,169C,227C,231C,234C,244C,277C,293C,356C,422C,448C,451C 'everi':317C 'exist':77B,144C,315C 'fallibl':207C 'file':111B 'filesystem':208C 'five':465C 'fix':398C,519C 'foreign':312C 'found':309C,490C 'frontmatt':193C 'ghsa':20A 'ghsa-r3g7-r4hr-m258':19A 'git':323C 'gone':219C 'graph':107B,365C 'hand':90B,159C 'hand-author':89B,158C 'id':415C 'impact':327C 'import':500C 'inconsist':108B,366C 'independ':507C 'insert':199C,235C,458C 'intend':345C 'keep':190C 'leav':104B,216C,362C 'left':165C 'lifecycl':6A,14A,437C 'link':58B,117C,233C,450C 'm258':23A 'm5':340C 'maintain':493C 'materi':242C 'materializeent':321C,405C 'mcp':45B,54B 'md':138C 'memori':5A,33B,55B,106B,128C,137C,333C,341C,364C,480C,499C 'memory-lifecycl':4A 'memory-writ':332C 'miss':417C 'mitig':378C 'mutat':433C 'name':171C,186C,189C 'neighbour':305C 'new':424C 'next':184C 'no-op':444C 'none':472C 'note':10A,66B,78B,92B,102B,129C,142C,161C,213C,273C,286C,307C,353C,369C,377C,388C,411C 'npm':46B 'obsidian':2A,28A,48B,52B,394C,413C,497C 'obsidian-tc':1A,27A,47B,51B,393C,496C 'op':446C 'orphan':164C 'outsid':354C 'overwrit':8A,75B,314C,380C 'overwritten':181C 'own':151C 'ownership':257C 'packages/server/src/memory/materialize.ts':326C,525C 'packages/server/src/tools/m5/memory-lifecycle-tools.ts':526C 'partial':12A 'patch':397C 'path':74B,139C,147C,176C,248C,487C 'pr':501C,517C 'pre':427C 'pre-check':426C 'preserv':474C 'prevent':484C 'primit':404C 'r3g7':21A 'r4hr':22A 'refer':516C 'refus':101B,212C,275C,291C,406C,441C 'regener':64B,126C 'relat':218C,240C,280C,302C 'releas':318C 'remateri':288C 'remov':201C,278C 'renam':59B,119C,243C,265C 'restor':376C 'retir':221C,269C 'review':515C 'roll':452C 'rollback':470C 'row':204C,238C,281C,455C 'run':429C 'sanit':172C 'scope':335C,481C 'secur':524C 'seed':245C 'separ':194C 'sever':195C 'share':403C 'ship':322C 'show':324C 'sinc':320C 'site':467C 'snapshot':373C,383C 'snapshots-before-writ':372C 'sourc':285C 'sql':237C 'sqlite':70B,97B,133C,203C,432C 'status':222C,263C,270C 'status-on':262C 'step':209C 'summari':114C 'target':299C 'tc':3A,29A,49B,53B,395C,414C,498C 'test':521C 'test/m5-memory-ownership.test.ts':461C 'three':436C 'tool':7A,37B,56B,198C,342C,438C,476C 'type':188C 'unlink':60B,121C,276C 'unown':9A 'use':338C 'v1.0.1':325C 'vault':352C 'vendor':513C 'verifi':459C 'version':319C 'vulner':504C 'whose':170C,412C 'without':344C 'workaround':471C 'would':289C 'write':34B,103B,185C,214C,253C,274C,334C,359C,375C,408C,479C,486C 'written':156C,391C 'wrong':226C","nicknames":[],"mitigations":[],"who_affected":null,"tier":"B","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-23T15:17:30+00:00","kev":null,"cvss_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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","cvss_by":"security-advisories@github.com","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-r3g7-r4hr-m258","url":"https://github.com/advisories/GHSA-r3g7-r4hr-m258","severity":"high","published_at":"2026-10-23T18:47:25+00:00","modified_at":"2026-10-23T18:47:40+00:00","first_seen_at":"2026-10-23T19:11:31+00:00","fetched_at":"2026-10-23T19:11:31+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-115478","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-115478","severity":null,"published_at":"2026-10-23T15:17:30+00:00","modified_at":"2026-10-23T15:17:30+00:00","first_seen_at":"2026-10-23T21:57:34+00:00","fetched_at":"2026-10-23T22:29:03+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}