From b42cbfd95c185adc56cdfbc6ac0a50bd83bd7d9b Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 23 Oct 2026 15:08:10 +0900 Subject: [PATCH] test: keep cold first-exec and PowerShell start out of two timed cases The PATH-fallback activation cases copied a fresh Bun binary per case and executed it inside the 5s case budget; the OS first-run scan of that copy dominated under host load (#7578). One shared PATH Bun is now copied and warmed once in a budgeted beforeAll on every platform. The real CurrentUser Root inspection paid PowerShell's cold start inside the 10s status deadline and read as unknown on a loaded Windows runner (#7569). The same inspection is replayed once through warmColdSpawn, and the deadline breach itself is proven on every platform through a runner seam. Refs #7578 Refs #7569 --- .../windows-certificate-trust.ts | 58 +++++++++++++++---- tests/cli/ocx-launcher-runtime.test.ts | 45 +++++++++----- .../desktop-compatibility-trust.test.ts | 44 +++++++++++++- 3 files changed, 117 insertions(+), 30 deletions(-) diff --git a/src/codex/desktop-compatibility/windows-certificate-trust.ts b/src/codex/desktop-compatibility/windows-certificate-trust.ts index d26e2f828c3..1ad9e3be883 100644 --- a/src/codex/desktop-compatibility/windows-certificate-trust.ts +++ b/src/codex/desktop-compatibility/windows-certificate-trust.ts @@ -9,6 +9,26 @@ export type DesktopCertificateTrust = "trusted" | "not-trusted" | "unknown"; export type TrustOperation = "inspect" | "trust" | "remove"; export type CertificateTrustRunner = (operation: TrustOperation, certificateDer: string, fingerprint: string) => Promise; +/** Public status is an interactive read; a store that cannot answer within it reports `unknown`. */ +export const TRUST_INSPECT_DEADLINE_MS = 10_000; +/** A mutation may wait on the user's certificate prompt. */ +export const TRUST_CHANGE_DEADLINE_MS = 120_000; + +/** The slice of `child_process.execFile` the runner uses, so a deadline breach can be driven without a real store. */ +export type TrustCommandExec = ( + file: string, + args: string[], + options: { timeout: number; maxBuffer: number; windowsHide: boolean; encoding: "utf8" }, + callback: (error: Error | null, stdout: string) => void, +) => { stdin?: { on(event: "error", listener: () => void): unknown; end(chunk: string): unknown } | null }; + +export type PowerShellTrustRunnerOptions = { + inspectDeadlineMs?: number; + platform?: NodeJS.Platform; + exec?: TrustCommandExec; + resolvePowerShell?: () => string; +}; + const SCRIPT = [ "$ErrorActionPreference='Stop'", "$inputData=[Console]::In.ReadToEnd()|ConvertFrom-Json", @@ -31,18 +51,32 @@ const SCRIPT = [ "}finally{$store.Close();$store.Dispose();$cert.Dispose()}", ].join("\n"); -const run: CertificateTrustRunner = (operation, certificateDer, fingerprint) => { - if (process.platform !== "win32") return Promise.resolve("unknown"); - if (operation !== "inspect" && isTestHomeGuardArmed()) return Promise.resolve("unknown"); - return new Promise(resolve => { - const child = execFile(resolveTrustedWindowsPowerShellExe(), ["-NoProfile", "-NonInteractive", "-Command", SCRIPT], { - timeout: operation === "inspect" ? 10_000 : 120_000, maxBuffer: 4096, windowsHide: true, encoding: "utf8", - }, (error, stdout) => resolve(!error && (stdout.trim() === "trusted" || stdout.trim() === "not-trusted") - ? stdout.trim() as DesktopCertificateTrust : "unknown")); - child.stdin?.on("error", () => { /* completion and readback decide the result */ }); - child.stdin?.end(JSON.stringify({ operation, certificate: certificateDer, fingerprint })); - }); -}; +/** + * The real CurrentUser Root runner. Any failure, including a deadline breach, is `unknown`, which + * never authorizes a write. The options exist so a test can replay this exact call with a cold-start + * deadline and can drive a breach deterministically; production uses the defaults. + */ +export function createPowerShellCertificateTrustRunner({ + inspectDeadlineMs = TRUST_INSPECT_DEADLINE_MS, + platform = process.platform, + exec = execFile as unknown as TrustCommandExec, + resolvePowerShell = resolveTrustedWindowsPowerShellExe, +}: PowerShellTrustRunnerOptions = {}): CertificateTrustRunner { + return (operation, certificateDer, fingerprint) => { + if (platform !== "win32") return Promise.resolve("unknown"); + if (operation !== "inspect" && isTestHomeGuardArmed()) return Promise.resolve("unknown"); + return new Promise(resolve => { + const child = exec(resolvePowerShell(), ["-NoProfile", "-NonInteractive", "-Command", SCRIPT], { + timeout: operation === "inspect" ? inspectDeadlineMs : TRUST_CHANGE_DEADLINE_MS, maxBuffer: 4096, windowsHide: true, encoding: "utf8", + }, (error, stdout) => resolve(!error && (stdout.trim() === "trusted" || stdout.trim() === "not-trusted") + ? stdout.trim() as DesktopCertificateTrust : "unknown")); + child.stdin?.on("error", () => { /* completion and readback decide the result */ }); + child.stdin?.end(JSON.stringify({ operation, certificate: certificateDer, fingerprint })); + }); + }; +} + +const run = createPowerShellCertificateTrustRunner(); /** Public status cannot create a certificate, decrypt a key or alter OS trust. */ export async function inspectWindowsCertificateTrust(certPem: string, expectedFingerprint: string, runner: CertificateTrustRunner = run): Promise { diff --git a/tests/cli/ocx-launcher-runtime.test.ts b/tests/cli/ocx-launcher-runtime.test.ts index aa596031ef9..af4ac15d344 100644 --- a/tests/cli/ocx-launcher-runtime.test.ts +++ b/tests/cli/ocx-launcher-runtime.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { spawn, spawnSync, type ChildProcess } from "node:child_process"; import { chmodSync, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, realpathSync, readlinkSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs"; import { createServer } from "node:net"; @@ -10,6 +10,7 @@ import { findDesktopCli, findPathBun, type PathBunIo } from "../../src/lib/bun-p import { REAL_BUN_MIN_BYTES } from "../../src/lib/bun-binary-validator.mjs"; import { killProxy } from "../../src/lib/process-control"; import { repoPath } from "../helpers/repo-root"; +import { COLD_SPAWN_WARMUP_HOOK_BUDGET_MS, warmColdSpawn } from "../helpers/cold-spawn-warmup"; import { INTERNAL_DEADLINE_MS, SPAWN_BUDGET_MS } from "../helpers/test-budget"; import { trustedFixtureRoot } from "../helpers/trusted-fixture-root"; @@ -762,14 +763,29 @@ describe("real PATH Bun validation", () => { }); }); -function warmCopiedBun(path: string): void { - if (process.platform !== "win32") return; - const warmed = spawnSync(path, ["--version"], { - encoding: "utf8", - timeout: WINDOWS_COLD_SPAWN_MS, - windowsHide: true, +/** + * The one real Bun copy every PATH-fallback case puts on PATH, created and run once in setup. + * + * The first exec of a freshly copied bun binary is held by the OS's first-run scan: about 1s on an + * idle macOS host (later execs of the same path take ~3ms) and well over that for a cold win32 + * bun.exe. A copy per case put that scan inside each case's 5s budget and inside the launcher's own + * 5s PATH probe, so the verdict tracked host load (#7578). The scan is keyed by path, so a hardlink + * does not inherit it; sharing the warmed path is what keeps it out of the measured window. + * The launcher only reads and executes this file, so sharing it changes nothing a case asserts. + */ +let sharedPathBun: { root: string; dir: string; bun: string } | undefined; + +async function createWarmPathBun(): Promise { + const root = realpathSync.native(pathFixtureRoot("ocx-path-launcher-bun-")); + const dir = join(root, "path"); mkdirSync(dir); + const bun = join(dir, process.platform === "win32" ? "bun.exe" : "bun"); + copyFileSync(process.execPath, bun); + chmodSync(bun, 0o755); + sharedPathBun = { root, dir, bun }; + await warmColdSpawn("bun/launcher-path-fallback-copy", deadlineMs => { + const warmed = spawnSync(bun, ["--version"], { encoding: "utf8", timeout: deadlineMs, windowsHide: true }); + if (warmed.status !== 0) throw new Error(`[cold-spawn-warmup] PATH Bun copy did not run: status=${String(warmed.status)} ${warmed.stderr}`); }); - expect(warmed.status, warmed.stderr).toBe(0); } function launcherFallbackFixture(mode: "missing" | "bundled" | "repair" | "broken" | "override" | "none" | "inspection" | "rejected") { @@ -783,17 +799,14 @@ function launcherFallbackFixture(mode: "missing" | "bundled" | "repair" | "broke copyFileSync(BIN_OCX, join(root, "bin", "ocx.mjs")); writeFileSync(join(root, "package.json"), JSON.stringify({ version: "2.81.0", dependencies: { bun: "1.4.2" } })); writeFileSync(join(root, "src", "cli", "index.ts"), 'console.log(JSON.stringify({source:process.env.OCX_BUN_RUNTIME_SOURCE,path:process.env.OCX_BUN_RUNTIME_PATH}));'); - const pathDir = join(root, "path"); mkdirSync(pathDir); + const shared = mode !== "none" && mode !== "rejected"; + if (shared && !sharedPathBun) throw new Error("the shared PATH Bun is created by the activation suite's beforeAll"); + const pathDir = shared ? sharedPathBun!.dir : join(root, "path"); + if (!shared) mkdirSync(pathDir); const pathBun = join(pathDir, process.platform === "win32" ? "bun.exe" : "bun"); if (mode === "rejected") { writeFileSync(pathBun, '#!/bin/sh\nprintf "2.0.0\\n"\n#' + "x".repeat(REAL_BUN_MIN_BYTES)); chmodSync(pathBun, 0o755); - } else if (mode !== "none") { - copyFileSync(process.execPath, pathBun); - chmodSync(pathBun, 0o755); - // The product PATH probe is 5s for both execs together. Warm the copy first so - // that budget measures a scanned binary; a cold win32 bun.exe is the flake. - warmCopiedBun(pathBun); } const bundleDir = join(root, "node_modules", "bun"); if (["bundled", "repair", "broken", "inspection"].includes(mode)) { @@ -820,6 +833,8 @@ function launcherFallbackFixture(mode: "missing" | "bundled" | "repair" | "broke } describe.skipIf(!nodeAvailable)("Node launcher PATH fallback activation", () => { + beforeAll(async () => { await createWarmPathBun(); }, COLD_SPAWN_WARMUP_HOOK_BUDGET_MS); + afterAll(() => { if (sharedPathBun) removeTree(sharedPathBun.root); sharedPathBun = undefined; }); test.each(["missing", "broken", "inspection"] as const)("%s bundle reaches validated PATH Bun", mode => { const result = launcherFallbackFixture(mode); expect(result.status).toBe(0); diff --git a/tests/clients/desktop-compatibility-trust.test.ts b/tests/clients/desktop-compatibility-trust.test.ts index db580c72246..38232ead423 100644 --- a/tests/clients/desktop-compatibility-trust.test.ts +++ b/tests/clients/desktop-compatibility-trust.test.ts @@ -1,8 +1,12 @@ -import { describe, expect, test } from "bun:test"; +import { beforeAll, describe, expect, test } from "bun:test"; import { randomUUID, X509Certificate } from "node:crypto"; import { createCertificateAuthority } from "../../src/claude/intercept/local-ca"; import type { StoredDesktopAuthority } from "../../src/codex/desktop-compatibility/certificate-store"; -import { createWindowsCertificateTrust, inspectWindowsCertificateTrust, type DesktopCertificateTrust, type TrustOperation } from "../../src/codex/desktop-compatibility/windows-certificate-trust"; +import { + createPowerShellCertificateTrustRunner, createWindowsCertificateTrust, inspectWindowsCertificateTrust, TRUST_INSPECT_DEADLINE_MS, + type DesktopCertificateTrust, type TrustCommandExec, type TrustOperation, +} from "../../src/codex/desktop-compatibility/windows-certificate-trust"; +import { COLD_SPAWN_WARMUP_HOOK_BUDGET_MS, warmColdSpawn } from "../helpers/cold-spawn-warmup"; function fixture(serverAuthOnly = true): StoredDesktopAuthority { const commonName = `OpenCodex Codex Desktop ${randomUUID()}`; @@ -73,7 +77,41 @@ describe("exact Windows compatibility certificate trust", () => { release("trusted"); expect(await first).toBe("trusted"); }); - test.skipIf(process.platform !== "win32")("real CurrentUser Root inspection remains read-only for an unregistered synthetic CA", async () => { + test("a store read that outlives the status deadline is unknown and requests nothing but an inspection", async () => { + // The runner's own deadline, driven without a real store: execFile reports a killed child the way + // node does when `timeout` elapses, with whatever the child printed before the kill. This is the + // half of the real-store case that depends on timing, so it is proven here on every platform. + const value = fixture(), calls: { timeout: number; input: string }[] = []; + const exec: TrustCommandExec = (_file, _args, options, callback) => { + let input = ""; + queueMicrotask(() => { + calls.push({ timeout: options.timeout, input }); + callback(Object.assign(new Error("Command failed: powershell.exe"), { killed: true, signal: "SIGTERM", code: null }), "not-trusted"); + }); + return { stdin: { on: () => undefined, end: (chunk: string) => { input = chunk; } } }; + }; + const runner = createPowerShellCertificateTrustRunner({ platform: "win32", exec, resolvePowerShell: () => "powershell.exe" }); + expect(await inspectWindowsCertificateTrust(value.authority.certPem, value.fingerprint, runner)).toBe("unknown"); + expect(calls).toHaveLength(1); + expect(calls[0].timeout).toBe(TRUST_INSPECT_DEADLINE_MS); + expect(JSON.parse(calls[0].input).operation).toBe("inspect"); + }); +}); + +describe.skipIf(process.platform !== "win32")("exact Windows compatibility certificate trust on the real store", () => { + // The first PowerShell in a shard pays process start, .NET load and the X509 store open; on a + // loaded runner that crossed the 10s status deadline and read as `unknown` (#7569). Replaying the + // same inspection once here leaves the unchanged product deadline below measuring a warm read. + beforeAll(async () => { + await warmColdSpawn("powershell/desktop-certificate-trust-inspect", async deadlineMs => { + const value = fixture(); + const runner = createPowerShellCertificateTrustRunner({ inspectDeadlineMs: deadlineMs }); + const state = await inspectWindowsCertificateTrust(value.authority.certPem, value.fingerprint, runner); + if (state === "unknown") throw new Error(`[cold-spawn-warmup] real CurrentUser Root inspection gave no answer within ${deadlineMs}ms`); + }); + }, COLD_SPAWN_WARMUP_HOOK_BUDGET_MS); + + test("real CurrentUser Root inspection remains read-only for an unregistered synthetic CA", async () => { const value = fixture(); expect(await inspectWindowsCertificateTrust(value.authority.certPem, value.fingerprint)).toBe("not-trusted"); }, 15_000);