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

@@ -8,6 +8,10 @@ import {
requiresExecApproval,
resolveExecApprovalsFromFile,
} from "../infra/exec-approvals.js";
import {
describeInterpreterInlineEval,
detectInterpreterInlineEvalArgv,
} from "../infra/exec-inline-eval.js";
import { detectCommandObfuscation } from "../infra/exec-obfuscation-detect.js";
import { buildNodeShellCommand } from "../infra/node-shell.js";
import { parsePreparedSystemRunPayload } from "../infra/system-run-approval-context.js";
@@ -42,6 +46,7 @@ export type ExecuteNodeHostCommandParams = {
agentId?: string;
security: ExecSecurity;
ask: ExecAsk;
strictInlineEval?: boolean;
timeoutSec?: number;
defaultTimeoutSec: number;
approvalRunningNoticeMs: number;
@@ -129,6 +134,21 @@ export async function executeNodeHostCommand(
});
let analysisOk = baseAllowlistEval.analysisOk;
let allowlistSatisfied = false;
const inlineEvalHit =
params.strictInlineEval === true
? (baseAllowlistEval.segments
.map((segment) =>
detectInterpreterInlineEvalArgv(segment.resolution?.effectiveArgv ?? segment.argv),
)
.find((entry) => entry !== null) ?? null)
: null;
if (inlineEvalHit) {
params.warnings.push(
`Warning: strict inline-eval mode requires explicit approval for ${describeInterpreterInlineEval(
inlineEvalHit,
)}.`,
);
}
if (hostAsk === "on-miss" && hostSecurity === "allowlist" && analysisOk) {
try {
const approvalsSnapshot = await callGatewayTool<{ file: string }>(
@@ -176,7 +196,9 @@ export async function executeNodeHostCommand(
security: hostSecurity,
analysisOk,
allowlistSatisfied,
}) || obfuscation.detected;
}) ||
inlineEvalHit !== null ||
obfuscation.detected;
const invokeTimeoutMs = Math.max(
10_000,
(typeof params.timeoutSec === "number" ? params.timeoutSec : params.defaultTimeoutSec) * 1000 +
@@ -200,7 +222,10 @@ export async function executeNodeHostCommand(
agentId: runAgentId,
sessionKey: runSessionKey,
approved: approvedByAsk,
approvalDecision: approvalDecision ?? undefined,
approvalDecision:
approvalDecision === "allow-always" && inlineEvalHit !== null
? "allow-once"
: (approvalDecision ?? undefined),
runId: runId ?? undefined,
suppressNotifyOnExit: suppressNotifyOnExit === true ? true : undefined,
},