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
104 changes: 50 additions & 54 deletions apps/server/src/provider/Layers/CodexAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,65 +439,61 @@ validationLayer("CodexAdapterLive validation", (it) => {
);
});

it.effect(
"retries Windows Command Center isolation unelevated after the elevated probe fails",
() => {
const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "cc-windows-fallback-"));
const runtimePath = NodePath.join(tempDir, "codex.exe");
const sourceHomePath = NodePath.join(tempDir, "codex-home");
NodeFS.mkdirSync(sourceHomePath, { recursive: true });
NodeFS.writeFileSync(runtimePath, Uint8Array.from([0x4d, 0x5a, 0x00, 0x00]));

const runtimeFactory = makeRuntimeFactory({
startFailures: [
new CodexSessionRuntimeIsolationProbeError({
issue: "The elevated live isolation probe failed.",
exitCode: 79,
}),
],
});
const layer = Layer.effect(
CodexAdapter,
makeCodexAdapter(decodeCodexSettings({}), {
makeRuntime: runtimeFactory.factory,
commandCenterSourceHomePath: sourceHomePath,
commandCenterRuntimeExecutablePath: runtimePath,
commandCenterPlatform: "win32",
commandCenterArchitecture: "x64",
it.effect("fails closed when elevated Windows Command Center isolation cannot be verified", () => {
const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "cc-windows-fallback-"));
const runtimePath = NodePath.join(tempDir, "codex.exe");
const sourceHomePath = NodePath.join(tempDir, "codex-home");
NodeFS.mkdirSync(sourceHomePath, { recursive: true });
NodeFS.writeFileSync(runtimePath, Uint8Array.from([0x4d, 0x5a, 0x00, 0x00]));

const runtimeFactory = makeRuntimeFactory({
startFailures: [
new CodexSessionRuntimeIsolationProbeError({
issue: "The elevated live isolation probe failed.",
exitCode: 79,
}),
).pipe(
Layer.provideMerge(
ServerConfig.layerTest(process.cwd(), { prefix: "codex-adapter-windows-fallback-" }),
),
Layer.provideMerge(ServerSettingsService.layerTest()),
Layer.provideMerge(providerSessionDirectoryTestLayer),
Layer.provideMerge(NodeServices.layer),
);
],
});
const layer = Layer.effect(
CodexAdapter,
makeCodexAdapter(decodeCodexSettings({}), {
makeRuntime: runtimeFactory.factory,
commandCenterSourceHomePath: sourceHomePath,
commandCenterRuntimeExecutablePath: runtimePath,
commandCenterPlatform: "win32",
commandCenterArchitecture: "x64",
}),
).pipe(
Layer.provideMerge(
ServerConfig.layerTest(process.cwd(), { prefix: "codex-adapter-windows-fallback-" }),
),
Layer.provideMerge(ServerSettingsService.layerTest()),
Layer.provideMerge(providerSessionDirectoryTestLayer),
Layer.provideMerge(NodeServices.layer),
);

return Effect.gen(function* () {
const adapter = yield* CodexAdapter;
const threadId = asThreadId("cc:interactive:windows-fallback");
yield* adapter.startSession({
return Effect.gen(function* () {
const adapter = yield* CodexAdapter;
const threadId = asThreadId("cc:interactive:windows-fallback");
const result = yield* adapter
.startSession({
provider: ProviderDriverKind.make("codex"),
threadId,
runtimeMode: "approval-required",
});

NodeAssert.equal(runtimeFactory.runtimes.length, 2);
NodeAssert.equal(runtimeFactory.runtimes[0]?.options.windowsSandboxMode, "elevated");
NodeAssert.equal(runtimeFactory.runtimes[0]?.closeImpl.mock.calls.length, 1);
NodeAssert.equal(runtimeFactory.runtimes[1]?.options.windowsSandboxMode, "unelevated");
NodeAssert.match(
runtimeFactory.runtimes[1]?.options.appServerArgs?.join(" ") ?? "",
/windows\.sandbox="unelevated"/u,
);
yield* adapter.stopSession(threadId);
}).pipe(
Effect.provide(layer),
Effect.ensuring(Effect.sync(() => NodeFS.rmSync(tempDir, { recursive: true, force: true }))),
);
},
);
})
.pipe(Effect.result);

NodeAssert.equal(result._tag, "Failure");
NodeAssert.equal(result.failure._tag, "ProviderAdapterProcessError");
NodeAssert.match(result.failure.message, /elevated live isolation probe failed/u);
NodeAssert.equal(runtimeFactory.runtimes.length, 1);
NodeAssert.equal(runtimeFactory.runtimes[0]?.options.windowsSandboxMode, "elevated");
NodeAssert.equal(runtimeFactory.runtimes[0]?.closeImpl.mock.calls.length, 1);
}).pipe(
Effect.provide(layer),
Effect.ensuring(Effect.sync(() => NodeFS.rmSync(tempDir, { recursive: true, force: true }))),
);
});

const sessionRuntimeFactory = makeRuntimeFactory();
const sessionErrorLayer = it.layer(
Expand Down
38 changes: 0 additions & 38 deletions apps/server/src/provider/Layers/CodexAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ import { resolveAttachmentPath } from "../../attachmentStore.ts";
import { ServerConfig } from "../../config.ts";
import {
CodexResumeCursorSchema,
CodexSessionRuntimeIsolationProbeError,
CodexSessionRuntimeWindowsSandboxSetupError,
CodexSessionRuntimeThreadIdMissingError,
makeCodexSessionRuntime,
matchesCodexInterruptTarget,
Expand Down Expand Up @@ -90,10 +88,6 @@ const isCodexAppServerProtocolParseError = Schema.is(CodexErrors.CodexAppServerP
const isCodexSessionRuntimeThreadIdMissingError = Schema.is(
CodexSessionRuntimeThreadIdMissingError,
);
const isCodexSessionRuntimeWindowsSandboxSetupError = Schema.is(
CodexSessionRuntimeWindowsSandboxSetupError,
);
const isCodexSessionRuntimeIsolationProbeError = Schema.is(CodexSessionRuntimeIsolationProbeError);
const isCommandCenterCodexHomeIsolationError = Schema.is(CommandCenterCodexHomeIsolationError);
const isCodexResumeCursorSchema = Schema.is(CodexResumeCursorSchema);

Expand Down Expand Up @@ -1967,38 +1961,6 @@ export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
});

const attempt = yield* startRuntimeAttempt(runtimeInput).pipe(
Effect.catch((cause) => {
const canTryUnelevated =
isCodexSessionRuntimeIsolationProbeError(cause) ||
(isCodexSessionRuntimeWindowsSandboxSetupError(cause) &&
cause.allowUnelevatedFallback);
if (
hostPlatform !== "win32" ||
!commandCenterThread ||
!canTryUnelevated ||
commandCenterRuntimeExecutable === undefined ||
commandCenterHome === undefined
) {
return Effect.fail(cause);
}
const fallbackIsolation = commandCenterCodexIsolation(
input.runtimeMode,
managedGitMetadata,
commandCenterRuntimeExecutable,
commandCenterHome,
hostPlatform,
"unelevated",
);
if (fallbackIsolation === undefined) return Effect.fail(cause);
const fallbackInput: CodexSessionRuntimeOptions = {
...runtimeInput,
permissionProfile: fallbackIsolation.permissionProfile,
commandCenterPlatform: "win32",
windowsSandboxMode: "unelevated",
appServerArgs: [...fallbackIsolation.appServerArgs, ...mcpAppServerArgs],
};
return startRuntimeAttempt(fallbackInput);
}),
Effect.mapError(
(cause) =>
new ProviderAdapterProcessError({
Expand Down
6 changes: 3 additions & 3 deletions apps/server/src/provider/Layers/CodexSessionRuntime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe("Command Center native sandbox admission", () => {
}),
);

it.effect("marks failed elevated setup as eligible for verified unelevated fallback", () =>
it.effect("reports failed elevated setup without offering an unsafe fallback", () =>
Effect.gen(function* () {
const error = yield* ensureCommandCenterWindowsSandbox({
client: {
Expand All @@ -67,8 +67,8 @@ describe("Command Center native sandbox admission", () => {
}).pipe(Effect.flip);

NodeAssert.equal(error.mode, "elevated");
NodeAssert.equal(error.allowUnelevatedFallback, true);
NodeAssert.equal(error.issue, "administrator approval was unavailable");
NodeAssert.match(error.issue, /administrator-approved Windows sandbox/u);
NodeAssert.match(error.issue, /administrator approval was unavailable/u);
}),
);

Expand Down
15 changes: 6 additions & 9 deletions apps/server/src/provider/Layers/CodexSessionRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export interface CodexSessionRuntimeOptions {
readonly appServerArgs?: ReadonlyArray<string>;
readonly permissionProfile?: string;
readonly commandCenterPlatform?: NodeJS.Platform;
readonly windowsSandboxMode?: "elevated" | "unelevated";
readonly windowsSandboxMode?: "elevated";
}

export interface CodexSessionRuntimeSendTurnInput {
Expand Down Expand Up @@ -243,7 +243,6 @@ export class CodexSessionRuntimeWindowsSandboxSetupError extends Schema.TaggedEr
{
issue: Schema.String,
mode: Schema.Literals(["elevated", "unelevated"]),
allowUnelevatedFallback: Schema.Boolean,
},
) {
override get message(): string {
Expand Down Expand Up @@ -879,24 +878,22 @@ export const ensureCommandCenterWindowsSandbox = Effect.fn(
)(function* (input: {
readonly client: CodexWindowsSandboxSetupClient;
readonly cwd: string;
readonly mode: "elevated" | "unelevated";
readonly mode: "elevated";
readonly setupCompleted: Effect.Effect<
EffectCodexSchema.V2WindowsSandboxSetupCompletedNotification,
never
>;
}) {
const setupError = (issue: string, allowUnelevatedFallback = input.mode === "elevated") =>
const setupError = (issue: string) =>
new CodexSessionRuntimeWindowsSandboxSetupError({
issue,
mode: input.mode,
allowUnelevatedFallback,
});
const readiness = yield* input.client.request("windowsSandbox/readiness", undefined).pipe(
Effect.flatMap(decodeV2WindowsSandboxReadinessResponse),
Effect.mapError((cause) =>
setupError(
`Command Center requires a Codex installation with native Windows sandbox setup support: ${cause instanceof Error ? cause.message : String(cause)}`,
false,
),
),
);
Expand Down Expand Up @@ -925,12 +922,13 @@ export const ensureCommandCenterWindowsSandbox = Effect.fn(
if (completed.mode !== input.mode) {
return yield* setupError(
`Codex completed '${completed.mode}' Windows sandbox setup while '${input.mode}' was required.`,
false,
);
}
if (!completed.success) {
const safeError = completed.error?.split(/\r?\n/u)[0]?.trim();
return yield* setupError(safeError || `Codex ${input.mode} Windows sandbox setup failed.`);
return yield* setupError(
`Command Center requires Codex's administrator-approved Windows sandbox to enforce workspace-only filesystem access. Approve the administrator prompt and try again.${safeError ? ` Codex reported: ${safeError}` : ""}`,
);
}
});

Expand Down Expand Up @@ -1828,7 +1826,6 @@ export const makeCodexSessionRuntime = (
return yield* new CodexSessionRuntimeWindowsSandboxSetupError({
issue: "Command Center did not select a native Windows sandbox mode.",
mode: "elevated",
allowUnelevatedFallback: false,
});
}
yield* ensureCommandCenterWindowsSandbox({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,19 +123,9 @@
"C:\\runtime\\codex.exe",
codexHome,
"win32",
"elevated",
);
const windowsUnelevated = commandCenterCodexIsolation(
"approval-required",
undefined,
"C:\\runtime\\codex.exe",
codexHome,
"win32",
"unelevated",
);
NodeAssert.ok(auto);
NodeAssert.ok(windowsElevated);
NodeAssert.ok(windowsUnelevated);

NodeAssert.equal(read.permissionProfile, COMMAND_CENTER_CODEX_READ_PERMISSION_PROFILE);
NodeAssert.equal(write.permissionProfile, COMMAND_CENTER_CODEX_WRITE_PERMISSION_PROFILE);
Expand Down Expand Up @@ -168,7 +158,7 @@
NodeAssert.match(writeConfig, /":workspace_roots"=\{"\."="write"\}/u);
NodeAssert.doesNotMatch(writeConfig, /network=\{enabled=true\}/u);
NodeAssert.match(windowsElevated.appServerArgs.join(" "), /windows\.sandbox="elevated"/u);
NodeAssert.match(windowsUnelevated.appServerArgs.join(" "), /windows\.sandbox="unelevated"/u);
NodeAssert.doesNotMatch(windowsElevated.appServerArgs.join(" "), /unelevated/u);
});

it.runIf(NodeProcess.platform === "linux")(
Expand Down Expand Up @@ -674,7 +664,7 @@
fileSystem,
path,
}).pipe(Effect.flip);
NodeAssert.match(error.issue, fixture.expected);

Check failure on line 667 in apps/server/src/provider/security/CommandCenterProviderIsolation.test.ts

View workflow job for this annotation

GitHub Actions / Command Center Native Isolation (macOS)

apps/server/src/provider/security/CommandCenterProviderIsolation.test.ts > CommandCenter provider runtime isolation > denies executable or included config for provider-writable sessions

AssertionError: The input did not match the regular expression /filter\.\*\.process/u. Input: 'The managed Command Center worktree Git directory contains a symlink escape.' - Expected: /filter\.\*\.process/u + Received: "The managed Command Center worktree Git directory contains a symlink escape." ❯ Array.<anonymous> apps/server/src/provider/security/CommandCenterProviderIsolation.test.ts:667:20 ❯ next node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/effect.ts:1350:25 ❯ Object.~effect/Effect/evaluate node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/core.ts:514:30 ❯ FiberImpl.runLoop node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/effect.ts:655:39 ❯ runLoop node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/effect.ts:607:22 ❯ evaluate node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/effect.ts:1106:14 ❯ resume node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/Effect.ts:25168:12
}
}),
);
Expand Down Expand Up @@ -712,7 +702,7 @@
fileSystem,
path,
}).pipe(Effect.flip);
NodeAssert.match(error.issue, /single-link regular file/u);

Check failure on line 705 in apps/server/src/provider/security/CommandCenterProviderIsolation.test.ts

View workflow job for this annotation

GitHub Actions / Command Center Native Isolation (macOS)

apps/server/src/provider/security/CommandCenterProviderIsolation.test.ts > CommandCenter provider runtime isolation > denies hardlink aliases for every linked-worktree control pointer

AssertionError: The input did not match the regular expression /single-link regular file/u. Input: 'The managed Command Center worktree Git directory contains a symlink escape.' - Expected: /single-link regular file/u + Received: "The managed Command Center worktree Git directory contains a symlink escape." ❯ Array.<anonymous> apps/server/src/provider/security/CommandCenterProviderIsolation.test.ts:705:20 ❯ next node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/effect.ts:1350:25 ❯ Object.~effect/Effect/evaluate node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/core.ts:514:30 ❯ FiberImpl.runLoop node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/effect.ts:655:39 ❯ runLoop node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/effect.ts:607:22 ❯ evaluate node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/effect.ts:1106:14 ❯ resume node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/Effect.ts:25168:12
}
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
export interface CommandCenterCodexIsolation {
readonly permissionProfile: string;
readonly appServerArgs: ReadonlyArray<string>;
readonly windowsSandboxMode?: "elevated" | "unelevated";
readonly windowsSandboxMode?: "elevated";
}

export interface CommandCenterManagedGitMetadata {
Expand Down Expand Up @@ -374,7 +374,7 @@
issue: string,
cause?: unknown,
): CommandCenterManagedWorktreeIsolationError {
return new CommandCenterManagedWorktreeIsolationError({

Check failure on line 377 in apps/server/src/provider/security/CommandCenterProviderIsolation.ts

View workflow job for this annotation

GitHub Actions / Command Center Native Isolation (macOS)

apps/server/src/provider/security/CommandCenterProviderIsolation.test.ts > CommandCenter provider runtime isolation > grants read-only access to the exact pointer and common metadata for a valid worktree

CommandCenterManagedWorktreeIsolationError: The managed Command Center worktree Git directory contains a symlink escape. ❯ managedWorktreeError apps/server/src/provider/security/CommandCenterProviderIsolation.ts:377:10 ❯ apps/server/src/provider/security/CommandCenterProviderIsolation.ts:1050:19 ❯ next node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/effect.ts:1350:25 ❯ Object.~effect/Effect/evaluate node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/core.ts:514:30 ❯ FiberImpl.runLoop node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/effect.ts:655:39 ❯ runLoop node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/effect.ts:607:22 ❯ evaluate node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/internal/effect.ts:1106:14 ❯ resume node_modules/.pnpm/effect@4.0.0-beta.102_patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488/node_modules/effect/src/Effect.ts:25168:12 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { _tag: 'CommandCenterManagedWorktreeIsolationError', issue: 'The managed Command Center worktree Git directory contains a symlink escape.' }
issue,
...(cause === undefined ? {} : { cause }),
});
Expand Down Expand Up @@ -1176,7 +1176,6 @@
runtimeExecutablePath?: string,
codexHome?: Pick<CommandCenterCodexHomeLayout, "homePath" | "helperBinPath">,
platform: NodeJS.Platform = "linux",
windowsSandboxMode: "elevated" | "unelevated" = "elevated",
): CommandCenterCodexIsolation | undefined {
if (
runtimeMode === "full-access" ||
Expand All @@ -1200,7 +1199,7 @@
});
return {
permissionProfile,
...(platform === "win32" ? { windowsSandboxMode } : {}),
...(platform === "win32" ? { windowsSandboxMode: "elevated" as const } : {}),
appServerArgs: [
"--strict-config",
"-c",
Expand Down Expand Up @@ -1239,9 +1238,7 @@
`default_permissions=${JSON.stringify(permissionProfile)}`,
"-c",
`permissions.${permissionProfile}=${profile}`,
...(platform === "win32"
? ["-c", `windows.sandbox=${JSON.stringify(windowsSandboxMode)}`]
: []),
...(platform === "win32" ? ["-c", 'windows.sandbox="elevated"'] : []),
],
};
}
31 changes: 31 additions & 0 deletions apps/web/src/components/settings/ProviderInstanceCard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it } from "vite-plus/test";
import type { ServerProviderModel } from "@t3tools/contracts";

import { deriveProviderModelsForDisplay } from "./ProviderInstanceCard";
import { getProviderMaintenancePresentation } from "./providerStatus";

describe("deriveProviderModelsForDisplay", () => {
it("uses current config custom models instead of stale live custom rows", () => {
Expand Down Expand Up @@ -34,3 +35,33 @@ describe("deriveProviderModelsForDisplay", () => {
).toEqual(["server-model", "kept-custom"]);
});
});

describe("getProviderMaintenancePresentation", () => {
it("offers update or reinstall even when the installed version is current", () => {
expect(
getProviderMaintenancePresentation({
status: "current",
currentVersion: "1.2.3",
latestVersion: "1.2.3",
updateCommand: "npm install -g @openai/codex@latest",
canUpdate: true,
checkedAt: null,
message: null,
}),
).toEqual({ updateCommand: "npm install -g @openai/codex@latest" });
});

it("withholds maintenance when the installation cannot be updated safely", () => {
expect(
getProviderMaintenancePresentation({
status: "unknown",
currentVersion: null,
latestVersion: null,
updateCommand: null,
canUpdate: false,
checkedAt: null,
message: null,
}),
).toBeNull();
});
});
Loading
Loading