From efdf17b034703f8b299ae9f17910c0620615b129 Mon Sep 17 00:00:00 2001 From: haelyra <49814733+haelyra@users.noreply.github.com> Date: Thu, 13 Aug 2026 17:20:48 -0400 Subject: [PATCH] fix(security): verify legacy files after opening --- scripts/lib/codex-legacy-sync.js | 27 ++++++++++++++------------- tests/lib/codex-legacy-sync.test.js | 14 ++++++++++---- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/scripts/lib/codex-legacy-sync.js b/scripts/lib/codex-legacy-sync.js index 91b0bda11..ac6e7ad2a 100644 --- a/scripts/lib/codex-legacy-sync.js +++ b/scripts/lib/codex-legacy-sync.js @@ -17,21 +17,22 @@ function getStatePath(codexHome) { function openRegularFileNoFollow(filePath, writable = false) { const noFollow = fs.constants.O_NOFOLLOW || 0; const flags = (writable ? fs.constants.O_RDWR : fs.constants.O_RDONLY) | noFollow; - let pathStat; - try { - pathStat = fs.lstatSync(filePath, { bigint: true }); - } catch (error) { - if (error.code === 'ENOENT') return null; - throw error; - } - if (!pathStat.isFile() || pathStat.isSymbolicLink()) { - throw new Error(`Refusing to manage non-regular legacy sync path: ${filePath}`); - } let descriptor; try { descriptor = fs.openSync(filePath, flags); } catch (error) { - if (error.code === 'ENOENT') return null; + if (error.code === 'ENOENT') { + try { + const unresolved = fs.lstatSync(filePath); + if (unresolved.isSymbolicLink() || !unresolved.isFile()) { + throw new Error(`Refusing to manage non-regular legacy sync path: ${filePath}`); + } + } catch (lstatError) { + if (lstatError.code === 'ENOENT') return null; + throw lstatError; + } + throw error; + } if (error.code === 'ELOOP') { throw new Error(`Refusing to manage non-regular legacy sync path: ${filePath}`); } @@ -52,10 +53,10 @@ function openRegularFileNoFollow(filePath, writable = false) { !descriptorStat.isFile() || !finalPathStat.isFile() || finalPathStat.isSymbolicLink() - || descriptorStat.dev !== pathStat.dev - || descriptorStat.ino !== pathStat.ino || descriptorStat.dev !== finalPathStat.dev || descriptorStat.ino !== finalPathStat.ino + || descriptorStat.nlink !== 1n + || finalPathStat.nlink !== 1n ) { fs.closeSync(descriptor); throw new Error(`Refusing to manage non-regular legacy sync path: ${filePath}`); diff --git a/tests/lib/codex-legacy-sync.test.js b/tests/lib/codex-legacy-sync.test.js index 051ab584e..ff98b06ca 100644 --- a/tests/lib/codex-legacy-sync.test.js +++ b/tests/lib/codex-legacy-sync.test.js @@ -194,7 +194,8 @@ function runTests() { const createdPath = path.join(codexHome, 'prompts', 'ecc-review.md'); fs.mkdirSync(path.dirname(existingPath), { recursive: true }); fs.writeFileSync(existingPath, '# User prompt\n', { mode: 0o640 }); - const originalMode = fs.statSync(existingPath).mode & 0o777; + const existingDescriptor = fs.openSync(existingPath, 'r+'); + const originalMode = fs.fstatSync(existingDescriptor).mode & 0o777; const statePath = beginLegacySyncState({ codexHome, @@ -204,7 +205,9 @@ function runTests() { }); recordLegacySyncPath({ statePath, filePath: existingPath }); recordLegacySyncPath({ statePath, filePath: createdPath }); - fs.writeFileSync(existingPath, '# Partial ECC write\n'); + const partialContent = Buffer.from('# Partial ECC write\n'); + fs.ftruncateSync(existingDescriptor, 0); + fs.writeSync(existingDescriptor, partialContent, 0, partialContent.length, 0); fs.writeFileSync(createdPath, '# Partial new file\n'); let hooksValue = path.join(codexHome, 'git-hooks'); @@ -215,8 +218,11 @@ function runTests() { }); assert.strictEqual(result.status, 'rolled-back'); - assert.strictEqual(fs.readFileSync(existingPath, 'utf8'), '# User prompt\n'); - assert.strictEqual(fs.statSync(existingPath).mode & 0o777, originalMode); + const restoredContent = Buffer.alloc(Buffer.byteLength('# User prompt\n')); + fs.readSync(existingDescriptor, restoredContent, 0, restoredContent.length, 0); + assert.strictEqual(restoredContent.toString('utf8'), '# User prompt\n'); + assert.strictEqual(fs.fstatSync(existingDescriptor).mode & 0o777, originalMode); + fs.closeSync(existingDescriptor); assert.ok(!fs.existsSync(createdPath)); assert.strictEqual(hooksValue, '/tmp/user-hooks'); assert.ok(!fs.existsSync(statePath));