Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions sdk/typescript/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1428,9 +1428,15 @@ async function prepareDeepScanConfig(
const value = options[name];
if (value !== undefined) overrides[key] = value;
}
if (existing === undefined && Object.keys(overrides).length === 0) return;
const destination = join(codexHome, "codex-security", "config.toml");
if (destination === source && Object.keys(overrides).length === 0) return;
const hasOverrides = Object.keys(overrides).length > 0;
if (existing === undefined && !hasOverrides) {
if (destination !== source) {
Comment thread
mldangelo-oai marked this conversation as resolved.
await rm(destination, { force: true });
}
Comment thread
mldangelo-oai marked this conversation as resolved.
return;
}
if (destination === source && !hasOverrides) return;
await mkdir(dirname(destination), { recursive: true, mode: 0o700 });
await writeFile(
destination,
Expand Down
125 changes: 125 additions & 0 deletions sdk/typescript/tests-ts/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1865,6 +1865,131 @@ describe("CodexSecurity orchestration", () => {
await client.close();
});

test.each([
"removed",
"without deep settings",
...(process.platform === "win32"
? []
: [
"without deep settings and a dangling runtime link",
"without deep settings and a cyclic runtime link",
]),
])(
"clears stale runtime deep-scan configuration when ambient settings are %s",
async (ambientState) => {
const root = await temporaryDirectory();
const repository = join(root, "repository");
const ambientHome = join(root, "ambient-home");
const runtimeHome = join(root, "runtime-home");
const scanDir = join(root, "scan");
const ambientConfig = join(ambientHome, "codex-security", "config.toml");
const runtimeConfig = join(runtimeHome, "codex-security", "config.toml");
const escapedConfig = join(root, "escaped-config.toml");
await mkdir(repository);
await mkdir(join(ambientHome, "codex-security"), { recursive: true });
await mkdir(runtimeHome);
await mkdir(scanDir, { mode: 0o700 });
await writeFile(
ambientConfig,
"[deep_scan]\nworkers = 5\n[other]\nenabled = true\n",
);

const client = new TestClient(
{},
{
environment: { CODEX_HOME: ambientHome },
prepareRuntime: async () => preparedRuntime(runtimeHome),
resolvePluginPython: async () => "/managed/python",
prepareOutputDir: async () => scanDir,
repositoryRevision: async () => "deadbeef",
createCodex: () => ({
startThread: () => ({
id: null,
async runStreamed() {
throw new Error("deep scan settings captured");
},
}),
}),
},
);

await expect(
client.run(repository, { mode: "deep", workers: 2 }),
).rejects.toThrow("deep scan settings captured");
expect(await readFile(runtimeConfig, "utf8")).toContain("workers = 2");

if (ambientState === "removed") {
await rm(ambientConfig);
} else {
await writeFile(ambientConfig, "[other]\nenabled = true\n");
}
if (
ambientState === "without deep settings and a dangling runtime link"
) {
await rm(runtimeConfig);
await symlink(escapedConfig, runtimeConfig);
} else if (
ambientState === "without deep settings and a cyclic runtime link"
) {
await rm(runtimeConfig);
await symlink(runtimeConfig, runtimeConfig);
}

await expect(client.run(repository, { mode: "deep" })).rejects.toThrow(
"deep scan settings captured",
);
await expect(fsPromises.lstat(runtimeConfig)).rejects.toMatchObject({
code: "ENOENT",
});

await writeFile(ambientConfig, "[deep_scan]\nworkers = 7\n");
await expect(client.run(repository, { mode: "deep" })).rejects.toThrow(
"deep scan settings captured",
);
expect(await readFile(runtimeConfig, "utf8")).toContain("workers = 7");
expect(existsSync(escapedConfig)).toBe(false);
await client.close();
},
);

test("preserves ambient configuration when the deep-scan runtime uses the same home", async () => {
const root = await temporaryDirectory();
const repository = join(root, "repository");
const codexHome = join(root, "codex-home");
const scanDir = join(root, "scan");
const configPath = join(codexHome, "codex-security", "config.toml");
const originalConfiguration = "[other]\nenabled = true\n";
await mkdir(repository);
await mkdir(join(codexHome, "codex-security"), { recursive: true });
await writeFile(configPath, originalConfiguration);
await mkdir(scanDir, { mode: 0o700 });

const client = new TestClient(
{},
{
environment: { CODEX_HOME: codexHome },
prepareRuntime: async () => preparedRuntime(codexHome),
resolvePluginPython: async () => "/managed/python",
prepareOutputDir: async () => scanDir,
repositoryRevision: async () => "deadbeef",
createCodex: () => ({
startThread: () => ({
id: null,
async runStreamed() {
throw new Error("deep scan settings captured");
},
}),
}),
},
);

await expect(client.run(repository, { mode: "deep" })).rejects.toThrow(
"deep scan settings captured",
);
expect(await readFile(configPath, "utf8")).toBe(originalConfiguration);
await client.close();
});

test("rejects a scan registration without an authoritative target contract", async () => {
const root = await temporaryDirectory();
const repository = join(root, "repository");
Expand Down
Loading