diff --git a/.changeset/exec-cwd-fallback.md b/.changeset/exec-cwd-fallback.md new file mode 100644 index 00000000..d167e8a7 --- /dev/null +++ b/.changeset/exec-cwd-fallback.md @@ -0,0 +1,5 @@ +--- +"@perstack/base": patch +--- + +Fallback to workspace when exec cwd is outside allowed directories diff --git a/apps/base/src/tools/exec.test.ts b/apps/base/src/tools/exec.test.ts index 73fc2337..4fc2ba08 100644 --- a/apps/base/src/tools/exec.test.ts +++ b/apps/base/src/tools/exec.test.ts @@ -110,4 +110,17 @@ describe("exec tool", () => { }) expect(result.output).toBeTruthy() }) + + it("falls back to workspace when cwd is outside allowed directories", async () => { + const result = await exec({ + command: process.execPath, + args: ["-e", 'process.stdout.write("OK")'], + env: {}, + cwd: "/home/user", + stdout: true, + stderr: false, + timeout: 2000, + }) + expect(result.output).toBe("OK") + }) }) diff --git a/apps/base/src/tools/exec.ts b/apps/base/src/tools/exec.ts index a535bfa1..d9c91ecf 100644 --- a/apps/base/src/tools/exec.ts +++ b/apps/base/src/tools/exec.ts @@ -3,7 +3,7 @@ import { promisify } from "node:util" import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" import { getFilteredEnv, truncateText } from "@perstack/core" import { z } from "zod/v4" -import { validatePath } from "../lib/path.js" +import { validatePath, workspacePath } from "../lib/path.js" import { successToolResult } from "../lib/tool-result.js" const execFileAsync = promisify(execFile) @@ -20,10 +20,18 @@ type ExecInput = { stderr: boolean timeout?: number } +async function resolveCwd(cwd: string): Promise { + try { + return await validatePath(cwd) + } catch { + return workspacePath + } +} + export async function exec(input: ExecInput) { - const validatedCwd = await validatePath(input.cwd) + const resolvedCwd = await resolveCwd(input.cwd) const { stdout, stderr } = await execFileAsync(input.command, input.args, { - cwd: validatedCwd, + cwd: resolvedCwd, env: getFilteredEnv(input.env), timeout: input.timeout, maxBuffer: 10 * 1024 * 1024,