fix(codex-auth): restrict symlink fallback and harden copy path (#1365)

This commit is contained in:
Kai (Tam Nhu) Tran
2026-05-23 21:43:51 -04:00
committed by GitHub
parent 1065f68eae
commit 0bd2e577b2
2 changed files with 38 additions and 4 deletions
+25 -4
View File
@@ -94,7 +94,7 @@ export function ensureSharedConfigSymlink(
target: targetPath,
});
} catch (err) {
copySharedConfigFallback(targetPath, linkPath, err);
handleSymlinkFailure(targetPath, linkPath, err);
}
}
@@ -106,9 +106,30 @@ function isRegularConfigCopyUnmodified(linkPath: string, targetPath: string): bo
}
}
function copySharedConfigFallback(targetPath: string, linkPath: string, err: unknown): void {
fs.copyFileSync(targetPath, linkPath);
fs.chmodSync(linkPath, 0o600);
function handleSymlinkFailure(targetPath: string, linkPath: string, err: unknown): void {
const code = (err as NodeJS.ErrnoException | null)?.code;
if (code !== 'EPERM' && code !== 'EACCES' && code !== 'ENOSYS') {
throw err;
}
const tmpPath = path.join(
path.dirname(linkPath),
`.config.toml.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`
);
try {
fs.copyFileSync(targetPath, tmpPath, fs.constants.COPYFILE_EXCL);
fs.chmodSync(tmpPath, 0o600);
fs.renameSync(tmpPath, linkPath);
} catch (copyErr) {
try {
fs.rmSync(tmpPath, { force: true });
} catch {
// best effort cleanup
}
throw copyErr;
}
process.stderr.write(
`[!] codex-auth: symlink unavailable; copied shared config.toml to ${linkPath}. ` +
`Config edits won't propagate automatically.\n`
@@ -143,6 +143,19 @@ describe('ensureSharedConfigSymlink', () => {
expect(stderrChunks.join('')).toContain('symlink unavailable');
});
it('rethrows symlink errors that are not fallback-safe', () => {
fs.writeFileSync(sharedConfigPath, 'model = "gpt-5.5"\n', { mode: 0o600 });
const symlinkSpy = spyOn(fs, 'symlinkSync').mockImplementation(() => {
throw Object.assign(new Error('simulated race'), { code: 'EEXIST' });
});
try {
expect(() => ensureSharedConfigSymlink(profileDir, sharedConfigPath)).toThrow(/simulated race/);
} finally {
symlinkSpy.mockRestore();
}
});
it('preserves edited fallback copies on later repair attempts', () => {
fs.writeFileSync(sharedConfigPath, 'model = "gpt-5.5"\n', { mode: 0o600 });
const linkPath = path.join(profileDir, 'config.toml');