diff --git a/packages/adapter-utils/src/command-managed-runtime.test.ts b/packages/adapter-utils/src/command-managed-runtime.test.ts index d9eb811780f..08d226c96ef 100644 --- a/packages/adapter-utils/src/command-managed-runtime.test.ts +++ b/packages/adapter-utils/src/command-managed-runtime.test.ts @@ -10,6 +10,7 @@ import { prepareCommandManagedRuntime, type CommandManagedRuntimeRunner, } from "./command-managed-runtime.js"; +import { SANDBOX_READ_FILE_TOO_LARGE_ERROR_CODE } from "./sandbox-managed-runtime.js"; import type { RunProcessResult } from "./server-utils.js"; const execFile = promisify(execFileCallback); @@ -396,6 +397,25 @@ describe("command managed runtime", () => { expect(progress.at(-1)?.done).toBe(payload.length); }); + it("rejects oversized downloads after the remote size probe", async () => { + const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-command-read-max-")); + cleanupDirs.push(rootDir); + const remotePath = path.join(rootDir, "download.bin"); + await writeFile(remotePath, Buffer.alloc(1024)); + + const { runner, calls } = makeSpawnRunner(); + const client = createCommandManagedRuntimeClient({ runner, commandCwd: "/", timeoutMs: 30_000 }); + + await expect(client.readFile(remotePath, { maxBytes: 16 })).rejects.toMatchObject({ + code: SANDBOX_READ_FILE_TOO_LARGE_ERROR_CODE, + actualBytes: 1024, + maxBytes: 16, + }); + + expect(calls.some((call) => call.args?.join(" ").includes("wc -c"))).toBe(true); + expect(calls.some((call) => call.args?.join(" ").includes("dd if="))).toBe(false); + }); + it("includes stdout diagnostics when a managed runtime command fails", async () => { const startedAt = new Date().toISOString(); const runner: CommandManagedRuntimeRunner = { diff --git a/packages/adapter-utils/src/sandbox-managed-runtime.test.ts b/packages/adapter-utils/src/sandbox-managed-runtime.test.ts index d23660a5e6b..00bf4e5321d 100644 --- a/packages/adapter-utils/src/sandbox-managed-runtime.test.ts +++ b/packages/adapter-utils/src/sandbox-managed-runtime.test.ts @@ -599,6 +599,99 @@ describe("sandbox managed runtime", () => { expect(lines.join("")).not.toContain("acct-sandbox"); }); + it("does not sync sandbox API-key Codex auth.json back to subscription host auth", async () => { + const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-sandbox-codex-auth-apikey-")); + cleanupDirs.push(rootDir); + const localWorkspaceDir = path.join(rootDir, "local-workspace"); + const remoteWorkspaceDir = path.join(rootDir, "remote-workspace"); + const localCodexHome = path.join(rootDir, "local-codex-home"); + const hostCodexHome = path.join(rootDir, "host-codex-home"); + const hostAuthSource = path.join(hostCodexHome, "auth.json"); + await mkdir(localWorkspaceDir, { recursive: true }); + await mkdir(localCodexHome, { recursive: true }); + await mkdir(hostCodexHome, { recursive: true }); + + const hostAuth = codexSubscriptionAuth({ + lastRefresh: "2026-01-01T00:00:00.000Z", + refreshToken: "refresh-host-before-apikey", + }); + const sandboxApiKey = "sk-sandbox-secret-should-not-copy-back"; + const sandboxAuth = `${JSON.stringify({ OPENAI_API_KEY: sandboxApiKey })}\n`; + await writeFile(hostAuthSource, hostAuth, { mode: 0o600 }); + await symlink(hostAuthSource, path.join(localCodexHome, "auth.json")); + + const lines: string[] = []; + const prepared = await prepareSandboxManagedRuntime({ + spec: { + transport: "sandbox", + provider: "test", + sandboxId: "sandbox-1", + remoteCwd: remoteWorkspaceDir, + timeoutMs: 30_000, + apiKey: null, + }, + adapterKey: "codex", + client: createLocalSandboxClient(), + workspaceLocalDir: localWorkspaceDir, + assets: [{ key: "home", localDir: localCodexHome, followSymlinks: true }], + onProgress: (line) => { + lines.push(line); + }, + }); + await writeFile(path.join(prepared.assetDirs.home, "auth.json"), sandboxAuth, "utf8"); + + await prepared.restoreWorkspace(); + + await expect(readFile(hostAuthSource, "utf8")).resolves.toBe(hostAuth); + expect(lines.join("")).toContain("API-key auth is not synced back"); + expect(lines.join("")).not.toContain(sandboxApiKey); + }); + + it("skips Codex auth.json sync-back when sandbox auth is missing", async () => { + const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-sandbox-codex-auth-missing-")); + cleanupDirs.push(rootDir); + const localWorkspaceDir = path.join(rootDir, "local-workspace"); + const remoteWorkspaceDir = path.join(rootDir, "remote-workspace"); + const localCodexHome = path.join(rootDir, "local-codex-home"); + const hostCodexHome = path.join(rootDir, "host-codex-home"); + const hostAuthSource = path.join(hostCodexHome, "auth.json"); + await mkdir(localWorkspaceDir, { recursive: true }); + await mkdir(localCodexHome, { recursive: true }); + await mkdir(hostCodexHome, { recursive: true }); + + const hostAuth = codexSubscriptionAuth({ + lastRefresh: "2026-01-01T00:00:00.000Z", + refreshToken: "refresh-host-before-missing", + }); + await writeFile(hostAuthSource, hostAuth, { mode: 0o600 }); + await symlink(hostAuthSource, path.join(localCodexHome, "auth.json")); + + const lines: string[] = []; + const prepared = await prepareSandboxManagedRuntime({ + spec: { + transport: "sandbox", + provider: "test", + sandboxId: "sandbox-1", + remoteCwd: remoteWorkspaceDir, + timeoutMs: 30_000, + apiKey: null, + }, + adapterKey: "codex", + client: createLocalSandboxClient(), + workspaceLocalDir: localWorkspaceDir, + assets: [{ key: "home", localDir: localCodexHome, followSymlinks: true }], + onProgress: (line) => { + lines.push(line); + }, + }); + await rm(path.join(prepared.assetDirs.home, "auth.json"), { force: true }); + + await prepared.restoreWorkspace(); + + await expect(readFile(hostAuthSource, "utf8")).resolves.toBe(hostAuth); + expect(lines.join("")).toContain("sandbox auth.json is missing"); + }); + it("does not sync Codex auth.json back through a regular host auth file", async () => { const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-sandbox-codex-auth-regular-")); cleanupDirs.push(rootDir); diff --git a/packages/adapter-utils/src/sandbox-managed-runtime.ts b/packages/adapter-utils/src/sandbox-managed-runtime.ts index 630db3fdca9..849369aca8d 100644 --- a/packages/adapter-utils/src/sandbox-managed-runtime.ts +++ b/packages/adapter-utils/src/sandbox-managed-runtime.ts @@ -117,7 +117,11 @@ type ParsedCodexAuth = }; type CodexAuthTokenField = "id_token" | "access_token" | "refresh_token"; -const CODEX_AUTH_TOKEN_FIELDS = ["id_token", "access_token", "refresh_token"] as const satisfies readonly CodexAuthTokenField[]; +const CODEX_AUTH_TOKEN_FIELDS = [ + "id_token", + "access_token", + "refresh_token", +] as const satisfies readonly CodexAuthTokenField[]; interface CanonicalCodexSubscriptionAuth { tokens: { account_id: string } & Partial>;