From 99a687d6bd34ceba08a6e49201ec8ff48eddeae0 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:35:14 -0700 Subject: [PATCH] fix(codex): verify reconstructed WSL homes --- src/cli/handlers/agent-hooks.test.ts | 2 +- src/cli/handlers/agent-hooks.ts | 4 ++-- .../managed-home-shell-preflight.test.ts | 8 +++++++ .../codex/managed-wsl-home-shell-preflight.ts | 15 +++++++++++- .../runtime/rpc/methods/agent-hooks.test.ts | 23 +++++++++++++++++-- 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/cli/handlers/agent-hooks.test.ts b/src/cli/handlers/agent-hooks.test.ts index e5aea0ab400..4fcc186b0d8 100644 --- a/src/cli/handlers/agent-hooks.test.ts +++ b/src/cli/handlers/agent-hooks.test.ts @@ -152,7 +152,7 @@ describe('agent hooks CLI handler', () => { expect(callMock).toHaveBeenCalledExactlyOnceWith( 'agentHooks.prepareCodexForWslPane', { codexHome: home, orcaCodexHome: home, wslDistro: 'Ubuntu-24.04' }, - { timeoutMs: 40_000 } + { timeoutMs: 50_000 } ) expect(prepareManagedCodexHomeBeforeShellLaunchMock).not.toHaveBeenCalled() }) diff --git a/src/cli/handlers/agent-hooks.ts b/src/cli/handlers/agent-hooks.ts index c7105a26f76..bf44211b4a7 100644 --- a/src/cli/handlers/agent-hooks.ts +++ b/src/cli/handlers/agent-hooks.ts @@ -28,8 +28,8 @@ type AgentHookCommandResult = { statuses: AgentHookInstallStatus[] } -// Covers the 5s WSL identity probe, 30s trust grant, and bounded app-server reap. -const WSL_CODEX_PREPARE_TIMEOUT_MS = 40_000 +// Covers managed-home verification, WSL identity, trust grant, and bounded app-server reap. +const WSL_CODEX_PREPARE_TIMEOUT_MS = 50_000 function getDataPath(): string { const userDataPath = getDefaultUserDataPath() diff --git a/src/main/codex/managed-home-shell-preflight.test.ts b/src/main/codex/managed-home-shell-preflight.test.ts index c238f968039..31a98a30b2f 100644 --- a/src/main/codex/managed-home-shell-preflight.test.ts +++ b/src/main/codex/managed-home-shell-preflight.test.ts @@ -2,6 +2,13 @@ import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' + +const { wslRealpathMock } = vi.hoisted(() => ({ + wslRealpathMock: vi.fn(async (path: string) => path) +})) + +vi.mock('node:fs/promises', () => ({ realpath: wslRealpathMock })) + import { prepareManagedCodexHomeBeforeShellLaunch, resolveManagedCodexShellPreflightHome @@ -24,6 +31,7 @@ function makeRoot(): string { } afterEach(() => { + wslRealpathMock.mockClear() managedWslHomeRegistryInternals.clearRecordedManagedWslCodexHomes() for (const root of roots.splice(0)) { rmSync(root, { recursive: true, force: true }) diff --git a/src/main/codex/managed-wsl-home-shell-preflight.ts b/src/main/codex/managed-wsl-home-shell-preflight.ts index 614894990ba..854869ab05a 100644 --- a/src/main/codex/managed-wsl-home-shell-preflight.ts +++ b/src/main/codex/managed-wsl-home-shell-preflight.ts @@ -1,9 +1,12 @@ +import { realpath } from 'node:fs/promises' import type { AgentHookInstallStatus } from '../../shared/agent-hook-types' +import { withTimeout } from '../../shared/promise-timeout-fallback' import type { CodexWslRuntimeHookTarget } from './codex-wsl-hook-install-plan' import { codexHookService } from './hook-service' import { isAbsolutePosixPathWithoutDotSegments, - resolveManagedWslCodexHome + resolveManagedWslCodexHome, + wslRuntimeHomePathsEqual } from './managed-wsl-codex-home-registry' type WslShellPreflightEnvironment = { @@ -12,6 +15,8 @@ type WslShellPreflightEnvironment = { WSL_DISTRO_NAME?: string } +const WSL_MANAGED_HOME_REALPATH_TIMEOUT_MS = 5_000 + export type ManagedWslCodexShellPreflightTarget = { runtimeHomePath: string wslDistro: string @@ -51,6 +56,14 @@ export async function prepareManagedWslCodexHomeBeforeShellLaunch(args: { if (!target) { return null } + const realHome = await withTimeout( + realpath(target.runtimeHomePath), + WSL_MANAGED_HOME_REALPATH_TIMEOUT_MS, + null + ) + if (!realHome || !wslRuntimeHomePathsEqual(realHome, target.runtimeHomePath)) { + return null + } const install = args.install ?? ((home: string, hookTarget: CodexWslRuntimeHookTarget) => diff --git a/src/main/runtime/rpc/methods/agent-hooks.test.ts b/src/main/runtime/rpc/methods/agent-hooks.test.ts index 16fbe06abbb..5781045a1f7 100644 --- a/src/main/runtime/rpc/methods/agent-hooks.test.ts +++ b/src/main/runtime/rpc/methods/agent-hooks.test.ts @@ -2,13 +2,15 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import type { OrcaRuntimeService } from '../../orca-runtime' import { isStreamingMethod, type RpcContext } from '../core' -const { installForRuntimeHomeSerializedMock } = vi.hoisted(() => ({ - installForRuntimeHomeSerializedMock: vi.fn() +const { installForRuntimeHomeSerializedMock, realpathMock } = vi.hoisted(() => ({ + installForRuntimeHomeSerializedMock: vi.fn(), + realpathMock: vi.fn() })) vi.mock('../../../codex/hook-service', () => ({ codexHookService: { installForRuntimeHomeSerialized: installForRuntimeHomeSerializedMock } })) +vi.mock('node:fs/promises', () => ({ realpath: realpathMock })) import { AGENT_HOOK_METHODS } from './agent-hooks' import { @@ -42,6 +44,8 @@ function runtimeWithSettings(enabled = true, disabledTuiAgents: string[] = []): describe('agent hook RPC methods', () => { beforeEach(() => { installForRuntimeHomeSerializedMock.mockReset() + realpathMock.mockReset() + realpathMock.mockImplementation(async (path: string) => path) managedWslHomeRegistryInternals.clearRecordedManagedWslCodexHomes() recordManagedWslCodexHome('Ubuntu-24.04', RUNTIME_HOME) }) @@ -112,6 +116,21 @@ describe('agent hook RPC methods', () => { expect(installForRuntimeHomeSerializedMock).toHaveBeenCalledOnce() }) + it('rejects a managed-looking home that resolves through a symlink', async () => { + realpathMock.mockResolvedValue( + '\\\\wsl.localhost\\Ubuntu-24.04\\home\\jin\\outside-managed-home' + ) + const method = prepareMethod() + const params = method.params!.parse({ + codexHome: LINUX_HOME, + orcaCodexHome: LINUX_HOME, + wslDistro: 'Ubuntu-24.04' + }) + + await expect(method.handler(params, { runtime: runtimeWithSettings() })).resolves.toBeNull() + expect(installForRuntimeHomeSerializedMock).not.toHaveBeenCalled() + }) + it('rejects malformed distro names at the RPC schema', () => { const method = prepareMethod()