import { afterEach, describe, expect, it, vi } from "vitest"; import { makeExecutable, makeExecApprovalsTempDir, } from "../../infra/exec-approvals-test-helpers.js"; import { loadExecApprovals, saveExecApprovals } from "../../infra/exec-approvals.js"; import type { CliBackendToolPermissionResult } from "../../plugins/cli-backend.types.js"; import { callGatewayTool } from "../tools/gateway.js"; import { closePluginTestAdmissions, createExecution, runPlugin, SUCCESS_RESULT, } from "./execute-plugin.test-support.js"; vi.mock("../tools/gateway.js", () => ({ callGatewayTool: vi.fn() })); const mockCallGatewayTool = vi.mocked(callGatewayTool); afterEach(() => { vi.unstubAllEnvs(); closePluginTestAdmissions(); mockCallGatewayTool.mockReset(); }); describe("native Bash execution policy", () => { it.each([ ["allowlist", "on-miss", "allow"], ["deny", "on-miss", "deny"], ["allowlist", "off", "deny"], ] as const)( "applies %s/%s to native Bash with configured PATH", async (security, ask, behavior) => { const dir = makeExecApprovalsTempDir(); vi.stubEnv("OPENCLAW_STATE_DIR", dir); const binary = makeExecutable(dir, "gog"); saveExecApprovals({ version: 1, agents: { main: { allowlist: [{ pattern: binary }] } } }); const { context } = await createExecution({ config: { tools: { exec: { security, ask, pathPrepend: [dir] } } }, nativeTools: ["Bash"], }); let decision: CliBackendToolPermissionResult | undefined; const runExit = await runPlugin(context, async function* (execution) { decision = await execution.requestToolPermission({ toolName: "Bash", toolInput: { command: "gog calendar list" }, cwd: dir, }); yield SUCCESS_RESULT; }); expect(decision?.behavior, JSON.stringify({ decision, runExit })).toBe(behavior); expect(mockCallGatewayTool).not.toHaveBeenCalled(); if (decision?.behavior === "allow") { expect(decision.updatedInput?.command).toContain(binary); expect(loadExecApprovals().agents?.main?.allowlist?.[0]?.lastUsedAt).toEqual( expect.any(Number), ); } else { expect(loadExecApprovals().agents?.main?.allowlist?.[0]?.lastUsedAt).toBeUndefined(); } }, ); });