diff --git a/scripts/lib/codex-legacy-sync.js b/scripts/lib/codex-legacy-sync.js index 0efce58a7..91b0bda11 100644 --- a/scripts/lib/codex-legacy-sync.js +++ b/scripts/lib/codex-legacy-sync.js @@ -17,6 +17,16 @@ 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); @@ -27,12 +37,30 @@ function openRegularFileNoFollow(filePath, writable = false) { } throw error; } - const stat = fs.fstatSync(descriptor); - if (!stat.isFile()) { + const descriptorStat = fs.fstatSync(descriptor, { bigint: true }); + let finalPathStat; + try { + finalPathStat = fs.lstatSync(filePath, { bigint: true }); + } catch (error) { + fs.closeSync(descriptor); + if (error.code === 'ENOENT') { + throw new Error(`Legacy sync path changed while opening: ${filePath}`); + } + throw error; + } + if ( + !descriptorStat.isFile() + || !finalPathStat.isFile() + || finalPathStat.isSymbolicLink() + || descriptorStat.dev !== pathStat.dev + || descriptorStat.ino !== pathStat.ino + || descriptorStat.dev !== finalPathStat.dev + || descriptorStat.ino !== finalPathStat.ino + ) { fs.closeSync(descriptor); throw new Error(`Refusing to manage non-regular legacy sync path: ${filePath}`); } - return { descriptor, stat }; + return { descriptor, stat: fs.fstatSync(descriptor) }; } function readRegularFileNoFollow(filePath, encoding = null) { diff --git a/tests/lib/codex-legacy-sync.test.js b/tests/lib/codex-legacy-sync.test.js index f860671c6..051ab584e 100644 --- a/tests/lib/codex-legacy-sync.test.js +++ b/tests/lib/codex-legacy-sync.test.js @@ -194,6 +194,7 @@ 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 statePath = beginLegacySyncState({ codexHome, @@ -215,7 +216,7 @@ function runTests() { assert.strictEqual(result.status, 'rolled-back'); assert.strictEqual(fs.readFileSync(existingPath, 'utf8'), '# User prompt\n'); - assert.strictEqual(fs.statSync(existingPath).mode & 0o777, 0o640); + assert.strictEqual(fs.statSync(existingPath).mode & 0o777, originalMode); assert.ok(!fs.existsSync(createdPath)); assert.strictEqual(hooksValue, '/tmp/user-hooks'); assert.ok(!fs.existsSync(statePath));