fix(memory): implement atomic writes to prevent race condition

Fixes race condition vulnerability where concurrent operations could
corrupt memory.json file by writing incomplete or malformed data.

Changes:
- Add atomic write operations using temporary files and rename
- Import crypto.randomBytes for generating unique temp file names
- Add proper error handling and cleanup for temp files
- Use same pattern as filesystem server for consistency

This resolves JSON parsing errors like 'Unexpected non-whitespace
character after JSON' that occurred when multiple processes
attempted simultaneous file writes.

Fixes #2579

Co-authored-by: Ola Hungerford <olaservo@users.noreply.github.com>
This commit is contained in:
claude[bot]
2025-08-24 03:26:36 +00:00
parent 338d8af7a6
commit 03ddb97bda

View File

@@ -9,6 +9,7 @@ import {
import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { randomBytes } from 'crypto';
// Define memory file path using environment variable with fallback
const defaultMemoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'memory.json');
@@ -63,7 +64,22 @@ class KnowledgeGraphManager {
...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })),
...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })),
];
await fs.writeFile(MEMORY_FILE_PATH, lines.join("\n"));
// Use atomic rename to prevent race conditions where concurrent writes
// could corrupt the file. Rename operations are atomic on all filesystems.
const tempPath = `${MEMORY_FILE_PATH}.${randomBytes(16).toString('hex')}.tmp`;
try {
await fs.writeFile(tempPath, lines.join("\n"), 'utf-8');
await fs.rename(tempPath, MEMORY_FILE_PATH);
} catch (error) {
// Clean up temp file on failure
try {
await fs.unlink(tempPath);
} catch (unlinkError) {
// Ignore unlink errors - temp file might not exist or already cleaned up
}
throw error;
}
}
async createEntities(entities: Entity[]): Promise<Entity[]> {