fix(codex-auth): reject aliased plugin cache paths

This commit is contained in:
Tam Nhu Tran committed 2026-07-15 11:36:15 -04:00
1 parent 3103af5355
commit 234dce3150
2 files changed
+60 -1

No files matched your search

+7 -1
View File
@@ -1,7 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
import { ConfigError } from '../errors/error-types';
import { symlinkPointsTo } from '../management/shared-manager/fs-helpers';
import { resolveCanonicalPath, symlinkPointsTo } from '../management/shared-manager/fs-helpers';
import { createLogger } from '../services/logging';
const logger = createLogger('codex-auth:resources');
@@ -15,6 +15,12 @@ export function ensureSharedPluginCache(profileDir: string, sharedCodexHome: str
ensureProfileLocalPluginsDirectory(pluginsPath);
fs.mkdirSync(targetPath, { recursive: true, mode: 0o700 });
if (resolveCanonicalPath(pluginsPath) === resolveCanonicalPath(path.dirname(targetPath))) {
throw new ConfigError(
'Refusing plugin cache repair: profile plugins directory resolves to the shared plugins directory.'
);
}
const existingStat = lstatIfExists(linkPath);
if (isExpectedCacheLink(linkPath, targetPath, existingStat)) {
return;
@@ -110,6 +110,59 @@ describe('ensureCodexProfileResources', () => {
expect(fs.readFileSync(sharedSkillPath, 'utf8')).toBe('# Current shared skill\n');
});
it('refuses a profile root that resolves to the shared Codex home without changing its cache', async () => {
const { ensureCodexProfileResources } = await import(
'../../../src/codex-auth/codex-profile-resources'
);
const sharedPluginsDir = path.join(sharedCodexHome, 'plugins');
const sharedCacheDir = path.join(sharedPluginsDir, 'cache');
const sharedMarkerPath = path.join(sharedCacheDir, 'shared-marker.txt');
fs.writeFileSync(sharedMarkerPath, 'preserve shared cache\n');
const cacheInodeBeforeRepair = fs.lstatSync(sharedCacheDir).ino;
fs.symlinkSync(sharedCodexHome, profileDir, 'dir');
expect(() => ensureCodexProfileResources(profileDir, { sharedCodexHome })).toThrow(
'profile plugins directory resolves to the shared plugins directory'
);
expect(fs.lstatSync(profileDir).isSymbolicLink()).toBe(true);
expect(fs.lstatSync(sharedCacheDir).isDirectory()).toBe(true);
expect(fs.lstatSync(sharedCacheDir).isSymbolicLink()).toBe(false);
expect(fs.lstatSync(sharedCacheDir).ino).toBe(cacheInodeBeforeRepair);
expect(fs.readFileSync(sharedMarkerPath, 'utf8')).toBe('preserve shared cache\n');
expect(
fs.readdirSync(sharedPluginsDir).some((name) => name.startsWith('.cache.ccs-backup-'))
).toBe(false);
});
it('refuses a shared plugins directory that resolves to the profile plugins directory', async () => {
const { ensureCodexProfileResources } = await import(
'../../../src/codex-auth/codex-profile-resources'
);
const profilePluginsDir = path.join(profileDir, 'plugins');
const profileCacheDir = path.join(profilePluginsDir, 'cache');
const profileMarkerPath = path.join(profileCacheDir, 'profile-marker.txt');
const sharedPluginsDir = path.join(sharedCodexHome, 'plugins');
fs.mkdirSync(profileCacheDir, { recursive: true, mode: 0o700 });
fs.writeFileSync(profileMarkerPath, 'preserve profile cache\n');
const cacheInodeBeforeRepair = fs.lstatSync(profileCacheDir).ino;
fs.rmSync(sharedPluginsDir, { recursive: true, force: true });
fs.symlinkSync(profilePluginsDir, sharedPluginsDir, 'dir');
expect(() => ensureCodexProfileResources(profileDir, { sharedCodexHome })).toThrow(
'profile plugins directory resolves to the shared plugins directory'
);
expect(fs.lstatSync(sharedPluginsDir).isSymbolicLink()).toBe(true);
expect(fs.lstatSync(profileCacheDir).isDirectory()).toBe(true);
expect(fs.lstatSync(profileCacheDir).isSymbolicLink()).toBe(false);
expect(fs.lstatSync(profileCacheDir).ino).toBe(cacheInodeBeforeRepair);
expect(fs.readFileSync(profileMarkerPath, 'utf8')).toBe('preserve profile cache\n');
expect(
fs.readdirSync(profilePluginsDir).some((name) => name.startsWith('.cache.ccs-backup-'))
).toBe(false);
});
it('keeps the plugin cache projection stable across repeated repairs', async () => {
const { ensureCodexProfileResources } = await import(
'../../../src/codex-auth/codex-profile-resources'