fix(security): harden exec approval boundaries

This commit is contained in:
Peter Steinberger
2026-03-22 09:35:16 -07:00
parent e99d44525a
commit a94ec3b79b
29 changed files with 835 additions and 67 deletions

View File

@@ -2,8 +2,9 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it, type Mock, vi } from "vitest";
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../config/config.js";
import type { SystemRunApprovalPlan } from "../infra/exec-approvals.js";
import { saveExecApprovals } from "../infra/exec-approvals.js";
import { loadExecApprovals, saveExecApprovals } from "../infra/exec-approvals.js";
import type { ExecHostResponse } from "../infra/exec-host.js";
import { buildSystemRunApprovalPlan } from "./invoke-system-run-plan.js";
import { handleSystemRunInvoke, formatSystemRunAllowlistMissMessage } from "./invoke-system-run.js";
@@ -1229,4 +1230,65 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
errorLabel: "runCommand should not be called for nested env depth overflow",
});
});
it("requires explicit approval for inline eval when strictInlineEval is enabled", async () => {
setRuntimeConfigSnapshot({
tools: {
exec: {
strictInlineEval: true,
},
},
});
try {
const { runCommand, sendInvokeResult, sendNodeEvent } = await runSystemInvoke({
preferMacAppExecHost: false,
command: ["python3", "-c", "print('hi')"],
security: "full",
ask: "off",
});
expect(runCommand).not.toHaveBeenCalled();
expect(sendNodeEvent).toHaveBeenCalledWith(
expect.anything(),
"exec.denied",
expect.objectContaining({ reason: "approval-required" }),
);
expectInvokeErrorMessage(sendInvokeResult, {
message: "python3 -c requires explicit approval in strictInlineEval mode",
});
} finally {
clearRuntimeConfigSnapshot();
}
});
it("does not persist allow-always interpreter approvals when strictInlineEval is enabled", async () => {
setRuntimeConfigSnapshot({
tools: {
exec: {
strictInlineEval: true,
},
},
});
try {
await withTempApprovalsHome({
approvals: createAllowlistOnMissApprovals(),
run: async () => {
const { runCommand, sendInvokeResult } = await runSystemInvoke({
preferMacAppExecHost: false,
command: ["python3", "-c", "print('hi')"],
security: "allowlist",
ask: "on-miss",
approved: true,
runCommand: vi.fn(async () => createLocalRunResult("inline-eval-ok")),
});
expect(runCommand).toHaveBeenCalledTimes(1);
expectInvokeOk(sendInvokeResult, { payloadContains: "inline-eval-ok" });
expect(loadExecApprovals().agents?.main?.allowlist ?? []).toEqual([]);
},
});
} finally {
clearRuntimeConfigSnapshot();
}
});
});