135 lines
3.8 KiB
TypeScript
135 lines
3.8 KiB
TypeScript
import { requiresExecApproval, type ExecAsk, type ExecSecurity } from "../infra/exec-approvals.js";
|
|
|
|
export type ExecApprovalDecision = "allow-once" | "allow-always" | null;
|
|
|
|
export type SystemRunPolicyDecision = {
|
|
analysisOk: boolean;
|
|
allowlistSatisfied: boolean;
|
|
shellWrapperBlocked: boolean;
|
|
windowsShellWrapperBlocked: boolean;
|
|
requiresAsk: boolean;
|
|
approvalDecision: ExecApprovalDecision;
|
|
approvedByAsk: boolean;
|
|
} & (
|
|
| {
|
|
allowed: true;
|
|
}
|
|
| {
|
|
allowed: false;
|
|
eventReason: "security=deny" | "approval-required" | "allowlist-miss";
|
|
errorMessage: string;
|
|
}
|
|
);
|
|
|
|
export function resolveExecApprovalDecision(value: unknown): ExecApprovalDecision {
|
|
if (value === "allow-once" || value === "allow-always") {
|
|
return value;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function formatSystemRunAllowlistMissMessage(params?: {
|
|
shellWrapperBlocked?: boolean;
|
|
windowsShellWrapperBlocked?: boolean;
|
|
}): string {
|
|
if (params?.windowsShellWrapperBlocked) {
|
|
return (
|
|
"SYSTEM_RUN_DENIED: allowlist miss " +
|
|
"(Windows shell wrappers like cmd.exe /c require approval; " +
|
|
"approve once/always or run with --ask on-miss|always)"
|
|
);
|
|
}
|
|
if (params?.shellWrapperBlocked) {
|
|
return (
|
|
"SYSTEM_RUN_DENIED: allowlist miss " +
|
|
"(shell wrappers like sh/bash/zsh -c require approval; " +
|
|
"approve once/always or run with --ask on-miss|always)"
|
|
);
|
|
}
|
|
return "SYSTEM_RUN_DENIED: allowlist miss";
|
|
}
|
|
|
|
export function evaluateSystemRunPolicy(params: {
|
|
security: ExecSecurity;
|
|
ask: ExecAsk;
|
|
analysisOk: boolean;
|
|
allowlistSatisfied: boolean;
|
|
approvalDecision: ExecApprovalDecision;
|
|
approved?: boolean;
|
|
isWindows: boolean;
|
|
cmdInvocation: boolean;
|
|
shellWrapperInvocation: boolean;
|
|
}): SystemRunPolicyDecision {
|
|
const shellWrapperBlocked = params.security === "allowlist" && params.shellWrapperInvocation;
|
|
const windowsShellWrapperBlocked =
|
|
shellWrapperBlocked && params.isWindows && params.cmdInvocation;
|
|
const analysisOk = shellWrapperBlocked ? false : params.analysisOk;
|
|
const allowlistSatisfied = shellWrapperBlocked ? false : params.allowlistSatisfied;
|
|
const approvedByAsk = params.approvalDecision !== null || params.approved === true;
|
|
|
|
if (params.security === "deny") {
|
|
return {
|
|
allowed: false,
|
|
eventReason: "security=deny",
|
|
errorMessage: "SYSTEM_RUN_DISABLED: security=deny",
|
|
analysisOk,
|
|
allowlistSatisfied,
|
|
shellWrapperBlocked,
|
|
windowsShellWrapperBlocked,
|
|
requiresAsk: false,
|
|
approvalDecision: params.approvalDecision,
|
|
approvedByAsk,
|
|
};
|
|
}
|
|
|
|
const requiresAsk = requiresExecApproval({
|
|
ask: params.ask,
|
|
security: params.security,
|
|
analysisOk,
|
|
allowlistSatisfied,
|
|
});
|
|
if (requiresAsk && !approvedByAsk) {
|
|
return {
|
|
allowed: false,
|
|
eventReason: "approval-required",
|
|
errorMessage: "SYSTEM_RUN_DENIED: approval required",
|
|
analysisOk,
|
|
allowlistSatisfied,
|
|
shellWrapperBlocked,
|
|
windowsShellWrapperBlocked,
|
|
requiresAsk,
|
|
approvalDecision: params.approvalDecision,
|
|
approvedByAsk,
|
|
};
|
|
}
|
|
|
|
if (params.security === "allowlist" && (!analysisOk || !allowlistSatisfied) && !approvedByAsk) {
|
|
return {
|
|
allowed: false,
|
|
eventReason: "allowlist-miss",
|
|
errorMessage: formatSystemRunAllowlistMissMessage({
|
|
shellWrapperBlocked,
|
|
windowsShellWrapperBlocked,
|
|
}),
|
|
analysisOk,
|
|
allowlistSatisfied,
|
|
shellWrapperBlocked,
|
|
windowsShellWrapperBlocked,
|
|
requiresAsk,
|
|
approvalDecision: params.approvalDecision,
|
|
approvedByAsk,
|
|
};
|
|
}
|
|
|
|
return {
|
|
allowed: true,
|
|
analysisOk,
|
|
allowlistSatisfied,
|
|
shellWrapperBlocked,
|
|
windowsShellWrapperBlocked,
|
|
requiresAsk,
|
|
approvalDecision: params.approvalDecision,
|
|
approvedByAsk,
|
|
};
|
|
}
|