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

@@ -9,6 +9,10 @@ import {
requiresExecApproval,
resolveAllowAlwaysPatterns,
} from "../infra/exec-approvals.js";
import {
describeInterpreterInlineEval,
detectInterpreterInlineEvalArgv,
} from "../infra/exec-inline-eval.js";
import { detectCommandObfuscation } from "../infra/exec-obfuscation-detect.js";
import type { SafeBinProfile } from "../infra/exec-safe-bin-policy.js";
import { logInfo } from "../logger.js";
@@ -48,6 +52,7 @@ export type ProcessGatewayAllowlistParams = {
ask: ExecAsk;
safeBins: Set<string>;
safeBinProfiles: Readonly<Record<string, SafeBinProfile>>;
strictInlineEval?: boolean;
agentId?: string;
sessionKey?: string;
turnSourceChannel?: string;
@@ -91,6 +96,21 @@ export async function processGatewayAllowlist(
const analysisOk = allowlistEval.analysisOk;
const allowlistSatisfied =
hostSecurity === "allowlist" && analysisOk ? allowlistEval.allowlistSatisfied : false;
const inlineEvalHit =
params.strictInlineEval === true
? (allowlistEval.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,
)}.`,
);
}
let enforcedCommand: string | undefined;
if (hostSecurity === "allowlist" && analysisOk && allowlistSatisfied) {
const enforced = buildEnforcedShellCommand({
@@ -126,6 +146,7 @@ export async function processGatewayAllowlist(
);
const requiresHeredocApproval =
hostSecurity === "allowlist" && analysisOk && allowlistSatisfied && hasHeredocSegment;
const requiresInlineEvalApproval = inlineEvalHit !== null;
const requiresAsk =
requiresExecApproval({
ask: hostAsk,
@@ -134,6 +155,7 @@ export async function processGatewayAllowlist(
allowlistSatisfied,
}) ||
requiresHeredocApproval ||
requiresInlineEvalApproval ||
obfuscation.detected;
if (requiresHeredocApproval) {
params.warnings.push(
@@ -226,7 +248,7 @@ export async function processGatewayAllowlist(
approvedByAsk = true;
} else if (decision === "allow-always") {
approvedByAsk = true;
if (hostSecurity === "allowlist") {
if (hostSecurity === "allowlist" && !requiresInlineEvalApproval) {
const patterns = resolveAllowAlwaysPatterns({
segments: allowlistEval.segments,
cwd: params.workdir,