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
13 changes: 12 additions & 1 deletion plugins/codex-security/mcp-app/tests/test_deep_scan_executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1457,7 +1457,7 @@ async function testWorkerRuntimeSettings() {
"GIT_SSH_COMMAND",
"GIT_CONFIG_GLOBAL",
"CODEX_CLI_PATH",
"CODEX_MCP_NODE_PATH",
"CODEX_BROWSER_USE_NODE_PATH",
"CODEX_HOME",
"CODEX_SECURITY_STATE_DIR",
"CODEX_SECURITY_CONFIG_PATH",
Expand Down Expand Up @@ -1498,6 +1498,8 @@ async function testWorkerRuntimeSettings() {
{ account: null, requiresOpenaiAuth: false },
process.platform === "win32" && configuration === "",
);
const browserNode = path.join(fixture.root, "browser node");
process.env.CODEX_BROWSER_USE_NODE_PATH = browserNode;
const python = path.join(fixture.root, "selected venv", "bin", "python");
const helperPython = path.join(
fixture.root,
Expand Down Expand Up @@ -1749,6 +1751,10 @@ env_key = "SYNTHETIC_GATEWAY_KEY"`,
? undefined
: {
...workerScanEnvironment(fixture.root, index),
CODEX_BROWSER_USE_NODE_PATH: path.join(
fixture.root,
`scan browser node ${index}`,
),
SYNTHETIC_GATEWAY_KEY: providerKeys[index],
SYNTHETIC_HEADER_VALUE: providerHeaders[index],
[headerKey]: ` synthetic-canonical-header-${index}\t`,
Expand Down Expand Up @@ -2278,6 +2284,11 @@ env_key = "SYNTHETIC_GATEWAY_KEY"`,
nodePaths[index],
);
assert.equal(process.env.CODEX_MCP_NODE_PATH, nodePaths.at(-1));
assert.equal(
invocation.runtimeEnvironment.CODEX_BROWSER_USE_NODE_PATH,
selectedProvider.environment?.CODEX_BROWSER_USE_NODE_PATH ??
browserNode,
);
assert.equal(invocation.providerHeader, providerHeaders[index]);

assert.equal(workerLaunch.environment!.CODEX_API_KEY, undefined);
Expand Down
67 changes: 67 additions & 0 deletions plugins/codex-security/mcp-app/tests/test_node_launcher.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { existsSync } from "node:fs";
import {
copyFile,
mkdir,
Expand Down Expand Up @@ -247,6 +248,72 @@ launcherTest(
},
);

test(
"Linux helpers use a system SQLite fallback without probing the checkout PATH",
{ skip: process.platform !== "linux" },
async () => {
const root = await directories.create("codex-security-linux-launcher-");
const withoutSqlite = path.join(root, "node-without-sqlite");
const marker = path.join(root, "path-node-ran");
await writeFile(
path.join(root, "node"),
`#!/bin/sh\n: > ${shellQuote(marker)}\nexit 0\n`,
{ mode: 0o755 },
);
await writeFile(
withoutSqlite,
`#!/bin/sh\nexec ${shellQuote(process.execPath)} --no-experimental-sqlite "$@"\n`,
{ mode: 0o755 },
);
const launcher = path.join(
pluginRoot,
"scripts",
"launch_codex_security_mcp",
);
const installedNode = path.join(root, "installed-node");
const systemLink = path.join(root, "system Node's link");
await writeFile(
installedNode,
`#!/bin/sh\nexec ${shellQuote(process.execPath)} "$@"\n`,
{ mode: 0o755 },
);
await symlink(installedNode, systemLink);
const source = await readFile(launcher, "utf8");
// Relocate only the fixed system paths so the test can supply a system symlink.
const systemPaths = "/usr/local/bin/node /usr/bin/node /bin/node";
assert.ok(source.includes(systemPaths));
const fixtureLauncher = path.join(root, "scripts", "launch");
await mkdir(path.dirname(fixtureLauncher));
await symlink(path.join(pluginRoot, "mcp"), path.join(root, "mcp"));
await writeFile(
fixtureLauncher,
source.replace(systemPaths, '"$FIXTURE_SYSTEM_NODE"'),
);
const run = (candidate: string) =>
spawnSync("/bin/sh", [fixtureLauncher, "--helper", "database-info"], {
cwd: root,
encoding: "utf8",
input: JSON.stringify(root),
env: {
HOME: root,
PATH: root,
NODE_DEBUG: "module",
CODEX_BROWSER_USE_NODE_PATH: withoutSqlite,
FIXTURE_SYSTEM_NODE: candidate,
},
});
const linked = run(systemLink);
assert.equal(linked.status, 0, linked.stderr);
assert.deepEqual(JSON.parse(linked.stdout), {
databasePath: path.join(root, "workbench.sqlite3"),
});
const missing = run(path.join(root, "missing-node"));
assert.notEqual(missing.status, 0);
assert.match(missing.stderr, /node:sqlite/);
assert.equal(existsSync(marker), false);
},
);

launcherTest(
"compatible managed runtimes retain priority over PATH",
async () => {
Expand Down
32 changes: 25 additions & 7 deletions plugins/codex-security/scripts/launch_codex_security_mcp
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,37 @@ fi

first_node=
can_load_native() {
selected_node=$1
if [ -z "$first_node" ]; then
first_node=$1
fi
# Some managed macOS runtimes enforce library validation and cannot load the
# plugin's addon. Check before starting a server or helper with side effects.
# Database helpers need SQLite. On macOS, library validation can also
# prevent a managed runtime from loading the plugin's addon.
# Capture output without opening a writable file for read-only callers.
_native_probe_output=$("$1" -e '
if (process.platform === "darwin") {
if _native_probe_output=$("$1" -e '
if (process.platform === "linux") {
try { require("node:sqlite"); } catch { process.exitCode = 78; }
} else if (process.platform === "darwin") {
require("node:sqlite");
require(require("node:path").join(process.argv[1], "native",
`${process.platform}-${process.arch}`, "unix.node"));
}
' "$launcher_dir/../mcp" </dev/null 2>&1)
' "$launcher_dir/../mcp" </dev/null 2>&1); then
return 0
elif [ "$?" -ne 78 ]; then
return 1
fi

# This Linux runtime can still run helpers without SQLite. Prefer a system
# installation if available, without expanding discovery into the caller's PATH.
for system_candidate in /usr/local/bin/node /usr/bin/node /bin/node; do
if [ -x "$system_candidate" ] &&
_native_probe_output=$("$system_candidate" -e 'require("node:sqlite")' </dev/null 2>&1); then
selected_node=$system_candidate
break
fi
done
return 0
}

for candidate in \
Expand All @@ -77,7 +95,7 @@ for candidate in \
"$cache_root/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node"
do
if [ -n "$candidate" ] && [ -x "$candidate" ] && can_load_native "$candidate"; then
launch_node "$candidate" "$@"
launch_node "$selected_node" "$@"
fi
done

Expand All @@ -86,7 +104,7 @@ while :; do
path_entry=${remaining_path%%:*}
node_path=${path_entry:-.}/node
if [ -x "$node_path" ] && can_load_native "$node_path"; then
launch_node "$node_path" "$@"
launch_node "$selected_node" "$@"
fi
case "$remaining_path" in
*:*) remaining_path=${remaining_path#*:} ;;
Expand Down
Loading