CVSS Assessment
| Metric |
Value |
| Score |
6.8 / 10.0 |
| Severity |
Medium |
| Vector |
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L |
CVSS v3.1 Calculator
Summary
Multiple methods in the memory index manager perform database writes across four tables (chunks, chunks_vec, chunks_fts, files) without transaction wrappers. If any operation fails midway due to disk errors, process crashes, or resource exhaustion, the SQLite database will be left in an inconsistent state with orphaned records, missing embeddings, or mismatched indexes.
Affected Code
Location 1: indexFile method
File: src/memory/manager.ts:2257-2354
private async indexFile(
entry: MemoryFileEntry | SessionFileEntry,
options: { source: MemorySource; content?: string },
) {
// ... embedding logic ...
// DELETE operations without transaction
if (vectorReady) {
try {
this.db
.prepare(
`DELETE FROM ${VECTOR_TABLE} WHERE id IN (SELECT id FROM chunks WHERE path = ? AND source = ?)`,
)
.run(entry.path, options.source);
} catch {}
}
if (this.fts.enabled && this.fts.available) {
try {
this.db
.prepare(`DELETE FROM ${FTS_TABLE} WHERE path = ? AND source = ? AND model = ?`)
.run(entry.path, options.source, this.provider.model);
} catch {}
}
this.db
.prepare(`DELETE FROM chunks WHERE path = ? AND source = ?`)
.run(entry.path, options.source);
// INSERT operations in loop without transaction
for (let i = 0; i < chunks.length; i++) {
// INSERT into chunks, VECTOR_TABLE, FTS_TABLE
}
// Final INSERT into files
this.db.prepare(`INSERT INTO files ...`).run(...);
}
Location 2: syncMemoryFiles stale cleanup
File: src/memory/manager.ts:1117-1137
// Inside syncMemoryFiles method - stale entry cleanup
for (const stale of staleRows) {
if (activePaths.has(stale.path)) continue;
this.db.prepare(`DELETE FROM files WHERE path = ? AND source = ?`).run(stale.path, "memory");
try {
this.db.prepare(`DELETE FROM ${VECTOR_TABLE} WHERE id IN ...`).run(stale.path, "memory");
} catch {}
this.db.prepare(`DELETE FROM chunks WHERE path = ? AND source = ?`).run(stale.path, "memory");
if (this.fts.enabled && this.fts.available) {
try {
this.db.prepare(`DELETE FROM ${FTS_TABLE} WHERE path = ? AND source = ? AND model = ?`).run(...);
} catch {}
}
}
Location 3: syncSessionFiles stale cleanup
File: src/memory/manager.ts:1214-1238
Same pattern as Location 2, but with source = "sessions".
Location 4: Standalone syncMemoryFiles function
File: src/memory/sync-memory-files.ts:81-101
for (const stale of staleRows) {
if (activePaths.has(stale.path)) continue;
params.db.prepare(`DELETE FROM files WHERE path = ? AND source = ?`).run(stale.path, "memory");
try {
params.db.prepare(`DELETE FROM ${params.vectorTable} WHERE id IN ...`).run(stale.path, "memory");
} catch {}
params.db.prepare(`DELETE FROM chunks WHERE path = ? AND source = ?`).run(stale.path, "memory");
if (params.ftsEnabled && params.ftsAvailable) {
try {
params.db.prepare(`DELETE FROM ${params.ftsTable} WHERE path = ? AND source = ? AND model = ?`).run(...);
} catch {}
}
}
Location 5: Standalone syncSessionFiles function
File: src/memory/sync-session-files.ts:106-130
for (const stale of staleRows) {
if (activePaths.has(stale.path)) continue;
params.db.prepare(`DELETE FROM files WHERE path = ? AND source = ?`).run(stale.path, "sessions");
try {
params.db.prepare(`DELETE FROM ${params.vectorTable} WHERE id IN ...`).run(stale.path, "sessions");
} catch {}
params.db.prepare(`DELETE FROM chunks WHERE path = ? AND source = ?`).run(stale.path, "sessions");
if (params.ftsEnabled && params.ftsAvailable) {
try {
params.db.prepare(`DELETE FROM ${params.ftsTable} WHERE path = ? AND source = ? AND model = ?`).run(...);
} catch {}
}
}
Attack Surface
How is this reached?
Authentication required?
Entry point: Memory sync operations triggered by file changes, search queries, or interval timers via MemoryIndexManager.sync().
Exploit Conditions
Complexity: [x] Low
User interaction: [x] None
Prerequisites: Process crash, disk full, or OOM during memory indexing operation.
Impact Assessment
Scope: [x] Unchanged
| Impact Type |
Level |
Description |
| Confidentiality |
None |
No data exposure |
| Integrity |
High |
Database left in inconsistent state: chunks deleted but files table not updated, embeddings missing from vector table, FTS index out of sync with chunks table |
| Availability |
Low |
Memory search returns incomplete or incorrect results until next full reindex |
Steps to Reproduce
- Start the memory indexer with a large workspace containing many markdown files
- Trigger a sync operation (e.g.,
memory sync --force)
- Kill the process mid-operation (Ctrl+C or
kill -9)
- Restart and observe inconsistent state:
SELECT COUNT(*) FROM files vs SELECT COUNT(DISTINCT path) FROM chunks may differ
- Vector table may have entries for chunks that no longer exist
- FTS table may be out of sync with chunks table
Recommended Fix
Wrap the multi-write operations in transactions. The codebase already has a working pattern at seedEmbeddingCache (lines 711-759):
// Example for indexFile method
private async indexFile(
entry: MemoryFileEntry | SessionFileEntry,
options: { source: MemorySource; content?: string },
) {
// ... embedding logic unchanged ...
const now = Date.now();
try {
this.db.exec("BEGIN");
// All DELETE operations
if (vectorReady) {
try {
this.db.prepare(`DELETE FROM ${VECTOR_TABLE} WHERE id IN ...`).run(...);
} catch {}
}
// ... other deletes ...
// All INSERT operations in loop
for (let i = 0; i < chunks.length; i++) {
// ... inserts ...
}
// Final files INSERT
this.db.prepare(`INSERT INTO files ...`).run(...);
this.db.exec("COMMIT");
} catch (err) {
try {
this.db.exec("ROLLBACK");
} catch {}
throw err;
}
}
Apply the same pattern to all 5 affected locations.
References
- CWE: CWE-460 - Improper Cleanup on Thrown Exception
CVSS Assessment
Summary
Multiple methods in the memory index manager perform database writes across four tables (chunks, chunks_vec, chunks_fts, files) without transaction wrappers. If any operation fails midway due to disk errors, process crashes, or resource exhaustion, the SQLite database will be left in an inconsistent state with orphaned records, missing embeddings, or mismatched indexes.
Affected Code
Location 1:
indexFilemethodFile:
src/memory/manager.ts:2257-2354Location 2:
syncMemoryFilesstale cleanupFile:
src/memory/manager.ts:1117-1137Location 3:
syncSessionFilesstale cleanupFile:
src/memory/manager.ts:1214-1238Same pattern as Location 2, but with
source = "sessions".Location 4: Standalone
syncMemoryFilesfunctionFile:
src/memory/sync-memory-files.ts:81-101Location 5: Standalone
syncSessionFilesfunctionFile:
src/memory/sync-session-files.ts:106-130Attack Surface
How is this reached?
Authentication required?
Entry point: Memory sync operations triggered by file changes, search queries, or interval timers via
MemoryIndexManager.sync().Exploit Conditions
Complexity: [x] Low
User interaction: [x] None
Prerequisites: Process crash, disk full, or OOM during memory indexing operation.
Impact Assessment
Scope: [x] Unchanged
Steps to Reproduce
memory sync --force)kill -9)SELECT COUNT(*) FROM filesvsSELECT COUNT(DISTINCT path) FROM chunksmay differRecommended Fix
Wrap the multi-write operations in transactions. The codebase already has a working pattern at
seedEmbeddingCache(lines 711-759):Apply the same pattern to all 5 affected locations.
References