mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-05 10:13:12 +00:00
fix(codex-auth): reject aliased plugin cache paths
This commit is contained in:
1 parent
3103af5355
commit
234dce3150
2 files changed
+60
-1
No files matched your search
@@ -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'
|
||||
|
||||
Reference in new issue
Block a user