From fb848727ca779aa5d798870c0a5102798ce4e6be Mon Sep 17 00:00:00 2001 From: GautamSharma99 Date: Thu, 30 Jul 2026 19:35:44 +0530 Subject: [PATCH] fix: prioritize active preflight config --- sdk/typescript/src/api.ts | 56 ++++++++++++--- sdk/typescript/tests-ts/api.test.ts | 102 ++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 9 deletions(-) diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index d2381d3d..1903c977 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -397,6 +397,14 @@ export class CodexSecurity { ) { await this.#refreshPersistentRuntime(runtime, scanEnvironment, signal); } + const effectiveConfig = + runtime.effectiveConfig ?? (await mergedCodexConfig(this.config)); + if (runtime.configPath !== undefined) { + await writeCodexConfig( + runtime.configPath, + scanPreflightCodexConfig(effectiveConfig, repo), + ); + } const runtimeHome = await realpath(runtime.codexHome); requireOutputOutsideRepository(protectedRoot, runtimeHome, "runtime"); if ( @@ -533,8 +541,6 @@ export class CodexSecurity { mode, pluginVersion: runtime.plugin.version, }; - const effectiveConfig = - runtime.effectiveConfig ?? (await mergedCodexConfig(this.config)); const { model } = scanModelConfiguration(effectiveConfig); validateScanCostLimit(options.maxCostUsd, model); const tracker = new ScanCostTracker({ @@ -1578,7 +1584,7 @@ function scanRecipe( mode, ...(repositoryRevision === null ? {} : { repositoryRevision }), pluginVersion, - config: scanPreflightCodexConfig(effectiveConfig), + config: scanPreflightCodexConfig(effectiveConfig, repository), ...(failOnSeverity === undefined ? {} : { failOnSeverity }), ...(knowledgeBasePaths === undefined ? {} : { knowledgeBasePaths }), ...(maxCostUsd === undefined ? {} : { maxCostUsd }), @@ -1833,7 +1839,10 @@ export function scanRuntimeCodexConfig( }; } -export function scanPreflightCodexConfig(config: JsonObject): JsonObject { +export function scanPreflightCodexConfig( + config: JsonObject, + activeProjectPath?: string, +): JsonObject { const safeString = (value: unknown, maxLength: number): value is string => typeof value === "string" && value.length > 0 && @@ -1903,18 +1912,41 @@ export function scanPreflightCodexConfig(config: JsonObject): JsonObject { } return result; }; + const prioritizedEntries = ( + value: Record, + priority: string | undefined, + ): [string, unknown][] => { + const entries = Object.entries(value); + if (priority === undefined || !Object.hasOwn(value, priority)) { + return entries; + } + return [ + [priority, value[priority]], + ...entries.filter(([key]) => key !== priority), + ]; + }; const result = executionConfig(config); - if (safeProfileName(config["profile"])) { - result["profile"] = config["profile"]; + const selectedProfile = safeProfileName(config["profile"]) + ? config["profile"] + : undefined; + if (selectedProfile !== undefined) { + result["profile"] = selectedProfile; } const profiles = config["profiles"]; if (isRecord(profiles)) { const sanitized: JsonObject = {}; - for (const [name, profile] of Object.entries(profiles).slice(0, 256)) { + let accepted = 0; + for (const [name, profile] of prioritizedEntries( + profiles, + selectedProfile, + )) { if (!safeProfileName(name) || !isRecord(profile)) continue; const projected = executionConfig(profile as JsonObject); - if (Object.keys(projected).length > 0) sanitized[name] = projected; + if (Object.keys(projected).length === 0) continue; + sanitized[name] = projected; + accepted += 1; + if (accepted === 256) break; } if (Object.keys(sanitized).length > 0) result["profiles"] = sanitized; } @@ -1927,13 +1959,19 @@ export function scanPreflightCodexConfig(config: JsonObject): JsonObject { const projects = config["projects"]; if (isRecord(projects)) { const sanitized: JsonObject = {}; - for (const [path, project] of Object.entries(projects).slice(0, 256)) { + let accepted = 0; + for (const [path, project] of prioritizedEntries( + projects, + activeProjectPath, + )) { if (!safeString(path, 4096) || !isAbsolute(path) || !isRecord(project)) { continue; } const trust = project["trust_level"]; if (trust !== "trusted" && trust !== "untrusted") continue; sanitized[path] = { trust_level: trust }; + accepted += 1; + if (accepted === 256) break; } if (Object.keys(sanitized).length > 0) result["projects"] = sanitized; } diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index 13e73152..5c82d6bc 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -495,6 +495,93 @@ describe("CodexSecurity orchestration", () => { ).resolves.toBeUndefined(); }); + test("prioritizes the selected profile and active project before projection limits", () => { + const activeProject = "/workspace/active"; + const profiles = Object.fromEntries([ + ...Array.from({ length: 256 }, (_, index) => [ + `profile_${index}`, + { features: { goals: index % 2 === 0 } }, + ]), + ["selected", { agents: { max_threads: 17 } }], + ]); + const projects = Object.fromEntries([ + ...Array.from({ length: 256 }, (_, index) => [ + `/workspace/project-${index}`, + { trust_level: "untrusted" }, + ]), + [activeProject, { trust_level: "trusted" }], + ]); + + const prioritized = scanPreflightCodexConfig( + { + profile: "selected", + profiles, + projects, + }, + activeProject, + ); + + expect(prioritized["profile"]).toBe("selected"); + expect(Object.keys(prioritized["profiles"] as JsonObject)).toHaveLength( + 256, + ); + expect(prioritized["profiles"]).toMatchObject({ + selected: { agents: { max_threads: 17 } }, + }); + expect(Object.keys(prioritized["projects"] as JsonObject)).toHaveLength( + 256, + ); + expect(prioritized["projects"]).toMatchObject({ + [activeProject]: { trust_level: "trusted" }, + }); + + const validProfiles = Object.fromEntries( + Array.from({ length: 256 }, (_, index) => [ + `valid_${index}`, + { features: { goals: true } }, + ]), + ); + const validProjects = Object.fromEntries( + Array.from({ length: 256 }, (_, index) => [ + `/valid/project-${index}`, + { trust_level: "trusted" }, + ]), + ); + const afterInvalid = scanPreflightCodexConfig({ + profiles: { + ...Object.fromEntries( + Array.from({ length: 256 }, (_, index) => [ + `invalid.profile.${index}`, + { features: { goals: false } }, + ]), + ), + ...validProfiles, + }, + projects: { + ...Object.fromEntries( + Array.from({ length: 256 }, (_, index) => [ + `relative-${index}`, + { trust_level: "trusted" }, + ]), + ), + ...validProjects, + }, + }); + + expect(Object.keys(afterInvalid["profiles"] as JsonObject)).toHaveLength( + 256, + ); + expect(afterInvalid["profiles"]).toMatchObject({ + valid_255: { features: { goals: true } }, + }); + expect(Object.keys(afterInvalid["projects"] as JsonObject)).toHaveLength( + 256, + ); + expect(afterInvalid["projects"]).toMatchObject({ + "/valid/project-255": { trust_level: "trusted" }, + }); + }); + test("selects a real-scan target in the active repository layout", async () => { await expect( stat(join(REPOSITORY_ROOT, INTEGRATION_TARGET)), @@ -2268,11 +2355,21 @@ describe("CodexSecurity orchestration", () => { expect(interpreter).not.toBeNull(); let capturedConfigPath: string | undefined; let capturedCodexHome: string | undefined; + const unrelatedProjects = Object.fromEntries( + Array.from({ length: 256 }, (_, index) => [ + join(root, `unrelated-project-${index}`), + { trust_level: "untrusted" }, + ]), + ); const client = new TestClient( { pluginPath: PLUGIN_ROOT, codexOverrides: { features: { goals: true }, + projects: { + ...unrelatedProjects, + [repository]: { trust_level: "trusted" }, + }, mcp_servers: { private: { command: "echo", @@ -2331,6 +2428,11 @@ describe("CodexSecurity orchestration", () => { expect(serialized).not.toContain("RUNTIME_SHELL_SECRET"); expect(serialized).not.toContain("mcp_servers"); expect(serialized).not.toContain("shell_environment_policy"); + expect(parseToml(serialized)).toMatchObject({ + projects: { + [repository]: { trust_level: "trusted" }, + }, + }); expect(input).toContain('--config "$CODEX_SECURITY_CONFIG_PATH"'); expect(input).toContain("--effective-config"); const shellEnvironment = options.env as Record;