From a97992fcf244140ae0bb271c10f0d1e13947f316 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 21 Feb 2026 23:50:22 +0000 Subject: [PATCH] test(pi-tools): share safeBins e2e setup and teardown --- src/agents/pi-tools.safe-bins.e2e.test.ts | 270 +++++++++++----------- 1 file changed, 139 insertions(+), 131 deletions(-) diff --git a/src/agents/pi-tools.safe-bins.e2e.test.ts b/src/agents/pi-tools.safe-bins.e2e.test.ts index 7ccd4ad7b16..051e45dbb8c 100644 --- a/src/agents/pi-tools.safe-bins.e2e.test.ts +++ b/src/agents/pi-tools.safe-bins.e2e.test.ts @@ -118,160 +118,168 @@ async function createSafeBinsExecTool(params: { return { tmpDir, execTool: execTool as ExecTool }; } +async function withSafeBinsExecTool( + params: Parameters[0], + run: (ctx: Awaited>) => Promise, +) { + if (process.platform === "win32") { + return; + } + const ctx = await createSafeBinsExecTool(params); + try { + await run(ctx); + } finally { + fs.rmSync(ctx.tmpDir, { recursive: true, force: true }); + } +} + describe("createOpenClawCodingTools safeBins", () => { it("threads tools.exec.safeBins into exec allowlist checks", async () => { - if (process.platform === "win32") { - return; - } + await withSafeBinsExecTool( + { + tmpPrefix: "openclaw-safe-bins-", + safeBins: ["echo"], + }, + async ({ tmpDir, execTool }) => { + const marker = `safe-bins-${Date.now()}`; + const result = await withEnvAsync( + { OPENCLAW_SHELL_ENV_TIMEOUT_MS: "1000" }, + async () => + await execTool.execute("call1", { + command: `echo ${marker}`, + workdir: tmpDir, + }), + ); + const text = result.content.find((content) => content.type === "text")?.text ?? ""; - const { tmpDir, execTool } = await createSafeBinsExecTool({ - tmpPrefix: "openclaw-safe-bins-", - safeBins: ["echo"], - }); - - const marker = `safe-bins-${Date.now()}`; - const result = await withEnvAsync( - { OPENCLAW_SHELL_ENV_TIMEOUT_MS: "1000" }, - async () => - await execTool.execute("call1", { - command: `echo ${marker}`, - workdir: tmpDir, - }), + const resultDetails = result.details as { status?: string }; + expect(resultDetails.status).toBe("completed"); + expect(text).toContain(marker); + }, ); - const text = result.content.find((content) => content.type === "text")?.text ?? ""; - - const resultDetails = result.details as { status?: string }; - expect(resultDetails.status).toBe("completed"); - expect(text).toContain(marker); }); it("does not allow env var expansion to smuggle file args via safeBins", async () => { - if (process.platform === "win32") { - return; - } - - const { tmpDir, execTool } = await createSafeBinsExecTool({ - tmpPrefix: "openclaw-safe-bins-expand-", - safeBins: ["head", "wc"], - files: [{ name: "secret.txt", contents: "TOP_SECRET\n" }], - }); - - await expect( - execTool.execute("call1", { - command: "head $FOO ; wc -l", - workdir: tmpDir, - env: { FOO: "secret.txt" }, - }), - ).rejects.toThrow("exec denied: allowlist miss"); + await withSafeBinsExecTool( + { + tmpPrefix: "openclaw-safe-bins-expand-", + safeBins: ["head", "wc"], + files: [{ name: "secret.txt", contents: "TOP_SECRET\n" }], + }, + async ({ tmpDir, execTool }) => { + await expect( + execTool.execute("call1", { + command: "head $FOO ; wc -l", + workdir: tmpDir, + env: { FOO: "secret.txt" }, + }), + ).rejects.toThrow("exec denied: allowlist miss"); + }, + ); }); it("does not leak file existence from sort output flags", async () => { - if (process.platform === "win32") { - return; - } + await withSafeBinsExecTool( + { + tmpPrefix: "openclaw-safe-bins-oracle-", + safeBins: ["sort"], + files: [{ name: "existing.txt", contents: "x\n" }], + }, + async ({ tmpDir, execTool }) => { + const run = async (command: string) => { + try { + const result = await execTool.execute("call-oracle", { command, workdir: tmpDir }); + const text = result.content.find((content) => content.type === "text")?.text ?? ""; + const resultDetails = result.details as { status?: string }; + return { kind: "result" as const, status: resultDetails.status, text }; + } catch (err) { + return { kind: "error" as const, message: String(err) }; + } + }; - const { tmpDir, execTool } = await createSafeBinsExecTool({ - tmpPrefix: "openclaw-safe-bins-oracle-", - safeBins: ["sort"], - files: [{ name: "existing.txt", contents: "x\n" }], - }); - - const run = async (command: string) => { - try { - const result = await execTool.execute("call-oracle", { command, workdir: tmpDir }); - const text = result.content.find((content) => content.type === "text")?.text ?? ""; - const resultDetails = result.details as { status?: string }; - return { kind: "result" as const, status: resultDetails.status, text }; - } catch (err) { - return { kind: "error" as const, message: String(err) }; - } - }; - - const existing = await run("sort -o existing.txt"); - const missing = await run("sort -o missing.txt"); - expect(existing).toEqual(missing); + const existing = await run("sort -o existing.txt"); + const missing = await run("sort -o missing.txt"); + expect(existing).toEqual(missing); + }, + ); }); it("blocks sort output flags from writing files via safeBins", async () => { - if (process.platform === "win32") { - return; - } + await withSafeBinsExecTool( + { + tmpPrefix: "openclaw-safe-bins-sort-", + safeBins: ["sort"], + }, + async ({ tmpDir, execTool }) => { + const cases = [ + { command: "sort -oblocked-short.txt", target: "blocked-short.txt" }, + { command: "sort --output=blocked-long.txt", target: "blocked-long.txt" }, + ] as const; - const { tmpDir, execTool } = await createSafeBinsExecTool({ - tmpPrefix: "openclaw-safe-bins-sort-", - safeBins: ["sort"], - }); - - const cases = [ - { command: "sort -oblocked-short.txt", target: "blocked-short.txt" }, - { command: "sort --output=blocked-long.txt", target: "blocked-long.txt" }, - ] as const; - - for (const [index, testCase] of cases.entries()) { - await expect( - execTool.execute(`call${index + 1}`, { - command: testCase.command, - workdir: tmpDir, - }), - ).rejects.toThrow("exec denied: allowlist miss"); - expect(fs.existsSync(path.join(tmpDir, testCase.target))).toBe(false); - } + for (const [index, testCase] of cases.entries()) { + await expect( + execTool.execute(`call${index + 1}`, { + command: testCase.command, + workdir: tmpDir, + }), + ).rejects.toThrow("exec denied: allowlist miss"); + expect(fs.existsSync(path.join(tmpDir, testCase.target))).toBe(false); + } + }, + ); }); it("blocks sort --compress-program from bypassing safeBins", async () => { - if (process.platform === "win32") { - return; - } - - const { tmpDir, execTool } = await createSafeBinsExecTool({ - tmpPrefix: "openclaw-safe-bins-sort-compress-", - safeBins: ["sort"], - }); - - await expect( - execTool.execute("call1", { - command: "sort --compress-program=sh", - workdir: tmpDir, - }), - ).rejects.toThrow("exec denied: allowlist miss"); + await withSafeBinsExecTool( + { + tmpPrefix: "openclaw-safe-bins-sort-compress-", + safeBins: ["sort"], + }, + async ({ tmpDir, execTool }) => { + await expect( + execTool.execute("call1", { + command: "sort --compress-program=sh", + workdir: tmpDir, + }), + ).rejects.toThrow("exec denied: allowlist miss"); + }, + ); }); it("blocks shell redirection metacharacters in safeBins mode", async () => { - if (process.platform === "win32") { - return; - } - - const { tmpDir, execTool } = await createSafeBinsExecTool({ - tmpPrefix: "openclaw-safe-bins-redirect-", - safeBins: ["head"], - files: [{ name: "source.txt", contents: "line1\nline2\n" }], - }); - - await expect( - execTool.execute("call1", { - command: "head -n 1 source.txt > blocked-redirect.txt", - workdir: tmpDir, - }), - ).rejects.toThrow("exec denied: allowlist miss"); - expect(fs.existsSync(path.join(tmpDir, "blocked-redirect.txt"))).toBe(false); + await withSafeBinsExecTool( + { + tmpPrefix: "openclaw-safe-bins-redirect-", + safeBins: ["head"], + files: [{ name: "source.txt", contents: "line1\nline2\n" }], + }, + async ({ tmpDir, execTool }) => { + await expect( + execTool.execute("call1", { + command: "head -n 1 source.txt > blocked-redirect.txt", + workdir: tmpDir, + }), + ).rejects.toThrow("exec denied: allowlist miss"); + expect(fs.existsSync(path.join(tmpDir, "blocked-redirect.txt"))).toBe(false); + }, + ); }); it("blocks grep recursive flags from reading cwd via safeBins", async () => { - if (process.platform === "win32") { - return; - } - - const { tmpDir, execTool } = await createSafeBinsExecTool({ - tmpPrefix: "openclaw-safe-bins-grep-", - safeBins: ["grep"], - files: [{ name: "secret.txt", contents: "SAFE_BINS_RECURSIVE_SHOULD_NOT_LEAK\n" }], - }); - - await expect( - execTool.execute("call1", { - command: "grep -R SAFE_BINS_RECURSIVE_SHOULD_NOT_LEAK", - workdir: tmpDir, - }), - ).rejects.toThrow("exec denied: allowlist miss"); + await withSafeBinsExecTool( + { + tmpPrefix: "openclaw-safe-bins-grep-", + safeBins: ["grep"], + files: [{ name: "secret.txt", contents: "SAFE_BINS_RECURSIVE_SHOULD_NOT_LEAK\n" }], + }, + async ({ tmpDir, execTool }) => { + await expect( + execTool.execute("call1", { + command: "grep -R SAFE_BINS_RECURSIVE_SHOULD_NOT_LEAK", + workdir: tmpDir, + }), + ).rejects.toThrow("exec denied: allowlist miss"); + }, + ); }); });