From 03ddb97bda562b0a7bbff4f3b0536a6b163338df Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 03:26:36 +0000 Subject: [PATCH] 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 --- src/memory/index.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/memory/index.ts b/src/memory/index.ts index 4590a1db..9dee0b52 100644 --- a/src/memory/index.ts +++ b/src/memory/index.ts @@ -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 {