mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-23 02:23:38 +00:00
fix(codex-auth): fail closed on registry corruption
This commit is contained in:
@@ -16,6 +16,33 @@ function emptyRegistry(): CodexProfileData {
|
||||
return { version: CODEX_PROFILE_SCHEMA_VERSION, default: null, profiles: {} };
|
||||
}
|
||||
|
||||
export class CodexProfileRegistryReadError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'CodexProfileRegistryReadError';
|
||||
}
|
||||
}
|
||||
|
||||
function validateRegistryData(parsed: unknown): CodexProfileData {
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
throw new Error('registry YAML root is not an object');
|
||||
}
|
||||
|
||||
const data = parsed as Partial<CodexProfileData>;
|
||||
if (!data.profiles || typeof data.profiles !== 'object' || Array.isArray(data.profiles)) {
|
||||
throw new Error('registry YAML is missing an object profiles map');
|
||||
}
|
||||
if (data.default !== undefined && data.default !== null && typeof data.default !== 'string') {
|
||||
throw new Error('registry YAML default must be a string or null');
|
||||
}
|
||||
|
||||
return {
|
||||
version: typeof data.version === 'string' ? data.version : CODEX_PROFILE_SCHEMA_VERSION,
|
||||
default: data.default ?? null,
|
||||
profiles: data.profiles as Record<string, CodexProfileMetadata>,
|
||||
};
|
||||
}
|
||||
|
||||
function sleepSync(ms: number): void {
|
||||
try {
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
||||
@@ -55,18 +82,16 @@ export class CodexProfileRegistry {
|
||||
}
|
||||
try {
|
||||
const raw = fs.readFileSync(this.registryPath, 'utf8');
|
||||
const parsed = yaml.load(raw) as CodexProfileData | null;
|
||||
if (!parsed || typeof parsed !== 'object' || !parsed.profiles) {
|
||||
return emptyRegistry();
|
||||
}
|
||||
return parsed;
|
||||
return validateRegistryData(yaml.load(raw));
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logger.warn(
|
||||
'codex-auth.registry.corrupt',
|
||||
`Corrupt registry at ${this.registryPath}, returning empty state: ${msg}`
|
||||
'codex-auth.registry.read-failed',
|
||||
`Registry at ${this.registryPath} could not be read safely; refusing empty-state rewrite: ${msg}`
|
||||
);
|
||||
throw new CodexProfileRegistryReadError(
|
||||
`Codex profile registry at ${this.registryPath} could not be read safely: ${msg}. Refusing to rewrite it.`
|
||||
);
|
||||
return emptyRegistry();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,13 @@ function registryDisplayPath(registryPath: string): string {
|
||||
return registryPath;
|
||||
}
|
||||
|
||||
function resolutionFailure(message: string, envName: string, displayEnvName: string): never {
|
||||
const prefix = envName ? `CCS_CODEX_PROFILE=${displayEnvName} is set but ` : '';
|
||||
throw new CodexAuthProfileResolutionError(
|
||||
`${prefix}${message}. Refusing to fall back to ~/.codex.`
|
||||
);
|
||||
}
|
||||
|
||||
/** @param env - Process env map; defaults to process.env. Injectable for tests. */
|
||||
export function resolveActiveProfile(env: NodeJS.ProcessEnv = process.env): ResolvedProfile | null {
|
||||
const registryPath = getCodexAuthRegistryPath();
|
||||
@@ -74,28 +81,23 @@ export function resolveActiveProfile(env: NodeJS.ProcessEnv = process.env): Reso
|
||||
const parsed = yaml.load(raw);
|
||||
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
const msg = `registry at ${displayRegistryPath} is not a valid YAML object`;
|
||||
if (envName) {
|
||||
throw new CodexAuthProfileResolutionError(
|
||||
`CCS_CODEX_PROFILE=${displayEnvName} is set but ${msg}. Refusing to fall back to ~/.codex.`
|
||||
);
|
||||
}
|
||||
process.stderr.write(`[!] codex-auth: ${msg}, falling back to ~/.codex\n`);
|
||||
return null;
|
||||
resolutionFailure(msg, envName, displayEnvName);
|
||||
}
|
||||
registry = parsed as RegistryShape;
|
||||
} catch (err) {
|
||||
if (err instanceof CodexAuthProfileResolutionError) throw err;
|
||||
const msg = `registry YAML could not be parsed at ${displayRegistryPath}`;
|
||||
if (envName) {
|
||||
throw new CodexAuthProfileResolutionError(
|
||||
`CCS_CODEX_PROFILE=${displayEnvName} is set but ${msg}. Refusing to fall back to ~/.codex.`
|
||||
);
|
||||
}
|
||||
process.stderr.write(`[!] codex-auth: ${msg}, falling back to ~/.codex\n`);
|
||||
return null;
|
||||
resolutionFailure(msg, envName, displayEnvName);
|
||||
}
|
||||
|
||||
const profiles = registry.profiles ?? {};
|
||||
const profiles = registry.profiles;
|
||||
if (!profiles || typeof profiles !== 'object' || Array.isArray(profiles)) {
|
||||
resolutionFailure(
|
||||
`registry at ${displayRegistryPath} is missing a valid profiles map`,
|
||||
envName,
|
||||
displayEnvName
|
||||
);
|
||||
}
|
||||
|
||||
// F2: explicit env override
|
||||
if (envName) {
|
||||
|
||||
Reference in New Issue
Block a user