| |
| import crypto from "node:crypto"; |
| import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; |
| import type { OpenClawConfig } from "../config/types.openclaw.js"; |
| import { |
| describeInterpreterInlineEval, |
| type InterpreterInlineEvalHit, |
| } from "../infra/command-analysis/inline-eval.js"; |
| import { detectPolicyInlineEval } from "../infra/command-analysis/policy.js"; |
| import { createDedupeCache } from "../infra/dedupe.js"; |
| import { |
| analyzeArgvCommand, |
| commitExecAuthorizationLocked, |
| commandRequiresSecurityAuditSuppressionApproval, |
| createExecApprovalPolicySnapshot, |
| hasDurableExecApproval, |
| isExecApprovalPolicySnapshotCurrent, |
| maxAsk, |
| minSecurity, |
| resolveApprovalAuditTrustPath, |
| resolveAllowAlwaysPersistenceDecision, |
| resolveDurableExecApprovalRequirement, |
| resolveExecApprovalsLocked, |
| type ExecAllowlistEntry, |
| type ExecApprovalUsageAuthorization, |
| type ExecApprovalPolicySnapshot, |
| type ExecApprovalsResolved, |
| type ExecAsk, |
| type ExecCommandSegment, |
| type ExecSegmentSatisfiedBy, |
| type ExecSecurity, |
| } from "../infra/exec-approvals.js"; |
| import { |
| planExecAuthorization, |
| type ExecAuthorizationPlan, |
| } from "../infra/exec-authorization-plan.js"; |
| import { resolveUnpinnedAutoApprovalEligibility } from "../infra/exec-auto-approval-eligibility.js"; |
| import { |
| EXEC_AUTO_REVIEW_DENIAL_GUIDANCE, |
| EXEC_AUTO_REVIEW_SHELL_STARTUP_WARNING, |
| formatExecAutoReviewAssessment, |
| resolveExecAutoReviewDecision, |
| type ExecAutoReviewer, |
| } from "../infra/exec-auto-review.js"; |
| import type { ExecHostRequest, ExecHostResponse, ExecHostRunResult } from "../infra/exec-host.js"; |
| import { resolveExecSafeBinRuntimePolicy } from "../infra/exec-safe-bin-runtime-policy.js"; |
| import { |
| extractEnvAssignmentKeysFromDispatchWrappers, |
| hasPosixShellStartupBeforeInlineCommand, |
| isBlockedShellWrapperCommand, |
| isShellWrapperInvocation, |
| resolveShellWrapperTransportArgv, |
| } from "../infra/exec-wrapper-resolution.js"; |
| import { |
| inspectHostExecEnvOverrides, |
| sanitizeSystemRunEnvOverrides, |
| } from "../infra/host-env-security.js"; |
| import { |
| APPROVAL_SCRIPT_OPERAND_DRIFT_DENIED_MESSAGE, |
| prepareSystemRunExecutableIdentityBinding, |
| revalidateSystemRunMutableFileBinding, |
| resolveMutableFileOperandSnapshotSync, |
| type SystemRunMutableFileBinding, |
| } from "../infra/system-run-approval-binding.js"; |
| import { normalizeSystemRunApprovalPlan } from "../infra/system-run-approval-plan.js"; |
| import { formatExecCommand, resolveSystemRunCommandRequest } from "../infra/system-run-command.js"; |
| import { |
| APPROVAL_CWD_DRIFT_DENIED_MESSAGE, |
| type ApprovedCwdSnapshot, |
| captureApprovedCwdSnapshotSync, |
| revalidateApprovedCwdSnapshot, |
| } from "../infra/system-run-cwd-binding.js"; |
| import { revalidateApprovedMutableFileOperand } from "../infra/system-run-file-snapshot.js"; |
| import { logWarn } from "../logger.js"; |
| import type { NodeHostClient } from "./client.js"; |
| import { |
| evaluateSystemRunPolicy, |
| resolveExecApprovalDecision, |
| resolveNodeExecConfigPolicy, |
| } from "./exec-policy.js"; |
| import { |
| applyOutputTruncation, |
| evaluateSystemRunAllowlist, |
| resolvePlannedAllowlistArgv, |
| resolveSystemRunExecArgv, |
| } from "./invoke-system-run-allowlist.js"; |
| import { hardenApprovedExecutionPaths } from "./invoke-system-run-plan.js"; |
| import type { |
| ExecEventPayload, |
| ExecFinishedResult, |
| ExecFinishedEventParams, |
| RunResult, |
| SkillBinsProvider, |
| SystemRunParams, |
| } from "./invoke-types.js"; |
|
|
| type SystemRunInvokeResult = { |
| ok: boolean; |
| payloadJSON?: string | null; |
| error?: { code?: string; message?: string } | null; |
| }; |
|
|
| type SystemRunDeniedReason = |
| | "security=deny" |
| | "approval-required" |
| | "auto-review-denied" |
| | "approval-state-write-failed" |
| | "allowlist-miss" |
| | "execution-plan-miss" |
| | "companion-unavailable" |
| | "cwd-unavailable" |
| | "permission:screenRecording"; |
|
|
| type SystemRunExecutionContext = { |
| sessionKey: string; |
| runId: string; |
| commandText: string; |
| suppressNotifyOnExit: boolean; |
| }; |
|
|
| type SystemRunParsePhase = { |
| argv: string[]; |
| shellPayload: string | null; |
| shellWrapperInvocation: boolean; |
| commandText: string; |
| commandPreview: string | null; |
| approvalPlan: import("../infra/exec-approvals.js").SystemRunApprovalPlan | null; |
| agentId: string | undefined; |
| sessionKey: string; |
| runId: string; |
| execution: SystemRunExecutionContext; |
| approvalDecision: ReturnType<typeof resolveExecApprovalDecision>; |
| approvalSource: "ask-fallback" | "auto-review" | undefined; |
| delayedApprovalPolicySnapshot: ExecApprovalPolicySnapshot | null; |
| envOverrides: Record<string, string> | undefined; |
| env: Record<string, string> | undefined; |
| cwd: string | undefined; |
| timeoutMs: number | undefined; |
| needsScreenRecording: boolean; |
| approved: boolean; |
| }; |
|
|
| type SystemRunPolicyPhase = SystemRunParsePhase & { |
| approvals: ExecApprovalsResolved; |
| evaluationPolicySnapshot: ExecApprovalPolicySnapshot; |
| security: ExecSecurity; |
| ask: ExecAsk; |
| policy: ReturnType<typeof evaluateSystemRunPolicy>; |
| approvalGrantSource: "explicit-approval" | "auto-review" | null; |
| durableApprovalSatisfied: boolean; |
| durableApprovalRequirement: ReturnType<typeof resolveDurableExecApprovalRequirement>; |
| strictInlineEval: boolean; |
| inlineEvalHit: InterpreterInlineEvalHit | null; |
| allowlistMatches: ExecAllowlistEntry[]; |
| analysisOk: boolean; |
| allowlistSatisfied: boolean; |
| allowlistAuthorizationSatisfied: boolean; |
| segments: ExecCommandSegment[]; |
| segmentSatisfiedBy: ExecSegmentSatisfiedBy[]; |
| authorizationPlan: ExecAuthorizationPlan | undefined; |
| plannedAllowlistArgv: string[] | undefined; |
| isWindows: boolean; |
| approvedCwdSnapshot: ApprovedCwdSnapshot | undefined; |
| executableBinding: SystemRunMutableFileBinding | undefined; |
| }; |
|
|
| const safeBinTrustedDirWarningCache = createDedupeCache({ |
| ttlMs: 0, |
| maxSize: 4096, |
| }); |
| const APPROVAL_SCRIPT_OPERAND_BINDING_DENIED_MESSAGE = |
| "SYSTEM_RUN_DENIED: approval missing script operand binding"; |
| const APPROVAL_STATE_WRITE_FAILED_MESSAGE = |
| "SYSTEM_RUN_DENIED: approval state could not be persisted"; |
| type ExecToolConfig = NonNullable<NonNullable<OpenClawConfig["tools"]>["exec"]>; |
|
|
| type EffectiveSystemRunExecPolicy = { |
| agentExec: ExecToolConfig | undefined; |
| globalExec: ExecToolConfig | undefined; |
| approvals: ExecApprovalsResolved; |
| security: ExecSecurity; |
| ask: ExecAsk; |
| autoReview: boolean; |
| }; |
|
|
| function warnWritableTrustedDirOnce(message: string): void { |
| if (safeBinTrustedDirWarningCache.check(message)) { |
| return; |
| } |
| logWarn(message); |
| } |
|
|
| function normalizeDeniedReason(reason: string | null | undefined): SystemRunDeniedReason { |
| switch (reason) { |
| case "security=deny": |
| case "approval-required": |
| case "allowlist-miss": |
| case "execution-plan-miss": |
| case "companion-unavailable": |
| case "cwd-unavailable": |
| case "permission:screenRecording": |
| return reason; |
| default: |
| return "approval-required"; |
| } |
| } |
|
|
| |
| export async function resolveEffectiveSystemRunExecPolicy(params: { |
| cfg: OpenClawConfig; |
| agentId: string | undefined; |
| defaultSecurity: ExecSecurity; |
| defaultAsk: ExecAsk; |
| requireSocket: boolean; |
| }): Promise<EffectiveSystemRunExecPolicy> { |
| const modePolicy = resolveNodeExecConfigPolicy(params); |
| const { agentExec, globalExec } = modePolicy; |
| const approvals = await resolveExecApprovalsLocked(params.agentId, { |
| security: modePolicy.security, |
| ask: modePolicy.ask, |
| requireSocket: params.requireSocket, |
| }); |
| return { |
| agentExec, |
| globalExec, |
| approvals, |
| security: minSecurity(modePolicy.security, approvals.agent.security), |
| ask: maxAsk(modePolicy.ask, approvals.agent.ask), |
| autoReview: modePolicy.autoReview, |
| }; |
| } |
|
|
| async function resolveSystemRunAutoReviewer(params: { |
| opts: HandleSystemRunInvokeOptions; |
| cfg: OpenClawConfig; |
| agentId: string | undefined; |
| agentExec: ExecToolConfig | undefined; |
| globalExec: ExecToolConfig | undefined; |
| }): Promise<ExecAutoReviewer> { |
| if (params.opts.autoReviewer) { |
| return params.opts.autoReviewer; |
| } |
| const { createModelExecAutoReviewer } = await import("../agents/exec-auto-reviewer.js"); |
| return createModelExecAutoReviewer({ |
| cfg: params.cfg, |
| agentId: params.agentId, |
| reviewer: params.agentExec?.reviewer ?? params.globalExec?.reviewer, |
| }); |
| } |
|
|
| type HandleSystemRunInvokeOptions = { |
| client: NodeHostClient; |
| params: SystemRunParams; |
| skillBins: SkillBinsProvider; |
| signal?: AbortSignal; |
| execHostEnforced: boolean; |
| execHostFallbackAllowed: boolean; |
| resolveExecSecurity: (value?: string) => ExecSecurity; |
| resolveExecAsk: (value?: string) => ExecAsk; |
| isCmdExeInvocation: (argv: string[]) => boolean; |
| sanitizeEnv: (overrides?: Record<string, string> | null) => Record<string, string> | undefined; |
| runCommand: ( |
| argv: string[], |
| cwd: string | undefined, |
| env: Record<string, string> | undefined, |
| timeoutMs: number | undefined, |
| signal?: AbortSignal, |
| assertCurrent?: () => void, |
| ) => Promise<RunResult>; |
| runViaMacAppExecHost: (params: { |
| approvals: ExecApprovalsResolved; |
| request: ExecHostRequest; |
| signal?: AbortSignal; |
| }) => Promise<ExecHostResponse | null>; |
| sendNodeEvent: (client: NodeHostClient, event: string, payload: unknown) => Promise<void>; |
| buildExecEventPayload: (payload: ExecEventPayload) => ExecEventPayload; |
| sendInvokeResult: (result: SystemRunInvokeResult) => Promise<void>; |
| sendExecFinishedEvent: (params: ExecFinishedEventParams) => Promise<void>; |
| preferMacAppExecHost: boolean; |
| getRuntimeConfig?: () => OpenClawConfig; |
| autoReviewer?: ExecAutoReviewer; |
| commitExecAuthorization?: typeof commitExecAuthorizationLocked; |
| }; |
|
|
| async function loadSystemRunConfig(opts: HandleSystemRunInvokeOptions): Promise<OpenClawConfig> { |
| if (opts.getRuntimeConfig) { |
| return opts.getRuntimeConfig(); |
| } |
| const { getRuntimeConfig } = await import("../config/config.js"); |
| return getRuntimeConfig(); |
| } |
|
|
| async function sendSystemRunDenied( |
| opts: Pick< |
| HandleSystemRunInvokeOptions, |
| "client" | "sendNodeEvent" | "buildExecEventPayload" | "sendInvokeResult" |
| >, |
| execution: SystemRunExecutionContext, |
| params: { |
| reason: SystemRunDeniedReason; |
| message: string; |
| }, |
| ) { |
| await opts.sendNodeEvent( |
| opts.client, |
| "exec.denied", |
| opts.buildExecEventPayload({ |
| sessionKey: execution.sessionKey, |
| runId: execution.runId, |
| host: "node", |
| command: execution.commandText, |
| reason: params.reason, |
| suppressNotifyOnExit: execution.suppressNotifyOnExit, |
| }), |
| ); |
| await opts.sendInvokeResult({ |
| ok: false, |
| |
| error: { |
| code: params.reason === "companion-unavailable" ? "UNAVAILABLE" : "SYSTEM_RUN_DENIED", |
| message: params.message, |
| }, |
| }); |
| } |
|
|
| async function sendSystemRunCompleted( |
| opts: Pick<HandleSystemRunInvokeOptions, "sendExecFinishedEvent" | "sendInvokeResult">, |
| execution: SystemRunExecutionContext, |
| result: ExecFinishedResult, |
| payloadJSON: string, |
| ) { |
| await opts.sendExecFinishedEvent({ |
| sessionKey: execution.sessionKey, |
| runId: execution.runId, |
| commandText: execution.commandText, |
| result, |
| suppressNotifyOnExit: execution.suppressNotifyOnExit, |
| }); |
| await opts.sendInvokeResult({ |
| ok: true, |
| payloadJSON, |
| }); |
| } |
|
|
| function argvArraysMatch(left: readonly string[] | undefined, right: readonly string[]): boolean { |
| return ( |
| left !== undefined && |
| left.length === right.length && |
| left.every((entry, index) => entry === right[index]) |
| ); |
| } |
|
|
| export { buildSystemRunApprovalPlan } from "./invoke-system-run-plan.js"; |
|
|
| async function parseSystemRunPhase( |
| opts: HandleSystemRunInvokeOptions, |
| ): Promise<SystemRunParsePhase | null> { |
| const command = resolveSystemRunCommandRequest({ |
| command: opts.params.command, |
| rawCommand: opts.params.rawCommand, |
| }); |
| if (!command.ok) { |
| await opts.sendInvokeResult({ |
| ok: false, |
| error: { code: "INVALID_REQUEST", message: command.message }, |
| }); |
| return null; |
| } |
| if (command.argv.length === 0) { |
| await opts.sendInvokeResult({ |
| ok: false, |
| error: { code: "INVALID_REQUEST", message: "command required" }, |
| }); |
| return null; |
| } |
|
|
| const shellPayload = command.shellPayload; |
| const shellWrapperInvocation = isShellWrapperInvocation(command.argv); |
| const commandText = command.commandText; |
| const approvalPlan = |
| opts.params.systemRunPlan === undefined |
| ? null |
| : normalizeSystemRunApprovalPlan(opts.params.systemRunPlan); |
| if (opts.params.systemRunPlan !== undefined && !approvalPlan) { |
| await opts.sendInvokeResult({ |
| ok: false, |
| error: { code: "INVALID_REQUEST", message: "systemRunPlan invalid" }, |
| }); |
| return null; |
| } |
| const agentId = normalizeOptionalString(opts.params.agentId); |
| const requestedSessionKey = normalizeOptionalString(opts.params.sessionKey); |
| const sessionKey = requestedSessionKey ?? "node"; |
| const runId = normalizeOptionalString(opts.params.runId) ?? crypto.randomUUID(); |
| const cwd = normalizeOptionalString(opts.params.cwd); |
| const suppressNotifyOnExit = opts.params.suppressNotifyOnExit === true; |
| const approvalSource = opts.params.approvalSource; |
| if ( |
| approvalSource != null && |
| approvalSource !== "ask-fallback" && |
| approvalSource !== "auto-review" |
| ) { |
| await opts.sendInvokeResult({ |
| ok: false, |
| error: { code: "INVALID_REQUEST", message: "approvalSource invalid" }, |
| }); |
| return null; |
| } |
| const approvalDecision = resolveExecApprovalDecision(opts.params.approvalDecision); |
| const approved = opts.params.approved === true; |
| if ( |
| approvalSource != null && |
| (opts.params.approved !== undefined || opts.params.approvalDecision !== undefined) |
| ) { |
| await opts.sendInvokeResult({ |
| ok: false, |
| error: { |
| code: "INVALID_REQUEST", |
| message: "approvalSource cannot be combined with explicit approval", |
| }, |
| }); |
| return null; |
| } |
| const explicitApproval = approved || approvalDecision !== null; |
| const forwardedDelayedApproval = approvalSource === "auto-review" || explicitApproval; |
| if (approvalSource != null || explicitApproval) { |
| const planMatchesRequest = |
| approvalPlan !== null && |
| argvArraysMatch(approvalPlan.argv, command.argv) && |
| approvalPlan.commandText === commandText && |
| normalizeOptionalString(approvalPlan.cwd) === cwd && |
| normalizeOptionalString(approvalPlan.agentId) === agentId && |
| normalizeOptionalString(approvalPlan.sessionKey) === requestedSessionKey; |
| if (!planMatchesRequest) { |
| await opts.sendInvokeResult({ |
| ok: false, |
| error: { |
| code: "INVALID_REQUEST", |
| message: |
| approvalSource != null |
| ? "approvalSource requires matching systemRunPlan" |
| : "explicit approval requires matching systemRunPlan", |
| }, |
| }); |
| return null; |
| } |
| } |
| const delayedApprovalPolicySnapshot = forwardedDelayedApproval |
| ? (approvalPlan?.policySnapshot ?? null) |
| : null; |
| if (forwardedDelayedApproval && !delayedApprovalPolicySnapshot) { |
| await opts.sendInvokeResult({ |
| ok: false, |
| error: { |
| code: "INVALID_REQUEST", |
| message: "delayed approval requires a prepared policy snapshot", |
| }, |
| }); |
| return null; |
| } |
| const envAssignmentKeys = extractEnvAssignmentKeysFromDispatchWrappers(command.argv); |
| const envAssignmentOverrides = |
| envAssignmentKeys.length > 0 |
| ? Object.fromEntries(envAssignmentKeys.map((key) => [key, "1"])) |
| : undefined; |
| const envAssignmentDiagnostics = inspectHostExecEnvOverrides({ |
| overrides: envAssignmentOverrides, |
| blockPathOverrides: true, |
| }); |
| |
| |
| if (envAssignmentDiagnostics.rejectedOverrideBlockedKeys.length > 0) { |
| await opts.sendInvokeResult({ |
| ok: false, |
| error: { |
| code: "INVALID_REQUEST", |
| message: `SYSTEM_RUN_DENIED: command env assignment rejected (blocked env assignment keys: ${envAssignmentDiagnostics.rejectedOverrideBlockedKeys.join(", ")})`, |
| }, |
| }); |
| return null; |
| } |
| const envOverrideDiagnostics = inspectHostExecEnvOverrides({ |
| overrides: opts.params.env ?? undefined, |
| blockPathOverrides: true, |
| }); |
| if ( |
| envOverrideDiagnostics.rejectedOverrideBlockedKeys.length > 0 || |
| envOverrideDiagnostics.rejectedOverrideInvalidKeys.length > 0 |
| ) { |
| const details: string[] = []; |
| if (envOverrideDiagnostics.rejectedOverrideBlockedKeys.length > 0) { |
| details.push( |
| `blocked override keys: ${envOverrideDiagnostics.rejectedOverrideBlockedKeys.join(", ")}`, |
| ); |
| } |
| if (envOverrideDiagnostics.rejectedOverrideInvalidKeys.length > 0) { |
| details.push( |
| `invalid non-portable override keys: ${envOverrideDiagnostics.rejectedOverrideInvalidKeys.join(", ")}`, |
| ); |
| } |
| await opts.sendInvokeResult({ |
| ok: false, |
| error: { |
| code: "INVALID_REQUEST", |
| message: `SYSTEM_RUN_DENIED: environment override rejected (${details.join("; ")})`, |
| }, |
| }); |
| return null; |
| } |
| const envOverrides = sanitizeSystemRunEnvOverrides({ |
| overrides: opts.params.env ?? undefined, |
| shellWrapper: shellWrapperInvocation, |
| }); |
| return { |
| argv: command.argv, |
| shellPayload, |
| shellWrapperInvocation, |
| commandText, |
| commandPreview: command.previewText, |
| approvalPlan, |
| agentId, |
| sessionKey, |
| runId, |
| execution: { sessionKey, runId, commandText, suppressNotifyOnExit }, |
| approvalDecision, |
| approvalSource: approvalSource ?? undefined, |
| delayedApprovalPolicySnapshot, |
| envOverrides, |
| env: opts.sanitizeEnv(envOverrides), |
| cwd, |
| timeoutMs: opts.params.timeoutMs ?? undefined, |
| needsScreenRecording: opts.params.needsScreenRecording === true, |
| approved, |
| }; |
| } |
|
|
| async function evaluateSystemRunPolicyPhase( |
| opts: HandleSystemRunInvokeOptions, |
| parsed: SystemRunParsePhase, |
| ): Promise<SystemRunPolicyPhase | null> { |
| const cfg = await loadSystemRunConfig(opts); |
| const effectivePolicy = await resolveEffectiveSystemRunExecPolicy({ |
| cfg, |
| agentId: parsed.agentId, |
| defaultSecurity: opts.resolveExecSecurity(undefined), |
| defaultAsk: opts.resolveExecAsk(undefined), |
| requireSocket: opts.preferMacAppExecHost, |
| }); |
| const { agentExec, globalExec, approvals } = effectivePolicy; |
| const currentPolicySnapshot = createExecApprovalPolicySnapshot({ |
| file: approvals.file, |
| agentId: parsed.agentId, |
| }); |
| if ( |
| parsed.delayedApprovalPolicySnapshot && |
| !isExecApprovalPolicySnapshotCurrent( |
| parsed.delayedApprovalPolicySnapshot, |
| currentPolicySnapshot, |
| ) |
| ) { |
| await sendSystemRunDenied(opts, parsed.execution, { |
| reason: "approval-required", |
| message: "SYSTEM_RUN_DENIED: exec approval policy changed; request approval again", |
| }); |
| return null; |
| } |
| const evaluationPolicySnapshot = parsed.delayedApprovalPolicySnapshot ?? currentPolicySnapshot; |
| const baseSecurity = effectivePolicy.security; |
| const baseAsk = effectivePolicy.ask; |
| const fallbackRequest = parsed.approvalSource === "ask-fallback"; |
| const security = fallbackRequest |
| ? minSecurity(baseSecurity, approvals.agent.askFallback) |
| : baseSecurity; |
| const ask = fallbackRequest ? "off" : baseAsk; |
| const autoAllowSkills = approvals.agent.autoAllowSkills; |
| const { safeBins, safeBinProfiles, trustedSafeBinDirs } = resolveExecSafeBinRuntimePolicy({ |
| global: cfg.tools?.exec, |
| local: agentExec, |
| onWarning: warnWritableTrustedDirOnce, |
| }); |
| const bins = autoAllowSkills ? await opts.skillBins.current() : []; |
| const allowlistEvaluation = await evaluateSystemRunAllowlist({ |
| shellCommand: parsed.shellPayload, |
| argv: parsed.argv, |
| approvals, |
| security, |
| safeBins, |
| safeBinProfiles, |
| trustedSafeBinDirs, |
| cwd: parsed.cwd, |
| env: parsed.env, |
| skillBins: bins, |
| autoAllowSkills, |
| }); |
| const { |
| allowlistMatches, |
| allowlistAuthorizationSatisfied, |
| segments, |
| segmentAllowlistEntries, |
| segmentSatisfiedBy, |
| } = allowlistEvaluation; |
| let { analysisOk, allowlistSatisfied } = allowlistEvaluation; |
| const strictInlineEval = |
| agentExec?.strictInlineEval === true || cfg.tools?.exec?.strictInlineEval === true; |
| const inlineEvalHit = strictInlineEval ? detectPolicyInlineEval(segments) : null; |
| const isWindows = process.platform === "win32"; |
| |
| |
| |
| const cmdDetectionArgv = resolveShellWrapperTransportArgv(parsed.argv) ?? parsed.argv; |
| const cmdInvocation = opts.isCmdExeInvocation(cmdDetectionArgv); |
| const durableApprovalSatisfied = hasDurableExecApproval({ |
| analysisOk, |
| segmentAllowlistEntries, |
| allowlist: approvals.allowlist, |
| commandText: parsed.commandText, |
| }); |
| const inlineEvalExecutableTrusted = |
| inlineEvalHit !== null && |
| segmentAllowlistEntries.some((entry) => entry?.source === "allow-always"); |
| const forwardedAutoReview = parsed.approvalSource === "auto-review"; |
| let approvalDecision = forwardedAutoReview ? "allow-once" : parsed.approvalDecision; |
| let approvalGrantSource: SystemRunPolicyPhase["approvalGrantSource"] = forwardedAutoReview |
| ? "auto-review" |
| : parsed.approved || approvalDecision !== null |
| ? "explicit-approval" |
| : null; |
| let policy = evaluateSystemRunPolicy({ |
| security, |
| ask, |
| analysisOk, |
| allowlistSatisfied, |
| durableApprovalSatisfied: durableApprovalSatisfied || inlineEvalExecutableTrusted, |
| approvalDecision, |
| approved: parsed.approved, |
| isWindows, |
| cmdInvocation, |
| |
| |
| shellWrapperInvocation: parsed.shellPayload !== null, |
| }); |
| const requiresSecurityAuditSuppressionApproval = |
| commandRequiresSecurityAuditSuppressionApproval({ |
| command: parsed.commandText, |
| cwd: parsed.cwd, |
| env: parsed.env, |
| segments, |
| }) && !(baseSecurity === "full" && baseAsk === "off" && !fallbackRequest); |
| if (forwardedAutoReview && requiresSecurityAuditSuppressionApproval) { |
| await sendSystemRunDenied(opts, parsed.execution, { |
| reason: "approval-required", |
| message: "SYSTEM_RUN_DENIED: explicit approval required", |
| }); |
| return null; |
| } |
| if (requiresSecurityAuditSuppressionApproval && !policy.approvedByAsk) { |
| policy = { |
| allowed: false, |
| eventReason: "approval-required", |
| errorMessage: "SYSTEM_RUN_DENIED: approval required", |
| analysisOk: policy.analysisOk, |
| allowlistSatisfied: policy.allowlistSatisfied, |
| shellWrapperBlocked: policy.shellWrapperBlocked, |
| windowsShellWrapperBlocked: policy.windowsShellWrapperBlocked, |
| requiresAsk: true, |
| approvalDecision: policy.approvalDecision, |
| approvedByAsk: policy.approvedByAsk, |
| }; |
| } |
| let autoReviewDeferredMessage: string | undefined; |
| analysisOk = policy.analysisOk; |
| allowlistSatisfied = policy.allowlistSatisfied; |
| const strictInlineEvalRequiresApproval = |
| inlineEvalHit !== null && |
| !policy.approvedByAsk && |
| (policy.allowed ? true : policy.eventReason !== "security=deny"); |
| if (strictInlineEvalRequiresApproval) { |
| await sendSystemRunDenied(opts, parsed.execution, { |
| reason: "approval-required", |
| message: |
| `SYSTEM_RUN_DENIED: approval required (` + |
| `${describeInterpreterInlineEval(inlineEvalHit)} requires explicit approval in strictInlineEval mode)`, |
| }); |
| return null; |
| } |
|
|
| let executableBinding: SystemRunMutableFileBinding | undefined; |
| if ( |
| security !== "deny" && |
| (policy.approvedByAsk || |
| fallbackRequest || |
| security === "allowlist" || |
| effectivePolicy.autoReview) |
| ) { |
| const prepared = prepareSystemRunExecutableIdentityBinding({ |
| segments, |
| cwd: parsed.cwd, |
| env: parsed.env, |
| shellCommand: parsed.shellPayload !== null, |
| }); |
| if (!prepared.ok) { |
| await sendSystemRunDenied(opts, parsed.execution, { |
| reason: "approval-required", |
| message: prepared.message, |
| }); |
| return null; |
| } |
| executableBinding = prepared.binding; |
| } |
|
|
| if (!policy.allowed) { |
| const autoReviewBlockedByShellStartup = segments.some((segment) => |
| hasPosixShellStartupBeforeInlineCommand(segment.argv), |
| ); |
| const autoReviewEligibility = resolveUnpinnedAutoApprovalEligibility({ |
| authorizationPlan: await planExecAuthorization({ |
| analysis: analyzeArgvCommand({ argv: parsed.argv, cwd: parsed.cwd, env: parsed.env }), |
| command: parsed.commandText, |
| cwd: parsed.cwd, |
| env: parsed.env, |
| }), |
| binding: executableBinding, |
| }); |
| if (effectivePolicy.autoReview && ask !== "always") { |
| if (autoReviewBlockedByShellStartup) { |
| autoReviewDeferredMessage = `${policy.errorMessage} (${EXEC_AUTO_REVIEW_SHELL_STARTUP_WARNING})`; |
| } else if (!autoReviewEligibility.eligible) { |
| autoReviewDeferredMessage = `${policy.errorMessage} (${autoReviewEligibility.reason})`; |
| } |
| } |
| const [autoReviewSegment] = segments; |
| const directAutoReviewArgvMatchesRequest = |
| parsed.shellPayload !== null || argvArraysMatch(autoReviewSegment?.argv, parsed.argv); |
| const autoReviewArgv = |
| segments.length === 1 && |
| autoReviewSegment !== undefined && |
| autoReviewSegment.resolution?.policyBlocked !== true && |
| |
| !isBlockedShellWrapperCommand(autoReviewSegment.argv) && |
| directAutoReviewArgvMatchesRequest && |
| (parsed.shellPayload === null || |
| (autoReviewSegment.raw !== undefined && |
| autoReviewSegment.raw.trim() === parsed.shellPayload.trim())) |
| ? autoReviewSegment.argv |
| : undefined; |
| const canAutoReviewApprovalMiss = |
| !fallbackRequest && |
| effectivePolicy.autoReview && |
| ask !== "always" && |
| analysisOk && |
| autoReviewArgv !== undefined && |
| parsed.approvalPlan !== null && |
| inlineEvalHit === null && |
| !autoReviewBlockedByShellStartup && |
| autoReviewEligibility.eligible && |
| !requiresSecurityAuditSuppressionApproval && |
| policy.eventReason !== "security=deny"; |
| if (canAutoReviewApprovalMiss) { |
| const reviewer = await resolveSystemRunAutoReviewer({ |
| opts, |
| cfg, |
| agentId: parsed.agentId, |
| agentExec, |
| globalExec, |
| }); |
| const decision = await resolveExecAutoReviewDecision(reviewer, { |
| command: parsed.commandText, |
| argv: autoReviewArgv, |
| cwd: parsed.cwd, |
| envKeys: Object.keys(parsed.envOverrides ?? {}).toSorted(), |
| host: "node", |
| reason: policy.eventReason === "allowlist-miss" ? "allowlist-miss" : "approval-required", |
| analysis: { |
| parsed: analysisOk, |
| allowlistMatched: allowlistSatisfied, |
| durableApprovalMatched: durableApprovalSatisfied, |
| inlineEval: false, |
| shellWrapper: parsed.shellWrapperInvocation, |
| }, |
| agent: { |
| id: parsed.agentId, |
| sessionKey: parsed.sessionKey, |
| }, |
| }); |
| switch (decision.decision) { |
| case "deny": |
| await sendSystemRunDenied(opts, parsed.execution, { |
| reason: "auto-review-denied", |
| message: `SYSTEM_RUN_DENIED: auto-review denied (${formatExecAutoReviewAssessment(decision)}): ${decision.rationale}\n${EXEC_AUTO_REVIEW_DENIAL_GUIDANCE}`, |
| }); |
| return null; |
| case "ask": |
| break; |
| case "allow-once": { |
| if (decision.risk !== "low" && decision.risk !== "medium") { |
| break; |
| } |
| approvalDecision = "allow-once"; |
| approvalGrantSource = "auto-review"; |
| policy = evaluateSystemRunPolicy({ |
| security, |
| ask, |
| analysisOk, |
| allowlistSatisfied, |
| durableApprovalSatisfied: durableApprovalSatisfied || inlineEvalExecutableTrusted, |
| approvalDecision, |
| approved: true, |
| isWindows, |
| cmdInvocation, |
| shellWrapperInvocation: parsed.shellPayload !== null, |
| }); |
| break; |
| } |
| default: |
| throw new Error("Unsupported exec auto-review decision", { |
| cause: decision satisfies never, |
| }); |
| } |
| if (!policy.allowed) { |
| autoReviewDeferredMessage = `${policy.errorMessage} (exec auto-review deferred to human approval: ${decision.rationale})`; |
| } |
| } |
| } |
|
|
| if (!policy.allowed) { |
| await sendSystemRunDenied(opts, parsed.execution, { |
| reason: policy.eventReason, |
| message: autoReviewDeferredMessage ?? policy.errorMessage, |
| }); |
| return null; |
| } |
|
|
| |
| if (policy.shellWrapperBlocked && !policy.approvedByAsk && !durableApprovalSatisfied) { |
| await sendSystemRunDenied(opts, parsed.execution, { |
| reason: "approval-required", |
| message: "SYSTEM_RUN_DENIED: approval required", |
| }); |
| return null; |
| } |
| |
| |
| const durableApprovalRequired = |
| security === "allowlist" && |
| durableApprovalSatisfied && |
| !policy.approvedByAsk && |
| (!policy.analysisOk || !policy.allowlistSatisfied); |
| const durableApprovalRequirement = resolveDurableExecApprovalRequirement({ |
| durableApprovalRequired, |
| allowlist: approvals.allowlist, |
| commandText: parsed.commandText, |
| }); |
|
|
| const approvalContextBound = policy.approvedByAsk || fallbackRequest; |
| const hardenedPaths = hardenApprovedExecutionPaths({ |
| approvedByAsk: approvalContextBound, |
| argv: parsed.argv, |
| shellCommand: parsed.shellPayload, |
| cwd: parsed.cwd, |
| }); |
| if (!hardenedPaths.ok) { |
| await sendSystemRunDenied(opts, parsed.execution, { |
| reason: "approval-required", |
| message: hardenedPaths.message, |
| }); |
| return null; |
| } |
| let executionCwd = hardenedPaths.cwd; |
| let approvedCwdSnapshot = approvalContextBound ? hardenedPaths.approvedCwdSnapshot : undefined; |
| if (security === "allowlist" && !approvedCwdSnapshot) { |
| const capturedCwd = captureApprovedCwdSnapshotSync(executionCwd ?? process.cwd()); |
| if (!capturedCwd.ok) { |
| await sendSystemRunDenied(opts, parsed.execution, { |
| reason: "approval-required", |
| message: capturedCwd.message, |
| }); |
| return null; |
| } |
| executionCwd = capturedCwd.snapshot.cwd; |
| approvedCwdSnapshot = capturedCwd.snapshot; |
| } |
| if ((approvalContextBound || security === "allowlist") && !approvedCwdSnapshot) { |
| await sendSystemRunDenied(opts, parsed.execution, { |
| reason: "approval-required", |
| message: APPROVAL_CWD_DRIFT_DENIED_MESSAGE, |
| }); |
| return null; |
| } |
|
|
| const plannedAllowlistArgv = resolvePlannedAllowlistArgv({ |
| security, |
| shellCommand: parsed.shellPayload, |
| policy, |
| segments, |
| }); |
| if (plannedAllowlistArgv === null) { |
| await sendSystemRunDenied(opts, parsed.execution, { |
| reason: "execution-plan-miss", |
| message: "SYSTEM_RUN_DENIED: execution plan mismatch", |
| }); |
| return null; |
| } |
| return { |
| ...parsed, |
| cwd: executionCwd, |
| approvalDecision, |
| argv: hardenedPaths.argv, |
| approvals, |
| evaluationPolicySnapshot, |
| security, |
| ask, |
| policy, |
| approvalGrantSource, |
| durableApprovalSatisfied, |
| durableApprovalRequirement, |
| strictInlineEval, |
| inlineEvalHit, |
| allowlistMatches, |
| analysisOk, |
| allowlistSatisfied, |
| allowlistAuthorizationSatisfied, |
| segments, |
| segmentSatisfiedBy, |
| authorizationPlan: allowlistEvaluation.authorizationPlan, |
| plannedAllowlistArgv: plannedAllowlistArgv ?? undefined, |
| isWindows, |
| approvedCwdSnapshot, |
| executableBinding, |
| }; |
| } |
|
|
| async function revalidateSystemRunApprovedPathBindings( |
| opts: HandleSystemRunInvokeOptions, |
| phase: SystemRunPolicyPhase, |
| ): Promise<boolean> { |
| if (phase.approvedCwdSnapshot && !revalidateApprovedCwdSnapshot(phase.approvedCwdSnapshot)) { |
| logWarn(`security: system.run approval cwd drift blocked (runId=${phase.runId})`); |
| await sendSystemRunDenied(opts, phase.execution, { |
| reason: "approval-required", |
| message: APPROVAL_CWD_DRIFT_DENIED_MESSAGE, |
| }); |
| return false; |
| } |
| if ( |
| phase.approvalPlan?.mutableFileOperand && |
| !revalidateApprovedMutableFileOperand({ |
| snapshot: phase.approvalPlan.mutableFileOperand, |
| argv: phase.argv, |
| cwd: phase.cwd, |
| }) |
| ) { |
| logWarn(`security: system.run approval script drift blocked (runId=${phase.runId})`); |
| await sendSystemRunDenied(opts, phase.execution, { |
| reason: "approval-required", |
| message: APPROVAL_SCRIPT_OPERAND_DRIFT_DENIED_MESSAGE, |
| }); |
| return false; |
| } |
| if (phase.executableBinding) { |
| const revalidated = await revalidateSystemRunMutableFileBinding({ |
| binding: phase.executableBinding, |
| cwd: phase.cwd, |
| }); |
| if (!revalidated.ok) { |
| logWarn(`security: system.run approval executable drift blocked (runId=${phase.runId})`); |
| await sendSystemRunDenied(opts, phase.execution, { |
| reason: "approval-required", |
| message: revalidated.message, |
| }); |
| return false; |
| } |
| } |
| return true; |
| } |
|
|
| async function executeSystemRunPhase( |
| opts: HandleSystemRunInvokeOptions, |
| phase: SystemRunPolicyPhase, |
| ): Promise<void> { |
| if (!(await revalidateSystemRunApprovedPathBindings(opts, phase))) { |
| return; |
| } |
| const expectedMutableFileOperand = |
| phase.approvalPlan && |
| (phase.policy.approvedByAsk || |
| phase.approvalSource !== undefined || |
| phase.security === "allowlist") |
| ? resolveMutableFileOperandSnapshotSync({ |
| argv: phase.argv, |
| cwd: phase.cwd, |
| shellCommand: phase.shellPayload, |
| }) |
| : null; |
| if (expectedMutableFileOperand && !expectedMutableFileOperand.ok) { |
| logWarn(`security: system.run approval script binding blocked (runId=${phase.runId})`); |
| await sendSystemRunDenied(opts, phase.execution, { |
| reason: "approval-required", |
| message: expectedMutableFileOperand.message, |
| }); |
| return; |
| } |
| if (expectedMutableFileOperand?.snapshot && !phase.approvalPlan?.mutableFileOperand) { |
| logWarn(`security: system.run approval script binding missing (runId=${phase.runId})`); |
| await sendSystemRunDenied(opts, phase.execution, { |
| reason: "approval-required", |
| message: APPROVAL_SCRIPT_OPERAND_BINDING_DENIED_MESSAGE, |
| }); |
| return; |
| } |
| const execArgv = await resolveSystemRunExecArgv({ |
| plannedAllowlistArgv: phase.plannedAllowlistArgv, |
| argv: phase.argv, |
| security: phase.security, |
| isWindows: phase.isWindows, |
| policy: phase.policy, |
| shellCommand: phase.shellPayload, |
| segments: phase.segments, |
| segmentSatisfiedBy: phase.segmentSatisfiedBy, |
| authorizationPlan: phase.authorizationPlan, |
| }); |
| if (!execArgv) { |
| await sendSystemRunDenied(opts, phase.execution, { |
| reason: "execution-plan-miss", |
| message: "SYSTEM_RUN_DENIED: execution plan mismatch", |
| }); |
| return; |
| } |
|
|
| if (opts.preferMacAppExecHost) { |
| const macApprovalSource = |
| phase.approvalSource ?? |
| (phase.approvalGrantSource === "auto-review" ? "auto-review" : undefined); |
| const macApprovalDecision = macApprovalSource |
| ? null |
| : phase.approvalGrantSource === "explicit-approval" && phase.approvalDecision === null |
| ? "allow-once" |
| : phase.approvalDecision; |
| const execRequest: ExecHostRequest = { |
| command: execArgv, |
| |
| |
| rawCommand: execArgv === phase.argv ? phase.commandText || null : formatExecCommand(execArgv), |
| cwd: phase.cwd ?? null, |
| env: phase.envOverrides ?? null, |
| timeoutMs: phase.timeoutMs ?? null, |
| needsScreenRecording: phase.needsScreenRecording, |
| agentId: phase.agentId ?? null, |
| sessionKey: phase.sessionKey ?? null, |
| approvalDecision: macApprovalDecision, |
| approvalSource: macApprovalSource, |
| ...(phase.approvalGrantSource ? { policySnapshot: phase.evaluationPolicySnapshot } : {}), |
| }; |
| const response = await opts.runViaMacAppExecHost({ |
| approvals: phase.approvals, |
| request: execRequest, |
| signal: opts.signal, |
| }); |
| if (opts.signal?.aborted) { |
| return; |
| } |
| if (!response) { |
| if (opts.execHostEnforced || !opts.execHostFallbackAllowed) { |
| await sendSystemRunDenied(opts, phase.execution, { |
| reason: "companion-unavailable", |
| message: "COMPANION_APP_UNAVAILABLE: macOS app exec host unreachable", |
| }); |
| return; |
| } |
| } else if (!response.ok) { |
| await sendSystemRunDenied(opts, phase.execution, { |
| reason: normalizeDeniedReason(response.error.reason), |
| message: response.error.message, |
| }); |
| return; |
| } else { |
| const result: ExecHostRunResult = response.payload; |
| await sendSystemRunCompleted(opts, phase.execution, result, JSON.stringify(result)); |
| return; |
| } |
| } |
|
|
| if (phase.needsScreenRecording) { |
| await sendSystemRunDenied(opts, phase.execution, { |
| reason: "permission:screenRecording", |
| message: "PERMISSION_MISSING: screenRecording", |
| }); |
| return; |
| } |
|
|
| const allowAlwaysDecision = |
| phase.policy.approvalDecision === "allow-always" |
| ? resolveAllowAlwaysPersistenceDecision({ |
| segments: phase.segments, |
| cwd: phase.cwd, |
| env: phase.env, |
| platform: process.platform, |
| commandText: phase.commandText, |
| strictInlineEval: phase.strictInlineEval, |
| authorizationPlan: phase.authorizationPlan, |
| runtimePayload: phase.inlineEvalHit !== null, |
| }) |
| : undefined; |
| const authorizationSource: ExecApprovalUsageAuthorization["source"] = |
| phase.approvalSource === "ask-fallback" |
| ? "ask-fallback" |
| : phase.approvalSource === "auto-review" |
| ? "auto-review" |
| : (phase.approvalGrantSource ?? "current-policy"); |
| const delayedAuthorization = |
| authorizationSource === "explicit-approval" || authorizationSource === "auto-review"; |
| const authorization: ExecApprovalUsageAuthorization = { |
| source: authorizationSource, |
| security: phase.security, |
| ask: phase.ask, |
| allowlistSatisfied: phase.allowlistAuthorizationSatisfied || phase.durableApprovalSatisfied, |
| ...(delayedAuthorization ? { policySnapshot: phase.evaluationPolicySnapshot } : {}), |
| requireAutoAllowSkills: phase.segmentSatisfiedBy.includes("skills"), |
| requireExactCommandApproval: phase.durableApprovalRequirement === "exact-command", |
| requireDurableAllowlistApproval: phase.durableApprovalRequirement === "segment-allowlist", |
| }; |
|
|
| let assertCommittedAuthorization: () => void; |
| try { |
| assertCommittedAuthorization = await ( |
| opts.commitExecAuthorization ?? commitExecAuthorizationLocked |
| )({ |
| agentId: phase.agentId, |
| matches: phase.allowlistMatches, |
| command: phase.commandText, |
| resolvedPath: resolveApprovalAuditTrustPath(phase.segments[0]?.resolution ?? null, phase.cwd), |
| authorization, |
| ...(allowAlwaysDecision ? { allowAlwaysDecision } : {}), |
| }); |
| } catch { |
| |
| |
| |
| logWarn(`security: system.run approval state write failed (runId=${phase.runId})`); |
| await sendSystemRunDenied(opts, phase.execution, { |
| reason: "approval-state-write-failed", |
| message: APPROVAL_STATE_WRITE_FAILED_MESSAGE, |
| }); |
| return; |
| } |
|
|
| |
| |
| if (!(await revalidateSystemRunApprovedPathBindings(opts, phase))) { |
| return; |
| } |
|
|
| if (opts.signal?.aborted) { |
| return; |
| } |
| let authorizationDenied = false; |
| const assertCurrent = () => { |
| try { |
| assertCommittedAuthorization(); |
| } catch (error) { |
| authorizationDenied = true; |
| throw error; |
| } |
| }; |
| let result: RunResult; |
| try { |
| assertCurrent(); |
| result = await opts.runCommand( |
| execArgv, |
| phase.cwd, |
| phase.env, |
| phase.timeoutMs, |
| opts.signal, |
| assertCurrent, |
| ); |
| |
| |
| if (authorizationDenied) { |
| throw new Error("Exec approval changed before execution"); |
| } |
| } catch (error) { |
| if (!authorizationDenied) { |
| throw error; |
| } |
| await sendSystemRunDenied(opts, phase.execution, { |
| reason: "approval-required", |
| message: "SYSTEM_RUN_DENIED: exec approval changed before execution", |
| }); |
| return; |
| } |
| if (opts.signal?.aborted) { |
| return; |
| } |
| applyOutputTruncation(result); |
| await sendSystemRunCompleted( |
| opts, |
| phase.execution, |
| result, |
| JSON.stringify({ |
| exitCode: result.exitCode, |
| timedOut: result.timedOut, |
| success: result.success, |
| stdout: result.stdout, |
| stderr: result.stderr, |
| error: result.error ?? null, |
| }), |
| ); |
| } |
|
|
| |
| export async function handleSystemRunInvoke(opts: HandleSystemRunInvokeOptions): Promise<void> { |
| if (opts.signal?.aborted) { |
| return; |
| } |
| const parsed = await parseSystemRunPhase(opts); |
| if (!parsed || opts.signal?.aborted) { |
| return; |
| } |
| const policyPhase = await evaluateSystemRunPolicyPhase(opts, parsed); |
| if (!policyPhase || opts.signal?.aborted) { |
| return; |
| } |
| await executeSystemRunPhase(opts, policyPhase); |
| } |
| |
|
|