Skip to content
Open
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
Binary file modified sdk/typescript/_bundled_plugin/mcp/server.mjs.br.part-000
Binary file not shown.
Binary file modified sdk/typescript/_bundled_plugin/mcp/server.mjs.br.part-001
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ When the profile includes remediation patches, present the concrete config delta

Some remediation patches have `kind = "host_setting"`. Present those as host-level setup guidance, not as edits to persistent Codex config.

Deep Security Scan uses MCP-owned SDK sessions rather than the parent thread's worker pool. Its preflight does not require a particular parent delegation runtime, ownership, capacity, or depth. Discovery workers inherit the scan's model and run under the verified read-only worker sandbox.
Deep Security Scan uses MCP-owned SDK sessions rather than the parent thread's worker pool. Its preflight does not require a particular parent delegation runtime, ownership, capacity, or depth. Discovery workers inherit the scan's model and use the reserved `codex_security_deep_scan_worker` permission profile with the parent's supported filesystem denials. The selected Codex executable must support permission-profile configuration and allowance checks. If that command fails to start or exits early, report its path and the tool's diagnostic; do not infer that its version is unsupported. If the tool identifies a missing API, ask the user to update the Codex installation at the reported path: the desktop app for an app-bundled executable, or the selected CLI otherwise. If Codex policy rejects the profile, report the tool's administrator guidance. Do not remove deny rules or select a broader sandbox to work around the error.
Comment thread
ianw-oai marked this conversation as resolved.
Comment thread
ianw-oai marked this conversation as resolved.

Do not warn merely because a user's value differs from the profile's suggested patch. Warn or block only when the evaluated capability requirement is unmet.

Expand Down
131 changes: 131 additions & 0 deletions sdk/typescript/tests-ts/deep-scan-parent-denials.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import * as path from "node:path";
import * as url from "node:url";
import * as util from "node:util";
import { expect, test } from "bun:test";
import { parse } from "smol-toml";
import { loadBundledRuntime } from "./plugin-root.js";

type Sandbox = { filesystemDenies: string[]; globScanMaxDepth?: number };

async function bundledPolicy() {
const runtime = await loadBundledRuntime();
const start = runtime.indexOf("var CODEX_SANDBOX_STATE_META_CAPABILITY =");
const end = runtime.indexOf("\n// ", start);
expect(start).toBeGreaterThan(0);
expect(end).toBeGreaterThan(start);
const source = runtime.slice(start, end);
const imports = [
...new Set(source.match(/import_node_(?:path|url|util)\d*/gu)),
];
const resolve = new Function(
...imports,
"DeepScanNonRetryableError",
`${source}\nreturn resolveDeepWorkerParentSandbox;`,
)(
...imports.map((name) =>
name.startsWith("import_node_path")
? path
: name.startsWith("import_node_url")
? url
: util,
),
Error,
) as (metadata: unknown) => Sandbox;
const serializer = [
"workerPermissionProfile",
"workerPermissionProfileConfigOverrides",
"tomlInlineValue",
"tomlKey",
"tomlString",
]
.map((name) => {
const definition = new RegExp(
`function ${name}\\([^\\n]*\\) \\{[\\s\\S]*?\\n\\}`,
"u",
).exec(runtime)?.[0];
if (!definition) throw new Error(`Missing bundled function: ${name}`);
return definition;
})
.join("\n");
const overrides = new Function(
"DEEP_SCAN_WORKER_PERMISSION_PROFILE_ID",
`${serializer}\nreturn sandbox => workerPermissionProfileConfigOverrides(workerPermissionProfile(sandbox));`,
)("codex_security_deep_scan_worker") as (sandbox: Sandbox) => string[];
return { resolve, overrides };
}

function metadata(entries: unknown[]) {
return {
_meta: {
"codex/sandbox-state-meta": {
permissionProfile: {
type: "managed",
network: "enabled",
file_system: {
type: "restricted",
glob_scan_max_depth: 8,
entries: [
{
access: "read",
path: { type: "special", value: { kind: "root" } },
},
...entries,
],
},
},
},
},
};
}

test("preserves literal parent deny paths and globs without parent write grants", async () => {
const policy = await bundledPolicy();
const denied = path.resolve("synthetic", "secret.with.dots");
const glob = path.resolve("synthetic", "**", "*.secret");
const sandbox = policy.resolve(
metadata([
{
access: "write",
path: { type: "path", path: path.resolve("synthetic") },
},
{ access: "deny", path: { type: "path", path: denied } },
{ access: "none", path: { type: "glob_pattern", pattern: glob } },
]),
);
expect(sandbox).toEqual({
filesystemDenies: [denied, glob],
globScanMaxDepth: 8,
});
expect(parse(policy.overrides(sandbox).join("\n"))).toEqual({
default_permissions: "codex_security_deep_scan_worker",
permissions: {
codex_security_deep_scan_worker: {
extends: ":read-only",
filesystem: {
":root": "read",
[denied]: "deny",
[glob]: "deny",
glob_scan_max_depth: 8,
},
network: { enabled: false },
},
},
});
});

test("rejects parent denials that cannot be preserved", async () => {
const policy = await bundledPolicy();
for (const entry of [
{ access: "deny", path: { type: "path", path: "relative/secret" } },
{ access: "deny", path: { type: "special", value: { kind: "tmpdir" } } },
{
access: "write",
path: { type: "glob_pattern", pattern: path.resolve("synthetic", "*") },
},
]) {
expect(() => policy.resolve(metadata([entry]))).toThrow(
"cannot be preserved",
);
}
expect(() => policy.resolve({})).toThrow("trusted parent sandbox metadata");
});
120 changes: 120 additions & 0 deletions sdk/typescript/tests-ts/deep-scan-windows-executable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as fs from "node:fs";
import {
mkdir,
mkdtemp,
realpath,
rm,
utimes,
writeFile,
} from "node:fs/promises";
import * as module from "node:module";
import { tmpdir } from "node:os";
import * as path from "node:path";
import { expect, test } from "bun:test";
import { loadBundledRuntime } from "./plugin-root.js";

async function bundledResolver() {
const runtime = await loadBundledRuntime();
const start = runtime.indexOf("function resolveCodexPath(");
const end = runtime.indexOf("\n// server.ts", start);
expect(start).toBeGreaterThan(0);
expect(end).toBeGreaterThan(start);
const source = runtime.slice(start, end);
const imports = [
...new Set(source.match(/import_node_(?:fs|path|module)\d*/gu)),
];
return new Function(...imports, `${source}\nreturn resolveCodexPath;`)(
...imports.map((name) =>
name.startsWith("import_node_fs")
? fs
: name.startsWith("import_node_path")
? path
: module,
),
) as (
env: NodeJS.ProcessEnv,
platform: NodeJS.Platform,
architecture: NodeJS.Architecture,
) => string;
}

test("uses the newest relocated Windows executable when WindowsApps is inaccessible", async () => {
const root = await realpath(
await mkdtemp(path.join(tmpdir(), "codex-security-executable-")),
);
try {
const older = path.join(
root,
"OpenAI",
"Codex",
"bin",
"11111111",
"codex.exe",
);
const newer = path.join(
root,
"OpenAI",
"Codex",
"bin",
"22222222",
"codex.exe",
);
const empty = path.join(
root,
"OpenAI",
"Codex",
"bin",
"33333333",
"codex.exe",
);
for (const executable of [older, newer, empty]) {
await mkdir(path.dirname(executable), { recursive: true });
await writeFile(
executable,
executable === empty ? "" : "synthetic executable",
);
}
await utimes(older, 1, 1);
await utimes(newer, 2, 2);
const resolve = await bundledResolver();
const environment = {
PATH: "",
LOCALAPPDATA: root,
CODEX_CLI_PATH: "C:\\Program Files\\WindowsApps\\Codex\\codex.exe",
};
expect(resolve(environment, "win32", "x64")).toBe(newer);
expect(
resolve(
{ ...environment, CODEX_CLI_PATH: "C:\\Tools\\codex.exe" },
"win32",
"x64",
),
).toBe("C:\\Tools\\codex.exe");
expect(resolve({ PATH: "" }, "win32", "x64")).toBe(
path.resolve("codex.exe"),
);
expect(resolve({}, "linux", "x64")).toBe(path.resolve("codex"));
} finally {
await rm(root, { recursive: true, force: true });
}
});

test("does not retry Windows executable permission failures", async () => {
const runtime = await loadBundledRuntime();
const source =
/function classifyCodexWorkerError\([^\n]*\) \{[\s\S]*?\n\}/u.exec(
runtime,
)?.[0];
expect(source).toBeDefined();
class NonRetryableError extends Error {}
const classify = new Function(
"DeepScanNonRetryableError",
`${source}\nreturn classifyCodexWorkerError;`,
)(NonRetryableError) as (error: Error) => Error;
const original = Object.assign(new Error("spawn codex EPERM"), {
code: "EPERM",
});
const result = classify(original);
expect(result).toBeInstanceOf(NonRetryableError);
expect(result.cause).toBe(original);
});
36 changes: 32 additions & 4 deletions sdk/typescript/tests-ts/deep-scan-worker-shutdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type WorkerEvent =
| { type: "turn.failed"; error: { message: string } };

type WorkerExecutorConstructor = new (settings: {
parentSandbox: { filesystem: "workspace-write"; network: "restricted" };
parentSandbox: { filesystemDenies: string[] };
}) => {
run(request: {
kind: "discovery";
Expand All @@ -22,6 +22,7 @@ type WorkerExecutorConstructor = new (settings: {

async function bundledWorkerExecutor(
events: (signal: AbortSignal) => AsyncGenerator<WorkerEvent>,
preflight = async () => {},
): Promise<WorkerExecutorConstructor> {
const runtime = await loadBundledRuntime();
const source = /var CodexSdkWorkerExecutor = class \{[\s\S]*?\n\};/u.exec(
Expand Down Expand Up @@ -49,7 +50,12 @@ async function bundledWorkerExecutor(
return new Function(
"Codex",
fileSystemImport!,
"assertVerifiedParentSandbox",
"workerPermissionProfile",
"workerPermissionProfileConfigOverrides",
"snapshotWorkerEnvironment",
"preflightDeepScanWorkerPermissionProfile",
"DEEP_SCAN_WORKER_PERMISSION_PROFILE_ID",
"deepScanPermissionProfileFallbackError",
"resolveCodexPath",
"workerSubagentConfig",
"appendSafeItemDiagnostic",
Expand All @@ -58,7 +64,12 @@ async function bundledWorkerExecutor(
)(
FakeCodex,
{ promises: { readFile: async () => "fixture worker prompt" } },
() => {},
() => ({}),
() => [],
async () => ({}),
preflight,
"codex_security_deep_scan_worker",
() => undefined,
() => "/fixture/codex",
() => ({}),
() => {},
Expand All @@ -72,7 +83,7 @@ function runWorker(
onThreadStarted?: () => void,
) {
return new WorkerExecutor({
parentSandbox: { filesystem: "workspace-write", network: "restricted" },
parentSandbox: { filesystemDenies: [] },
}).run({
kind: "discovery",
promptPath: "/fixture/prompt.md",
Expand All @@ -83,6 +94,23 @@ function runWorker(
});
}

test("does not start a bundled worker when its permission profile check fails", async () => {
let started = false;
const WorkerExecutor = await bundledWorkerExecutor(
async function* () {
started = true;
yield { type: "turn.completed" };
},
async () => {
throw new Error("worker permission profile rejected");
},
);
await expect(
runWorker(WorkerExecutor, new AbortController().signal),
).rejects.toThrow("worker permission profile rejected");
expect(started).toBe(false);
});

test("settles completed bundled Deep Scan workers during coordinator cancellation", async () => {
const parentController = new AbortController();
let workerSignal: AbortSignal | undefined;
Expand Down
Loading