feat: route exec approvals via gateway
This commit is contained in:
@@ -477,6 +477,7 @@ export function createExecTool(
|
||||
(hostAsk === "on-miss" && hostSecurity === "allowlist" && !allowlistMatch);
|
||||
|
||||
let approvedByAsk = false;
|
||||
let approvalDecision: "allow-once" | "allow-always" | null = null;
|
||||
if (requiresAsk) {
|
||||
const decisionResult = (await callGatewayTool(
|
||||
"exec.approval.request",
|
||||
@@ -504,20 +505,24 @@ export function createExecTool(
|
||||
if (!decision) {
|
||||
if (askFallback === "full") {
|
||||
approvedByAsk = true;
|
||||
approvalDecision = "allow-once";
|
||||
} else if (askFallback === "allowlist") {
|
||||
if (!allowlistMatch) {
|
||||
throw new Error("exec denied: approval required (approval UI not available)");
|
||||
}
|
||||
approvedByAsk = true;
|
||||
approvalDecision = "allow-once";
|
||||
} else {
|
||||
throw new Error("exec denied: approval required (approval UI not available)");
|
||||
}
|
||||
}
|
||||
if (decision === "allow-once") {
|
||||
approvedByAsk = true;
|
||||
approvalDecision = "allow-once";
|
||||
}
|
||||
if (decision === "allow-always") {
|
||||
approvedByAsk = true;
|
||||
approvalDecision = "allow-always";
|
||||
if (hostSecurity === "allowlist") {
|
||||
const pattern =
|
||||
resolution?.resolvedPath ??
|
||||
@@ -556,6 +561,7 @@ export function createExecTool(
|
||||
agentId: defaults?.agentId,
|
||||
sessionKey: defaults?.sessionKey,
|
||||
approved: approvedByAsk,
|
||||
approvalDecision: approvalDecision ?? undefined,
|
||||
},
|
||||
idempotencyKey: crypto.randomUUID(),
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ export type ExecHostRequest = {
|
||||
needsScreenRecording?: boolean | null;
|
||||
agentId?: string | null;
|
||||
sessionKey?: string | null;
|
||||
approvalDecision?: "allow-once" | "allow-always" | null;
|
||||
};
|
||||
|
||||
export type ExecHostRunResult = {
|
||||
|
||||
@@ -50,6 +50,7 @@ type SystemRunParams = {
|
||||
agentId?: string | null;
|
||||
sessionKey?: string | null;
|
||||
approved?: boolean | null;
|
||||
approvalDecision?: string | null;
|
||||
};
|
||||
|
||||
type SystemWhichParams = {
|
||||
@@ -561,6 +562,10 @@ async function handleInvoke(
|
||||
const useMacAppExec =
|
||||
process.platform === "darwin" && (execHostEnforced || !execHostFallbackAllowed);
|
||||
if (useMacAppExec) {
|
||||
const approvalDecision =
|
||||
params.approvalDecision === "allow-once" || params.approvalDecision === "allow-always"
|
||||
? params.approvalDecision
|
||||
: null;
|
||||
const execRequest: ExecHostRequest = {
|
||||
command: argv,
|
||||
rawCommand: rawCommand || null,
|
||||
@@ -570,6 +575,7 @@ async function handleInvoke(
|
||||
needsScreenRecording: params.needsScreenRecording ?? null,
|
||||
agentId: agentId ?? null,
|
||||
sessionKey: sessionKey ?? null,
|
||||
approvalDecision,
|
||||
};
|
||||
const response = await runViaMacAppExecHost({ approvals, request: execRequest });
|
||||
if (!response) {
|
||||
@@ -660,7 +666,11 @@ async function handleInvoke(
|
||||
ask === "always" ||
|
||||
(ask === "on-miss" && security === "allowlist" && !allowlistMatch && !skillAllow);
|
||||
|
||||
const approvedByAsk = params.approved === true;
|
||||
const approvalDecision =
|
||||
params.approvalDecision === "allow-once" || params.approvalDecision === "allow-always"
|
||||
? params.approvalDecision
|
||||
: null;
|
||||
const approvedByAsk = approvalDecision !== null || params.approved === true;
|
||||
if (requiresAsk && !approvedByAsk) {
|
||||
await sendNodeEvent(
|
||||
client,
|
||||
@@ -679,7 +689,7 @@ async function handleInvoke(
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (approvedByAsk && security === "allowlist") {
|
||||
if (approvalDecision === "allow-always" && security === "allowlist") {
|
||||
const pattern = resolution?.resolvedPath ?? resolution?.rawExecutable ?? argv[0] ?? "";
|
||||
if (pattern) addAllowlistEntry(approvals.file, agentId, pattern);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user