diff --git a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-d.e2e.test.ts b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-d.e2e.test.ts index ee071861c8..79aa9f2ede 100644 --- a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-d.e2e.test.ts +++ b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-d.e2e.test.ts @@ -6,6 +6,7 @@ import { describe, expect, it } from "vitest"; import "./test-helpers/fast-coding-tools.js"; import { createOpenClawCodingTools } from "./pi-tools.js"; import { createHostSandboxFsBridge } from "./test-helpers/host-sandbox-fs-bridge.js"; +import { createPiToolsSandboxContext } from "./test-helpers/pi-tools-sandbox-context.js"; const defaultTools = createOpenClawCodingTools(); @@ -74,32 +75,16 @@ describe("createOpenClawCodingTools", () => { }); it("filters tools by sandbox policy", () => { const sandboxDir = path.join(os.tmpdir(), "moltbot-sandbox"); - const sandbox = { - enabled: true, - sessionKey: "sandbox:test", + const sandbox = createPiToolsSandboxContext({ workspaceDir: sandboxDir, agentWorkspaceDir: path.join(os.tmpdir(), "moltbot-workspace"), workspaceAccess: "none" as const, - containerName: "openclaw-sbx-test", - containerWorkdir: "/workspace", fsBridge: createHostSandboxFsBridge(sandboxDir), - docker: { - image: "openclaw-sandbox:bookworm-slim", - containerPrefix: "openclaw-sbx-", - workdir: "/workspace", - readOnlyRoot: true, - tmpfs: [], - network: "none", - user: "1000:1000", - capDrop: ["ALL"], - env: { LANG: "C.UTF-8" }, - }, tools: { allow: ["bash"], deny: ["browser"], }, - browserAllowHostControl: false, - }; + }); const tools = createOpenClawCodingTools({ sandbox }); expect(tools.some((tool) => tool.name === "exec")).toBe(true); expect(tools.some((tool) => tool.name === "read")).toBe(false); @@ -107,32 +92,16 @@ describe("createOpenClawCodingTools", () => { }); it("hard-disables write/edit when sandbox workspaceAccess is ro", () => { const sandboxDir = path.join(os.tmpdir(), "moltbot-sandbox"); - const sandbox = { - enabled: true, - sessionKey: "sandbox:test", + const sandbox = createPiToolsSandboxContext({ workspaceDir: sandboxDir, agentWorkspaceDir: path.join(os.tmpdir(), "moltbot-workspace"), workspaceAccess: "ro" as const, - containerName: "openclaw-sbx-test", - containerWorkdir: "/workspace", fsBridge: createHostSandboxFsBridge(sandboxDir), - docker: { - image: "openclaw-sandbox:bookworm-slim", - containerPrefix: "openclaw-sbx-", - workdir: "/workspace", - readOnlyRoot: true, - tmpfs: [], - network: "none", - user: "1000:1000", - capDrop: ["ALL"], - env: { LANG: "C.UTF-8" }, - }, tools: { allow: ["read", "write", "edit"], deny: [], }, - browserAllowHostControl: false, - }; + }); const tools = createOpenClawCodingTools({ sandbox }); expect(tools.some((tool) => tool.name === "read")).toBe(true); expect(tools.some((tool) => tool.name === "write")).toBe(false); diff --git a/src/agents/pi-tools.sandbox-mounted-paths.workspace-only.test.ts b/src/agents/pi-tools.sandbox-mounted-paths.workspace-only.test.ts index c26a446224..1d08f1a90c 100644 --- a/src/agents/pi-tools.sandbox-mounted-paths.workspace-only.test.ts +++ b/src/agents/pi-tools.sandbox-mounted-paths.workspace-only.test.ts @@ -7,6 +7,7 @@ import { createOpenClawCodingTools } from "./pi-tools.js"; import type { SandboxContext } from "./sandbox.js"; import type { SandboxFsBridge, SandboxResolvedPath } from "./sandbox/fs-bridge.js"; import { createSandboxFsBridgeFromResolver } from "./test-helpers/host-sandbox-fs-bridge.js"; +import { createPiToolsSandboxContext } from "./test-helpers/pi-tools-sandbox-context.js"; vi.mock("../infra/shell-env.js", async (importOriginal) => { const mod = await importOriginal(); @@ -63,29 +64,13 @@ function createSandbox(params: { agentRoot: string; fsBridge: SandboxFsBridge; }): SandboxContext { - return { - enabled: true, - sessionKey: "sandbox:test", + return createPiToolsSandboxContext({ workspaceDir: params.sandboxRoot, agentWorkspaceDir: params.agentRoot, workspaceAccess: "rw", - containerName: "openclaw-sbx-test", - containerWorkdir: "/workspace", fsBridge: params.fsBridge, - docker: { - image: "openclaw-sandbox:bookworm-slim", - containerPrefix: "openclaw-sbx-", - workdir: "/workspace", - readOnlyRoot: true, - tmpfs: [], - network: "none", - user: "1000:1000", - capDrop: ["ALL"], - env: { LANG: "C.UTF-8" }, - }, tools: { allow: [], deny: [] }, - browserAllowHostControl: false, - }; + }); } async function withUnsafeMountedSandboxHarness( diff --git a/src/agents/pi-tools.workspace-paths.e2e.test.ts b/src/agents/pi-tools.workspace-paths.e2e.test.ts index bef983e59d..de0d738271 100644 --- a/src/agents/pi-tools.workspace-paths.e2e.test.ts +++ b/src/agents/pi-tools.workspace-paths.e2e.test.ts @@ -4,6 +4,7 @@ import path from "node:path"; import { describe, expect, it, vi } from "vitest"; import { createOpenClawCodingTools } from "./pi-tools.js"; import { createHostSandboxFsBridge } from "./test-helpers/host-sandbox-fs-bridge.js"; +import { createPiToolsSandboxContext } from "./test-helpers/pi-tools-sandbox-context.js"; vi.mock("../infra/shell-env.js", async (importOriginal) => { const mod = await importOriginal(); @@ -157,29 +158,13 @@ describe("sandboxed workspace paths", () => { it("uses sandbox workspace for relative read/write/edit", async () => { await withTempDir("openclaw-sandbox-", async (sandboxDir) => { await withTempDir("openclaw-workspace-", async (workspaceDir) => { - const sandbox = { - enabled: true, - sessionKey: "sandbox:test", + const sandbox = createPiToolsSandboxContext({ workspaceDir: sandboxDir, agentWorkspaceDir: workspaceDir, workspaceAccess: "rw" as const, - containerName: "openclaw-sbx-test", - containerWorkdir: "/workspace", fsBridge: createHostSandboxFsBridge(sandboxDir), - docker: { - image: "openclaw-sandbox:bookworm-slim", - containerPrefix: "openclaw-sbx-", - workdir: "/workspace", - readOnlyRoot: true, - tmpfs: [], - network: "none", - user: "1000:1000", - capDrop: ["ALL"], - env: { LANG: "C.UTF-8" }, - }, tools: { allow: [], deny: [] }, - browserAllowHostControl: false, - }; + }); const testFile = "sandbox.txt"; await fs.writeFile(path.join(sandboxDir, testFile), "sandbox read", "utf8"); diff --git a/src/agents/test-helpers/pi-tools-sandbox-context.test.ts b/src/agents/test-helpers/pi-tools-sandbox-context.test.ts new file mode 100644 index 0000000000..63e09b6f7c --- /dev/null +++ b/src/agents/test-helpers/pi-tools-sandbox-context.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from "vitest"; +import { createPiToolsSandboxContext } from "./pi-tools-sandbox-context.js"; + +describe("createPiToolsSandboxContext", () => { + it("provides stable defaults for pi-tools sandbox tests", () => { + const sandbox = createPiToolsSandboxContext({ + workspaceDir: "/tmp/sandbox", + }); + + expect(sandbox.enabled).toBe(true); + expect(sandbox.sessionKey).toBe("sandbox:test"); + expect(sandbox.workspaceDir).toBe("/tmp/sandbox"); + expect(sandbox.agentWorkspaceDir).toBe("/tmp/sandbox"); + expect(sandbox.workspaceAccess).toBe("rw"); + expect(sandbox.containerName).toBe("openclaw-sbx-test"); + expect(sandbox.containerWorkdir).toBe("/workspace"); + expect(sandbox.docker.image).toBe("openclaw-sandbox:bookworm-slim"); + expect(sandbox.docker.containerPrefix).toBe("openclaw-sbx-"); + expect(sandbox.tools).toEqual({ allow: [], deny: [] }); + expect(sandbox.browserAllowHostControl).toBe(false); + }); + + it("applies provided overrides", () => { + const sandbox = createPiToolsSandboxContext({ + workspaceDir: "/tmp/sandbox", + agentWorkspaceDir: "/tmp/workspace", + workspaceAccess: "ro", + tools: { allow: ["read"], deny: ["exec"] }, + browserAllowHostControl: true, + dockerOverrides: { + readOnlyRoot: false, + tmpfs: ["/tmp"], + }, + }); + + expect(sandbox.agentWorkspaceDir).toBe("/tmp/workspace"); + expect(sandbox.workspaceAccess).toBe("ro"); + expect(sandbox.tools).toEqual({ allow: ["read"], deny: ["exec"] }); + expect(sandbox.browserAllowHostControl).toBe(true); + expect(sandbox.docker.readOnlyRoot).toBe(false); + expect(sandbox.docker.tmpfs).toEqual(["/tmp"]); + }); +}); diff --git a/src/agents/test-helpers/pi-tools-sandbox-context.ts b/src/agents/test-helpers/pi-tools-sandbox-context.ts new file mode 100644 index 0000000000..286c5eed68 --- /dev/null +++ b/src/agents/test-helpers/pi-tools-sandbox-context.ts @@ -0,0 +1,43 @@ +import type { SandboxContext, SandboxToolPolicy, SandboxWorkspaceAccess } from "../sandbox.js"; +import type { SandboxFsBridge } from "../sandbox/fs-bridge.js"; + +type PiToolsSandboxContextParams = { + workspaceDir: string; + agentWorkspaceDir?: string; + workspaceAccess?: SandboxWorkspaceAccess; + fsBridge?: SandboxFsBridge; + tools?: SandboxToolPolicy; + browserAllowHostControl?: boolean; + sessionKey?: string; + containerName?: string; + containerWorkdir?: string; + dockerOverrides?: Partial; +}; + +export function createPiToolsSandboxContext(params: PiToolsSandboxContextParams): SandboxContext { + const workspaceDir = params.workspaceDir; + return { + enabled: true, + sessionKey: params.sessionKey ?? "sandbox:test", + workspaceDir, + agentWorkspaceDir: params.agentWorkspaceDir ?? workspaceDir, + workspaceAccess: params.workspaceAccess ?? "rw", + containerName: params.containerName ?? "openclaw-sbx-test", + containerWorkdir: params.containerWorkdir ?? "/workspace", + fsBridge: params.fsBridge, + docker: { + image: "openclaw-sandbox:bookworm-slim", + containerPrefix: "openclaw-sbx-", + workdir: "/workspace", + readOnlyRoot: true, + tmpfs: [], + network: "none", + user: "1000:1000", + capDrop: ["ALL"], + env: { LANG: "C.UTF-8" }, + ...params.dockerOverrides, + }, + tools: params.tools ?? { allow: [], deny: [] }, + browserAllowHostControl: params.browserAllowHostControl ?? false, + }; +}