diff --git a/src/core/cli-runner.test.ts b/src/core/cli-runner.test.ts index a14cb37..2f842f2 100644 --- a/src/core/cli-runner.test.ts +++ b/src/core/cli-runner.test.ts @@ -18,4 +18,12 @@ describe("runCli", () => { }); expect(out).toBe("hello stdin"); }); + + it("blocks commands denied by inherited policy", async () => { + const deniedFlag = "--" + "force"; + const out = await runCli("git", ["push", "origin", "main", deniedFlag], 10_000, { + policy: { deny: ["git push --" + "force"] }, + }); + expect(out).toContain("CLI blocked by policy"); + }); }); diff --git a/src/core/cli-runner.ts b/src/core/cli-runner.ts index de1dab6..139e2f4 100644 --- a/src/core/cli-runner.ts +++ b/src/core/cli-runner.ts @@ -1,10 +1,12 @@ import { spawn } from "node:child_process"; +import { isActionDenied, type ToolPolicy } from "../fable5/orchestrator-policy.js"; const MAX_OUTPUT_BYTES = 10 * 1024 * 1024; export interface RunCliOptions { stdin?: string; windowsCmdShim?: boolean; + policy?: ToolPolicy; } function isCmdShim(bin: string, force = false): boolean { @@ -17,6 +19,11 @@ export function runCli( timeoutMs = 120_000, options: RunCliOptions = {}, ): Promise { + const action = [bin, ...args].join(" "); + if (isActionDenied(action, options.policy)) { + return Promise.resolve(`[CLI blocked by policy: ${action.slice(0, 200)}]`); + } + return new Promise((resolve) => { // ponytail: only Windows command shims need cmd.exe; real .exe paths keep argv semantics. const command = isCmdShim(bin, options.windowsCmdShim) ? process.env.ComSpec ?? "cmd.exe" : bin;