mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 18:18:21 +00:00
fix(codex-auth): fail closed on registry corruption
This commit is contained in:
1 parent
211e51b949
commit
2cd2d43186
6 files changed
+128
-50
No files matched your search
@@ -215,6 +215,24 @@ describe('codex-runtime router — non-auth profile resolution', () => {
|
||||
expect(stderr).toContain('registry YAML could not be parsed');
|
||||
});
|
||||
|
||||
it('fails fast when registry YAML is corrupt even without CCS_CODEX_PROFILE', async () => {
|
||||
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
|
||||
fs.writeFileSync(registryPath, 'profiles: [unterminated\n');
|
||||
|
||||
const { result: code, stderr } = await withCapturedStderr(async () => {
|
||||
require.cache[ccsPath] = { exports: {} } as NodeJS.Module;
|
||||
flushRouterCache();
|
||||
require.cache[ccsPath] = { exports: {} } as NodeJS.Module;
|
||||
|
||||
const { main } = require(routerPath) as { main: (argv: string[]) => Promise<number> };
|
||||
return main(['node', 'codex-runtime', 'chat']);
|
||||
});
|
||||
|
||||
expect(code).toBe(1);
|
||||
expect(process.env.CODEX_HOME).toBeUndefined();
|
||||
expect(stderr).toContain('registry YAML could not be parsed');
|
||||
});
|
||||
|
||||
it('fails fast when CCS_CODEX_PROFILE is set and registry is not an object', async () => {
|
||||
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
|
||||
fs.writeFileSync(registryPath, '- not\n- an\n- object\n');
|
||||
@@ -256,6 +274,29 @@ describe('codex-runtime router — non-auth profile resolution', () => {
|
||||
expect(stderr).toContain('boundary failure');
|
||||
});
|
||||
|
||||
it('fails closed for unexpected resolver errors instead of falling back to legacy mode', async () => {
|
||||
const { result: code, stderr } = await withCapturedStderr(async () => {
|
||||
require.cache[ccsPath] = { exports: {} } as NodeJS.Module;
|
||||
flushRouterCache();
|
||||
require.cache[ccsPath] = { exports: {} } as NodeJS.Module;
|
||||
require.cache[resolveProfilePath] = {
|
||||
exports: {
|
||||
resolveActiveProfile: () => {
|
||||
throw new Error('permission denied');
|
||||
},
|
||||
},
|
||||
} as NodeJS.Module;
|
||||
|
||||
const { main } = require(routerPath) as { main: (argv: string[]) => Promise<number> };
|
||||
return main(['node', 'codex-runtime', 'chat']);
|
||||
});
|
||||
|
||||
expect(code).toBe(1);
|
||||
expect(process.env.CODEX_HOME).toBeUndefined();
|
||||
expect(stderr).toContain('profile resolution failed');
|
||||
expect(stderr).toContain('permission denied');
|
||||
});
|
||||
|
||||
it('preserves an explicit CODEX_HOME already in env — does not overwrite', async () => {
|
||||
const explicitHome = path.join(tempDir, 'explicit-codex-home');
|
||||
fs.mkdirSync(explicitHome, { recursive: true });
|
||||
|
||||
@@ -156,13 +156,24 @@ describe('CodexProfileRegistry — listProfiles', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('CodexProfileRegistry — corrupt YAML recovery', () => {
|
||||
it('returns empty state on corrupt YAML without throwing', () => {
|
||||
describe('CodexProfileRegistry — corrupt YAML safety', () => {
|
||||
it('throws on corrupt YAML without rewriting the registry', () => {
|
||||
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
|
||||
fs.writeFileSync(registryPath, '{ invalid: yaml: content: [', { mode: 0o600 });
|
||||
const corrupt = '{ invalid: yaml: content: [';
|
||||
fs.writeFileSync(registryPath, corrupt, { mode: 0o600 });
|
||||
const reg = new CodexProfileRegistry(registryPath);
|
||||
expect(reg.listProfiles()).toEqual([]);
|
||||
expect(reg.getDefault()).toBeNull();
|
||||
expect(() => reg.listProfiles()).toThrow(/could not be read safely/i);
|
||||
expect(fs.readFileSync(registryPath, 'utf8')).toBe(corrupt);
|
||||
});
|
||||
|
||||
it('refuses mutating writes when the registry shape is invalid', () => {
|
||||
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
|
||||
const invalidShape = 'version: "1.0"\ndefault: null\nprofiles: []\n';
|
||||
fs.writeFileSync(registryPath, invalidShape, { mode: 0o600 });
|
||||
const reg = new CodexProfileRegistry(registryPath);
|
||||
|
||||
expect(() => reg.createProfile('work')).toThrow(/profiles map/i);
|
||||
expect(fs.readFileSync(registryPath, 'utf8')).toBe(invalidShape);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, spyOn } from 'bun:test';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
@@ -57,28 +57,18 @@ describe('resolveActiveProfile', () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null and warns to stderr when registry YAML is corrupt', () => {
|
||||
it('throws when registry YAML is corrupt even without an env override', () => {
|
||||
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
|
||||
fs.writeFileSync(registryPath, '{ invalid yaml: [[[', { mode: 0o600 });
|
||||
|
||||
const stderrMessages: string[] = [];
|
||||
const origWrite = process.stderr.write.bind(process.stderr);
|
||||
const spy = spyOn(process.stderr, 'write').mockImplementation(
|
||||
(msg: string | Uint8Array, ...rest: unknown[]) => {
|
||||
stderrMessages.push(typeof msg === 'string' ? msg : String(msg));
|
||||
return origWrite(msg as string, ...(rest as Parameters<typeof origWrite>).slice(1));
|
||||
}
|
||||
);
|
||||
|
||||
const result = resolveActiveProfile({});
|
||||
|
||||
spy.mockRestore();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(stderrMessages.some((m) => m.includes('codex-auth'))).toBe(true);
|
||||
expect(stderrMessages.join('')).toContain('$CCS_HOME/.ccs/codex-profiles.yaml');
|
||||
expect(stderrMessages.join('')).not.toContain(registryPath);
|
||||
expect(stderrMessages.join('')).not.toContain('invalid yaml');
|
||||
expect(() => resolveActiveProfile({})).toThrow(/registry YAML could not be parsed/);
|
||||
try {
|
||||
resolveActiveProfile({});
|
||||
} catch (err) {
|
||||
expect(String(err)).toContain('$CCS_HOME/.ccs/codex-profiles.yaml');
|
||||
expect(String(err)).not.toContain(registryPath);
|
||||
expect(String(err)).not.toContain('invalid yaml');
|
||||
}
|
||||
});
|
||||
|
||||
it('throws when CCS_CODEX_PROFILE is set and registry YAML is corrupt', () => {
|
||||
@@ -99,6 +89,15 @@ describe('resolveActiveProfile', () => {
|
||||
expect(String(thrown)).not.toContain('invalid yaml');
|
||||
});
|
||||
|
||||
it('throws when registry is missing a valid profiles map', () => {
|
||||
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
|
||||
fs.writeFileSync(registryPath, 'version: "1.0"\ndefault: null\nprofiles: []\n', {
|
||||
mode: 0o600,
|
||||
});
|
||||
|
||||
expect(() => resolveActiveProfile({})).toThrow(/valid profiles map/);
|
||||
});
|
||||
|
||||
it('returns source=env when CCS_CODEX_PROFILE matches a registry entry', () => {
|
||||
const profileDir = makeProfileDir('work');
|
||||
writeRegistry({
|
||||
|
||||
Reference in new issue
Block a user