Files
PaperClipAI/tests/runner-e2e/codex-planning-read-only.test.ts
T
DottaandClaude Opus 5.5 d221501fb5 fix(runner): keep Codex planning read-only inside Daytona without network namespaces
Codex planning runs use the paperclip-runner-workspace-read-only profile.
Its restricted reads made Codex load AGENTS.md through the filesystem
helper, which always runs bubblewrap with --unshare-net. Daytona has no
CAP_NET_ADMIN, so thread creation failed with "bwrap: loopback: Failed
RTM_NEWADDR". Inside an external sandbox the planning profile now reads the
whole filesystem, writes nowhere and enables network, so Codex reads files
directly and runs commands without a new network namespace while still
refusing writes. Adds a manual codex-planning-read-only e2e suite that
proves a planning write attempt is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 07:41:36 -06:00

34 lines
1.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { runnerMatrix } from "./catalog.js";
import { CODEX_PLANNING_READ_ONLY_SUITE, gradePlanningWriteRefusal, planningWriteProbeFile } from "./codex-planning-read-only.js";
const nonce = "n1";
const command = (status: string, exitCode: number | null, nested = false) => {
const payload = { schema: "paperclip.tool.execution.v1", name: `/bin/bash -lc 'printf probe > ${planningWriteProbeFile(nonce)}'`, status, exitCode };
return { eventType: "tool.execution.completed", payload: nested ? { prpEvent: { payload } } : payload };
};
describe("Codex planning read-only grading", () => {
it("passes only when the planning write attempt was refused", () => {
expect(gradePlanningWriteRefusal([command("failed", 1)], nonce)).toMatchObject({ passed: true });
expect(gradePlanningWriteRefusal([command("declined", null, true)], nonce)).toMatchObject({ passed: true });
expect(gradePlanningWriteRefusal([command("failed", 1), command("completed", 0)], nonce))
.toMatchObject({ passed: false, failure: expect.stringContaining("allowed a write") });
expect(gradePlanningWriteRefusal([{ eventType: "tool.execution.completed", payload: { name: "ls" } }], nonce))
.toMatchObject({ passed: false, failure: expect.stringContaining("No completed shell attempt") });
});
it("runs only as explicit native Codex planning cells", () => {
const cells = runnerMatrix.filter(entry => entry.suite.id === CODEX_PLANNING_READ_ONLY_SUITE);
expect(cells.map(cell => cell.id).sort()).toEqual([
"codex-planning-read-only.runner-codex.daytona.plan-write-refused",
"codex-planning-read-only.runner-codex.local.plan-write-refused",
]);
for (const cell of cells) {
expect(cell.suite.manualOnly).toBe(true);
expect(cell.task.workMode).toBe("planning");
expect(cell.task.flow).toBe("plan_approval_completion");
}
});
});