mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-15 10:21:41 +01:00
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>
34 lines
1.9 KiB
TypeScript
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");
|
|
}
|
|
});
|
|
});
|