From d14139fa1e14523ea2cd34ff5a2b52329d1e6c98 Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 25 Sep 2026 15:09:27 +0900 Subject: [PATCH 1/2] fix(test): keep the developer's live OpenCodex credentials out of the test sandbox A Windows install stores its data-plane token as a user environment variable, so the sandbox inherited OPENCODEX_API_AUTH_TOKEN from every shell on that machine. installLaunchd then baked the live token into the plist, the byte-identical check failed, and five launchd repair tests failed only on a developer machine. createIsolatedTestEnvironment and the preload now drop OPENCODEX_API_AUTH_TOKEN, OPENCODEX_ADMIN_AUTH_TOKEN, and OCX_API_TOKEN_FILE. --- scripts/test.ts | 16 +++++++++++++++- tests/ci-workflows/test-runner.test.ts | 18 ++++++++++++++++++ tests/preload.ts | 4 +++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/scripts/test.ts b/scripts/test.ts index 05bdc28acec..6db9075224a 100644 --- a/scripts/test.ts +++ b/scripts/test.ts @@ -22,6 +22,18 @@ export interface IsolatedTestEnvironment { cleanup(): void; } +/** + * Credentials of the developer's own OpenCodex install that the sandbox must not inherit. A + * Windows install stores its data-plane token as a user environment variable, so every shell on + * that machine carries it; a test that then builds a service definition or starts a proxy reads + * the live token instead of its fixture and fails only on a developer machine. + */ +export const LIVE_INSTALL_CREDENTIAL_ENV = [ + "OPENCODEX_API_AUTH_TOKEN", + "OPENCODEX_ADMIN_AUTH_TOKEN", + "OCX_API_TOKEN_FILE", +] as const; + export function createIsolatedTestEnvironment( baseEnv: Record = process.env, ): IsolatedTestEnvironment { @@ -53,11 +65,13 @@ export function createIsolatedTestEnvironment( mkdirSync(join(root, "AppData", "Roaming"), { recursive: true }); } writeTestTempOwner(root, baseEnv[TEST_RUN_ID_ENV]); + const inherited = { ...baseEnv }; + for (const name of LIVE_INSTALL_CREDENTIAL_ENV) delete inherited[name]; return { root, env: { - ...baseEnv, + ...inherited, // Captured BEFORE HOME is overwritten: once the child starts with a rewritten // HOME, `homedir()` returns the sandbox, so this hand-off is the only way the // real-home write guard can still know which path to protect. diff --git a/tests/ci-workflows/test-runner.test.ts b/tests/ci-workflows/test-runner.test.ts index 00004ff9a01..37f208c6b3e 100644 --- a/tests/ci-workflows/test-runner.test.ts +++ b/tests/ci-workflows/test-runner.test.ts @@ -18,6 +18,7 @@ import { changedSelectionFailure, captureTestOutput, createIsolatedTestEnvironment, + LIVE_INSTALL_CREDENTIAL_ENV, ensureGuiDependencies, inspectChangedRun, resolveBunTestArgs, @@ -327,6 +328,23 @@ describe("test runner isolation", () => { expect(existsSync(isolated.root)).toBe(false); }); + test("drops the developer's live install credentials and keeps the rest of the environment", () => { + const isolated = createIsolatedTestEnvironment({ + PATH: "/test/bin", + FIXTURE: "unchanged", + OPENCODEX_API_AUTH_TOKEN: "live-data-token", + OPENCODEX_ADMIN_AUTH_TOKEN: "live-admin-token", + OCX_API_TOKEN_FILE: "/real/home/.opencodex/service-api-token", + }); + try { + for (const name of LIVE_INSTALL_CREDENTIAL_ENV) expect(name in isolated.env).toBe(false); + expect(isolated.env.FIXTURE).toBe("unchanged"); + expect(isolated.env.PATH).toBe("/test/bin"); + } finally { + isolated.cleanup(); + } + }); + test.if(process.platform === "win32")("gives the Windows sandbox a real profile shape", () => { const isolated = createIsolatedTestEnvironment({ PATH: "C:\\test\\bin" }); try { diff --git a/tests/preload.ts b/tests/preload.ts index cb753e45346..97797ff1c2b 100644 --- a/tests/preload.ts +++ b/tests/preload.ts @@ -28,7 +28,7 @@ */ import { afterAll } from "bun:test"; import { isTestHomeGuardArmed, protectedHomeForTests } from "../src/lib/test-home-guard"; -import { createIsolatedTestEnvironment } from "../scripts/test"; +import { createIsolatedTestEnvironment, LIVE_INSTALL_CREDENTIAL_ENV } from "../scripts/test"; import { acquireTestRunLock, resolveBareTestRunIdentity, @@ -47,6 +47,8 @@ const isolated = createIsolatedTestEnvironment(); for (const [key, value] of Object.entries(isolated.env)) { if (value !== undefined) process.env[key] = value; } +// The sandbox drops these from its env, but this process started with them, so remove them here. +for (const name of LIVE_INSTALL_CREDENTIAL_ENV) delete process.env[name]; // Arm the guard once the sandbox is in place, and BEFORE the run lock. // From 962881955361c346aaa1cef38d08861f64ef00da Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 25 Sep 2026 15:11:12 +0900 Subject: [PATCH 2/2] test(link): skip POSIX mode-bit checks on Windows in two link tests link-compensation and client-link-tunnel asserted 0600 on files Windows protects with NTFS ACLs, where stat reports 0666. The other link tests already guard this the same way. --- tests/clients/client-link-tunnel.test.ts | 2 +- tests/clients/link-compensation.test.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/clients/client-link-tunnel.test.ts b/tests/clients/client-link-tunnel.test.ts index 4e4c8eb6305..2b41415c323 100644 --- a/tests/clients/client-link-tunnel.test.ts +++ b/tests/clients/client-link-tunnel.test.ts @@ -78,7 +78,7 @@ test("spawns the client tunnel with the exact local forward argv and writes a pr argv: fake.children[0]!.child.argv, ownerPid: process.pid, }); - expect(statSync(pidfile).mode & 0o777).toBe(0o600); + if (process.platform !== "win32") expect(statSync(pidfile).mode & 0o777).toBe(0o600); fake.children[0]!.resolve(0); await handle.stop(); expect(existsSync(pidfile)).toBe(false); diff --git a/tests/clients/link-compensation.test.ts b/tests/clients/link-compensation.test.ts index 1e7a3072580..ee93cb2578b 100644 --- a/tests/clients/link-compensation.test.ts +++ b/tests/clients/link-compensation.test.ts @@ -19,7 +19,8 @@ test("persists strict compensation markers atomically with private permissions", const since = "2026-09-25T00:00:00.000Z"; markCompensationFailed(linkId, since, path); expect(readCompensation(path)).toEqual({ version: 1, entries: { [linkId]: { reason: "compensation_failed", since } } }); - expect(statSync(path).mode & 0o777).toBe(0o600); + // Windows has no POSIX mode bits; the file is protected by the NTFS ACL hardening instead. + if (process.platform !== "win32") expect(statSync(path).mode & 0o777).toBe(0o600); expect(JSON.parse(readFileSync(path, "utf8"))).toEqual(readCompensation(path)); clearCompensationFailed(linkId, path); expect(readCompensation(path)).toEqual({ version: 1, entries: {} });