{"record":{"id":"c04bb392613e3975","repo":"affaan-m/ECC","slug":"memory-destination-changed-while-it-was-being-crea","errorCode":null,"errorMessage":"Memory destination changed while it was being created.","messagePattern":"Memory destination changed while it was being created\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/memory-vault.js","lineNumber":215,"sourceCode":"  const flags = fs.constants.O_WRONLY\n    | fs.constants.O_CREAT\n    | fs.constants.O_EXCL\n    | (fs.constants.O_NOFOLLOW || 0);\n  let descriptor;\n  let operationError;\n  let cleanupError;\n  try {\n    descriptor = fs.openSync(temporaryPath, flags, 0o600);\n    const opened = fs.fstatSync(descriptor, { bigint: true });\n    const after = fs.lstatSync(temporaryPath, { bigint: true });\n    assertWithinTrustedRoot(temporaryPath, trustedRoot, 'write memory');\n    if (\n      !opened.isFile()\n      || after.isSymbolicLink()\n      || !after.isFile()\n      || !sameFileIdentity(after, opened)\n    ) {\n      throw new Error('Memory destination changed while it was being created.');\n    }\n    fs.writeFileSync(descriptor, content, 'utf8');\n    fs.fsyncSync(descriptor);\n    fs.closeSync(descriptor);\n    descriptor = undefined;\n    assertWithinTrustedRoot(filePath, trustedRoot, 'write memory');\n    fs.linkSync(temporaryPath, filePath);\n  } catch (error) {\n    operationError = error;\n  } finally {\n    if (descriptor !== undefined) {\n      try {\n        fs.closeSync(descriptor);\n      } catch (error) {\n        cleanupError = error;\n      }\n    }\n    try {","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/memory-vault.js#L197-L233","documentation":"writeCreateOnlyTextFile opens a unique temp file with O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW, then re-validates with fstat and lstat that the temp file is a regular file with matching identity before writing. If the just-created temp file is not a regular file, has become a symlink, or its inode/dev no longer matches, the write is aborted as a suspected swap. This is the write-side counterpart of the read TOCTOU guard.","triggerScenarios":"Between openSync(O_EXCL) of the temp file and the post-open fstat/lstat, the temp file was replaced, symlinked, or its inode changed. Realistic causes: a concurrent cleanup process deleting and recreating temp files, a sync tool touching .ecc-memory-*.tmp, an antivirus quarantining the temp, or the Windows libuv volume-serial mismatch on Node 22.12–22.16 / 24.0–24.1.","commonSituations":"Two saveMemory calls racing into the same directory; a janitor script that nukes .tmp files; security software scanning and recreating temp files; the affected libuv/Windows version range where dev mismatches break sameFileIdentity.","solutions":["Ensure only one process writes to a given vault directory at a time (serialize with a lock or queue).","On Windows, upgrade Node to a version with libuv >= 1.51.0 (22.17+, 24.2+).","Disable or exclude the vault directory from temp-cleaning and sync tools that may touch .ecc-memory-*.tmp files.","Retry the save once after the race clears; transient races on temp creation are usually self-correcting on the next attempt."],"exampleFix":"// before: janitor removes .ecc-memory-*.tmp mid-write\n// exclude the vault from the cleaner, then retry\nawait saveMemory(input);\n// after: temp file survives the open->fstat window, write succeeds","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async function saveStable(input, options, retries = 2) {\n  for (let i = 0; i <= retries; i++) {\n    try { return saveMemory(input, options); }\n    catch (error) {\n      if (/destination changed while it was being created/i.test(error.message) && i < retries) continue;\n      throw error;\n    }\n  }\n}","preventionTips":["Serialize writes to a vault directory with a lock or queue.","Exclude .ecc-memory-*.tmp files from janitors, sync tools, and antivirus.","On Windows, run Node 22.17+ / 24.2+ (libuv 1.51.0+).","Treat recurring 'destination changed' errors as a concurrency or security-tool signal."],"tags":["security","toctou","memory-vault","filesystem","concurrency"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}