From b71c91022b58095b7863423cd14ff824959cf45d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 17 Apr 2026 05:27:49 +0100 Subject: [PATCH] test: collapse exec preflight parser cases --- .../bash-tools.exec.script-preflight.test.ts | 570 +++--------------- src/agents/bash-tools.exec.ts | 4 + .../models-config.providers.ollama.test.ts | 34 +- 3 files changed, 95 insertions(+), 513 deletions(-) diff --git a/src/agents/bash-tools.exec.script-preflight.test.ts b/src/agents/bash-tools.exec.script-preflight.test.ts index ecc70e774ec..d71d4c539ea 100644 --- a/src/agents/bash-tools.exec.script-preflight.test.ts +++ b/src/agents/bash-tools.exec.script-preflight.test.ts @@ -4,12 +4,13 @@ import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { __setFsSafeTestHooksForTest } from "../infra/fs-safe.js"; import { withTempDir } from "../test-utils/temp-dir.js"; -import { createExecTool } from "./bash-tools.exec.js"; +import { __testing, createExecTool } from "./bash-tools.exec.js"; const isWin = process.platform === "win32"; const describeNonWin = isWin ? describe.skip : describe; const describeWin = isWin ? describe : describe.skip; +const validateExecScriptPreflight = __testing.validateScriptFileForShellBleed; afterEach(() => { __setFsSafeTestHooksForTest(); @@ -17,7 +18,6 @@ afterEach(() => { async function expectSymlinkSwapDuringPreflightToAvoidErrors(params: { hookName: "afterPreOpenLstat" | "beforeOpen"; - callId: string; }) { await withTempDir("openclaw-exec-preflight-open-race-", async (parent) => { const workdir = path.join(parent, "workdir"); @@ -40,14 +40,13 @@ async function expectSymlinkSwapDuringPreflightToAvoidErrors(params: { }, }); - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - const result = await tool.execute(params.callId, { - command: "node script.js", - workdir, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; + await expect( + validateExecScriptPreflight({ + command: "node script.js", + workdir, + }), + ).resolves.toBeUndefined(); expect(swapped).toBe(true); - expect(text).not.toMatch(/exec preflight:/); }); } @@ -347,28 +346,24 @@ describeNonWin("exec script preflight", () => { await fs.mkdir(workdir, { recursive: true }); await fs.writeFile(outsidePath, "const value = $DM_JSON;", "utf-8"); - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-outside", { - command: "node ../outside.js", - workdir, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).not.toMatch(/exec preflight:/); + await expect( + validateExecScriptPreflight({ + command: "node ../outside.js", + workdir, + }), + ).resolves.toBeUndefined(); }); }); it("does not trust a swapped script pathname between validation and read", async () => { await expectSymlinkSwapDuringPreflightToAvoidErrors({ hookName: "afterPreOpenLstat", - callId: "call-swapped-pathname", }); }); it("handles pre-open symlink swaps without surfacing preflight errors", async () => { await expectSymlinkSwapDuringPreflightToAvoidErrors({ hookName: "beforeOpen", - callId: "call-pre-open-swapped-pathname", }); }); @@ -387,470 +382,75 @@ describeNonWin("exec script preflight", () => { }, }); - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - const result = await tool.execute("call-nonblocking-preflight-open", { - command: "node script.js", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; + await expect( + validateExecScriptPreflight({ + command: "node script.js", + workdir: tmp, + }), + ).resolves.toBeUndefined(); expect(scriptOpenFlags.length).toBeGreaterThan(0); expect(scriptOpenFlags.some((flags) => (flags & fsConstants.O_NONBLOCK) !== 0)).toBe(true); - expect(text).not.toMatch(/exec preflight:/); }); }); - it("fails closed for piped interpreter commands that bypass direct script parsing", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); + const failClosedCases = [ + ["piped interpreter command", "cat bad.py | python"], + ["top-level control-flow", "if true; then python bad.py; fi"], + ["multiline top-level control-flow", "if true; then\npython bad.py\nfi"], + ["shell-wrapped quoted script path", `bash -c "python 'bad.py'"`], + ["top-level control-flow with quoted script path", 'if true; then python "bad.py"; fi'], + ["shell-wrapped interpreter", 'bash -c "python bad.py"'], + ["shell-wrapped control-flow payload", 'bash -c "if true; then python bad.py; fi"'], + ["env-prefixed shell wrapper", 'env bash -c "python bad.py"'], + ["absolute shell path", '/bin/bash -c "python bad.py"'], + ["long option with separate value", 'bash --rcfile shell.rc -c "python bad.py"'], + ["leading long options", 'bash --noprofile --norc -c "python bad.py"'], + ["combined shell flags", 'bash -xc "python bad.py"'], + ["-O option value", 'bash -O extglob -c "python bad.py"'], + ["-o option value", 'bash -o errexit -c "python bad.py"'], + ["-c not trailing short flag", 'bash -ceu "python bad.py"'], + ["process substitution", "python <(cat bad.py)"], + ] as const; - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-pipe", { - command: "cat bad.py | python", - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); + it.each(failClosedCases)("fails closed for %s", async (_name, command) => { + await expect( + validateExecScriptPreflight({ + command, + workdir: process.cwd(), + }), + ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); }); - it("fails closed for top-level interpreter invocations inside shell control-flow", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-top-level-control-flow", { - command: "if true; then python bad.py; fi", - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for multiline top-level control-flow interpreter invocations", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-top-level-control-flow-multiline", { - command: "if true; then\npython bad.py\nfi", - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for shell-wrapped interpreter invocations with quoted script paths", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-shell-wrap-quoted-script", { - command: `bash -c "python '${path.basename(pyPath)}'"`, - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for top-level control-flow with quoted interpreter script paths", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-top-level-control-flow-quoted-script", { - command: 'if true; then python "bad.py"; fi', - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for shell-wrapped interpreter invocations", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-shell-wrap", { - command: 'bash -c "python bad.py"', - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("does not fail closed for shell-wrapped payloads that only echo interpreter words", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-shell-wrap-echo-text", { - command: 'bash -c "echo python"', - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toContain("python"); - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("fails closed for shell-wrapped interpreter invocations inside control-flow payloads", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-shell-wrap-control-flow", { - command: 'bash -c "if true; then python bad.py; fi"', - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for env-prefixed shell-wrapped interpreter invocations", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-env-shell-wrap", { - command: 'env bash -c "python bad.py"', - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for shell-wrapped interpreter invocations via absolute shell paths", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-shell-wrap-abs-path", { - command: '/bin/bash -c "python bad.py"', - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for shell-wrapped interpreter invocations when long options take separate values", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - await fs.writeFile(path.join(tmp, "shell.rc"), "# rc", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-shell-wrap-long-option-value", { - command: 'bash --rcfile shell.rc -c "python bad.py"', - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for shell-wrapped interpreter invocations with leading long options", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-shell-wrap-long-options", { - command: 'bash --noprofile --norc -c "python bad.py"', - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for shell-wrapped interpreter invocations with combined shell flags", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-shell-wrap-combined", { - command: 'bash -xc "python bad.py"', - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for shell-wrapped interpreter invocations when -O consumes a separate value", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-shell-wrap-short-option-O-value", { - command: 'bash -O extglob -c "python bad.py"', - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for shell-wrapped interpreter invocations when -o consumes a separate value", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-shell-wrap-short-option-o-value", { - command: 'bash -o errexit -c "python bad.py"', - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for shell-wrapped interpreter invocations when -c is not the trailing short flag", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-shell-wrap-short-flags", { - command: 'bash -ceu "python bad.py"', - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("fails closed for process-substitution interpreter invocations", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const pyPath = path.join(tmp, "bad.py"); - await fs.writeFile(pyPath, "payload = $DM_JSON", "utf-8"); - - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - await expect( - tool.execute("call-process-substitution", { - command: "python <(cat bad.py)", - workdir: tmp, - }), - ).rejects.toThrow(/exec preflight: complex interpreter invocation detected/); - }); - }); - - it("allows direct inline interpreter commands with no script file hint", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-inline", { - command: 'node -e "console.log(123)"', - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toContain("123"); - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("does not fail closed when interpreter and script hints only appear in echoed text", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-echo-text", { - command: "echo 'python bad.py | python'", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toContain("python bad.py | python"); - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("does not fail closed when shell keyword-like text appears only as echo arguments", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-echo-keyword-like-text", { - command: "echo time python bad.py; cat", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toContain("time python bad.py"); - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("does not fail closed for pipelines that only contain interpreter words as plain text", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-echo-pipe-text", { - command: "echo python | cat", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toContain("python"); - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("does not fail closed for non-executing pipelines that only print interpreter words", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-printf-pipe-text", { - command: "printf node | wc -c", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toContain("4"); - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("does not fail closed when script-like text is in a separate command segment", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-separate-script-hint-segment", { - command: "echo bad.py; python --version", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toContain("bad.py"); - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("does not fail closed when script hints appear outside the interpreter segment with &&", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - await fs.writeFile(path.join(tmp, "sample.py"), "print('ok')", "utf-8"); - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-interpreter-version-and-list", { - command: "node --version && ls *.py", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toContain("sample.py"); - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("does not fail closed for piped interpreter version commands with script-like upstream text", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-piped-interpreter-version", { - command: "echo bad.py | node --version", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toMatch(/v\d+/); - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("does not fail closed for piped node -c syntax-check commands with script-like upstream text", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - await fs.writeFile(path.join(tmp, "ok.js"), "console.log('ok')", "utf-8"); - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-piped-node-check", { - command: "echo bad.py | node -c ok.js", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("does not fail closed for piped node -e commands when inline code contains script-like text", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-piped-node-e-inline-script-hint", { - command: "node -e \"console.log('bad.py')\" | cat", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toContain("bad.py"); - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("does not fail closed when shell operator characters are escaped", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-echo-escaped-operator", { - command: "echo python bad.py \\| node", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toContain("python bad.py | node"); - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("does not fail closed when escaped semicolons appear with interpreter hints", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-echo-escaped-semicolon", { - command: "echo python bad.py \\; node", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toContain("python bad.py ; node"); - expect(text).not.toMatch(/exec preflight:/); - }); - }); - - it("does not fail closed for node -e when .py appears inside quoted inline code", async () => { - await withTempDir("openclaw-exec-preflight-", async (tmp) => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); - - const result = await tool.execute("call-inline-script-hint", { - command: "node -e \"console.log('bad.py')\"", - workdir: tmp, - }); - const text = result.content.find((block) => block.type === "text")?.text ?? ""; - expect(text).toContain("bad.py"); - expect(text).not.toMatch(/exec preflight:/); - }); + const passCases = [ + ["shell-wrapped echoed interpreter words", 'bash -c "echo python"'], + ["direct inline interpreter command", 'node -e "console.log(123)"'], + ["interpreter and script hints only in echoed text", "echo 'python bad.py | python'"], + ["shell keyword-like text only as echo arguments", "echo time python bad.py; cat"], + ["pipeline containing only interpreter words as plain text", "echo python | cat"], + ["non-executing pipeline that only prints interpreter words", "printf node | wc -c"], + ["script-like text in a separate command segment", "echo bad.py; python --version"], + ["script hints outside interpreter segment with &&", "node --version && ls *.py"], + [ + "piped interpreter version command with script-like upstream text", + "echo bad.py | node --version", + ], + ["piped node -c command with script-like upstream text", "echo bad.py | node -c ok.js"], + [ + "piped node -e command with inline script-like text", + "node -e \"console.log('bad.py')\" | cat", + ], + ["escaped shell operator characters", "echo python bad.py \\| node"], + ["escaped semicolons with interpreter hints", "echo python bad.py \\; node"], + ["node -e with .py inside quoted inline code", "node -e \"console.log('bad.py')\""], + ] as const; + + it.each(passCases)("does not fail closed for %s", async (_name, command) => { + await expect( + validateExecScriptPreflight({ + command, + workdir: process.cwd(), + }), + ).resolves.toBeUndefined(); }); }); @@ -917,29 +517,13 @@ describeWin("exec script preflight on windows path syntax", () => { describe("exec interpreter heuristics ReDoS guard", () => { it("does not hang on long commands with VAR=value assignments and whitespace-heavy text", async () => { - const tool = createExecTool({ host: "gateway", security: "full", ask: "off" }); // Simulate a heredoc with HTML content after a VAR= assignment. Keep the - // command-substitution failure local so the test measures parser behavior, - // not external network timing. + // command parser check direct so no shell process timing hides regex cost. const htmlBlock = '
'.repeat(50); const command = `ACCESS_TOKEN=$(__openclaw_missing_redos_guard__)\ncat > /tmp/out.html << 'EOF'\n${htmlBlock}\nEOF`; const start = Date.now(); - // The command itself will fail — we only care that the interpreter - // heuristics analysis completes without hanging. - try { - await Promise.race([ - tool.execute("redos-guard", { command }), - new Promise((_, reject) => - setTimeout(() => reject(new Error("ReDoS: regex hung for >5s")), 5000), - ), - ]); - } catch (e) { - // Any error EXCEPT the timeout is acceptable — it means the regex finished - if (e instanceof Error && e.message.includes("ReDoS")) { - throw e; - } - } + await validateExecScriptPreflight({ command, workdir: process.cwd() }); const elapsed = Date.now() - start; expect(elapsed).toBeLessThan(5000); }); diff --git a/src/agents/bash-tools.exec.ts b/src/agents/bash-tools.exec.ts index 76ee948dbdc..af18e7d3891 100644 --- a/src/agents/bash-tools.exec.ts +++ b/src/agents/bash-tools.exec.ts @@ -1784,3 +1784,7 @@ export function createExecTool( } export const execTool = createExecTool(); + +export const __testing = { + validateScriptFileForShellBleed, +}; diff --git a/src/agents/models-config.providers.ollama.test.ts b/src/agents/models-config.providers.ollama.test.ts index 19f816cf1b5..416cd7a5eec 100644 --- a/src/agents/models-config.providers.ollama.test.ts +++ b/src/agents/models-config.providers.ollama.test.ts @@ -12,8 +12,7 @@ import { import type { ProviderPlugin } from "../plugins/types.js"; import { withFetchPreconnect } from "../test-utils/fetch-mock.js"; import { OLLAMA_LOCAL_AUTH_MARKER } from "./model-auth-markers.js"; -import { resolveImplicitProviders } from "./models-config.providers.js"; -import type { ProviderConfig } from "./models-config.providers.js"; +import type { ProviderConfig } from "./models-config.providers.secrets.js"; afterEach(() => { vi.unstubAllEnvs(); @@ -49,15 +48,6 @@ describe("Ollama provider", () => { } } - async function resolveProvidersWithOllamaKey(agentDir: string) { - return withOllamaApiKey(() => - resolveProvidersWithOllamaOnly({ - agentDir, - env: { VITEST: "", NODE_ENV: "development" }, - }), - ); - } - async function resolveProvidersWithOllamaOnly(params: { agentDir: string; explicitProviders?: Record; @@ -71,6 +61,7 @@ describe("Ollama provider", () => { ...params.env, } satisfies NodeJS.ProcessEnv; + const { resolveImplicitProviders } = await import("./models-config.providers.implicit.js"); return resolveImplicitProviders({ agentDir: params.agentDir, explicitProviders: params.explicitProviders, @@ -202,7 +193,6 @@ describe("Ollama provider", () => { }); it("discovers per-model context windows from /api/show", async () => { - const agentDir = createAgentDir(); enableDiscoveryEnv(); const fetchMock = vi.fn(async (input: unknown, init?: RequestInit) => { const url = String(input); @@ -230,8 +220,10 @@ describe("Ollama provider", () => { }); vi.stubGlobal("fetch", withFetchPreconnect(fetchMock)); - const providers = await resolveProvidersWithOllamaKey(agentDir); - const models = providers?.ollama?.models ?? []; + const provider = await runOllamaCatalog({ + env: { OLLAMA_API_KEY: "test-key", VITEST: "", NODE_ENV: "development" }, + }); + const models = provider?.models ?? []; const qwen = models.find((model) => model.id === "qwen3:32b"); const llama = models.find((model) => model.id === "llama3.3:70b"); expect(qwen?.contextWindow).toBe(131072); @@ -325,7 +317,6 @@ describe("Ollama provider", () => { }); it("falls back to default context window when /api/show fails", async () => { - const agentDir = createAgentDir(); enableDiscoveryEnv(); const fetchMock = vi.fn(async (input: unknown) => { const url = String(input); @@ -342,14 +333,15 @@ describe("Ollama provider", () => { }); vi.stubGlobal("fetch", withFetchPreconnect(fetchMock)); - const providers = await resolveProvidersWithOllamaKey(agentDir); - const model = providers?.ollama?.models?.find((entry) => entry.id === "qwen3:32b"); + const provider = await runOllamaCatalog({ + env: { OLLAMA_API_KEY: "test-key", VITEST: "", NODE_ENV: "development" }, + }); + const model = provider?.models?.find((entry) => entry.id === "qwen3:32b"); expect(model?.contextWindow).toBe(128000); expectDiscoveryCallCounts(fetchMock, { tags: 1, show: 1 }); }); it("caps /api/show requests when /api/tags returns a very large model list", async () => { - const agentDir = createAgentDir(); enableDiscoveryEnv(); const manyModels = Array.from({ length: 250 }, (_, idx) => ({ name: `model-${idx}`, @@ -372,8 +364,10 @@ describe("Ollama provider", () => { }); vi.stubGlobal("fetch", withFetchPreconnect(fetchMock)); - const providers = await resolveProvidersWithOllamaKey(agentDir); - const models = providers?.ollama?.models ?? []; + const provider = await runOllamaCatalog({ + env: { OLLAMA_API_KEY: "test-key", VITEST: "", NODE_ENV: "development" }, + }); + const models = provider?.models ?? []; // 1 call for /api/tags + 200 capped /api/show calls. expectDiscoveryCallCounts(fetchMock, { tags: 1, show: 200 }); expect(models).toHaveLength(200);