Skip to content

Commit b7aea9d

Browse files
committed
fix: harden codex wrapper fallbacks
1 parent 538f89b commit b7aea9d

3 files changed

Lines changed: 38 additions & 3 deletions

File tree

scripts/codex-bin-resolver.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,14 @@ export function resolveRealCodexBin(options = {}) {
7575
encoding: "utf8",
7676
env,
7777
stdio: ["ignore", "pipe", "ignore"],
78+
timeout: 5000,
7879
windowsHide: true,
7980
})
8081
: spawnSyncImpl("npm", ["root", "-g"], {
8182
encoding: "utf8",
8283
env,
8384
stdio: ["ignore", "pipe", "ignore"],
85+
timeout: 5000,
8486
});
8587
if (rootResult.status === 0) {
8688
const globalRoot = rootResult.stdout.trim();

scripts/codex.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { spawn } from "node:child_process";
44
import {
5+
chmodSync,
56
copyFileSync,
67
existsSync,
78
mkdirSync,
@@ -510,12 +511,23 @@ function createCompatibilityCodexHome(rawArgs, baseEnv = process.env) {
510511
// Best-effort cleanup only.
511512
}
512513
};
514+
const tightenShadowHomePermissions = (path) => {
515+
try {
516+
chmodSync(path, 0o600);
517+
} catch {
518+
// Best-effort only; permission semantics vary by platform.
519+
}
520+
};
513521
try {
514-
writeFileSync(join(shadowCodexHome, "config.toml"), compatConfig, "utf8");
522+
const compatConfigPath = join(shadowCodexHome, "config.toml");
523+
writeFileSync(compatConfigPath, compatConfig, "utf8");
524+
tightenShadowHomePermissions(compatConfigPath);
515525
for (const name of ["auth.json", "accounts.json", ".codex-global-state.json"]) {
516526
const sourcePath = join(originalCodexHome, name);
517527
if (existsSync(sourcePath)) {
518-
copyFileSync(sourcePath, join(shadowCodexHome, name));
528+
const destinationPath = join(shadowCodexHome, name);
529+
copyFileSync(sourcePath, destinationPath);
530+
tightenShadowHomePermissions(destinationPath);
519531
}
520532
}
521533
} catch (error) {

test/codex-bin-wrapper.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,13 @@ describe("codex bin wrapper", () => {
347347
'const configPath = path.join(process.env.CODEX_HOME ?? "", "config.toml");',
348348
'const authPath = path.join(process.env.CODEX_HOME ?? "", "auth.json");',
349349
'console.log(`AUTH_EXISTS:${fs.existsSync(authPath)}`);',
350-
'if (fs.existsSync(authPath)) console.log(`AUTH_JSON:${fs.readFileSync(authPath, "utf8").trim()}`);',
350+
'if (fs.existsSync(authPath)) {',
351+
' console.log(`AUTH_JSON:${fs.readFileSync(authPath, "utf8").trim()}`);',
352+
' console.log(`AUTH_MODE:${(fs.statSync(authPath).mode & 0o777).toString(8)}`);',
353+
'}',
351354
'console.log("CONFIG_START");',
352355
'console.log(fs.readFileSync(configPath, "utf8").trim());',
356+
'console.log(`CONFIG_MODE:${(fs.statSync(configPath).mode & 0o777).toString(8)}`);',
353357
'console.log("CONFIG_END");',
354358
"process.exit(0);",
355359
]);
@@ -382,8 +386,14 @@ describe("codex bin wrapper", () => {
382386
expect(output).toContain("CODEX_MULTI_AUTH_DIR_JSON:null");
383387
expect(output).toContain("AUTH_EXISTS:true");
384388
expect(output).toContain("AUTH_JSON:{}");
389+
expect(output).toContain("AUTH_MODE:");
385390
expect(output).toContain('model_reasoning_effort = "high"');
391+
expect(output).toContain("CONFIG_MODE:");
386392
expect(output).not.toContain('model_reasoning_effort = "xhigh"');
393+
if (process.platform !== "win32") {
394+
expect(output).toContain("AUTH_MODE:600");
395+
expect(output).toContain("CONFIG_MODE:600");
396+
}
387397
});
388398

389399
it("cleans up compatibility shadow homes when staging fails", () => {
@@ -410,6 +420,7 @@ describe("codex bin wrapper", () => {
410420
CODEX_HOME: originalHome,
411421
TMP: controlledTmp,
412422
TEMP: controlledTmp,
423+
TMPDIR: controlledTmp,
413424
},
414425
);
415426

@@ -935,6 +946,7 @@ describe("codex bin wrapper", () => {
935946
npm_config_prefix: "",
936947
},
937948
stdio: ["ignore", "pipe", "ignore"],
949+
timeout: 5000,
938950
windowsHide: true,
939951
});
940952
});
@@ -983,6 +995,7 @@ describe("codex bin wrapper", () => {
983995
npm_config_prefix: "",
984996
},
985997
stdio: ["ignore", "pipe", "ignore"],
998+
timeout: 5000,
986999
windowsHide: true,
9871000
});
9881001
});
@@ -1022,6 +1035,9 @@ describe("codex bin wrapper", () => {
10221035
expect(spawnCalls).toHaveLength(1);
10231036
expect(spawnCalls[0]?.command).toBe("C:\\Windows\\System32\\cmd.exe");
10241037
expect(spawnCalls[0]?.args).toEqual(["/d", "/s", "/c", "npm root -g"]);
1038+
expect(spawnCalls[0]?.options).toMatchObject({
1039+
timeout: 5000,
1040+
});
10251041
});
10261042

10271043
it("derives cmd.exe from uppercase SYSTEMROOT when ComSpec is unavailable", () => {
@@ -1062,6 +1078,9 @@ describe("codex bin wrapper", () => {
10621078
expect(spawnCalls).toHaveLength(1);
10631079
expect(spawnCalls[0]?.command).toBe("C:\\Windows\\System32\\cmd.exe");
10641080
expect(spawnCalls[0]?.args).toEqual(["/d", "/s", "/c", "npm root -g"]);
1081+
expect(spawnCalls[0]?.options).toMatchObject({
1082+
timeout: 5000,
1083+
});
10651084
});
10661085

10671086
it("falls back to bare cmd.exe when no Windows shell env vars are set", () => {
@@ -1104,6 +1123,7 @@ describe("codex bin wrapper", () => {
11041123
npm_config_prefix: "",
11051124
},
11061125
stdio: ["ignore", "pipe", "ignore"],
1126+
timeout: 5000,
11071127
windowsHide: true,
11081128
});
11091129
});
@@ -1150,6 +1170,7 @@ describe("codex bin wrapper", () => {
11501170
npm_config_prefix: "",
11511171
},
11521172
stdio: ["ignore", "pipe", "ignore"],
1173+
timeout: 5000,
11531174
});
11541175
expect(spawnCalls[0]?.options).not.toHaveProperty("windowsHide");
11551176
});

0 commit comments

Comments
 (0)