143 lines
4.3 KiB
TypeScript
143 lines
4.3 KiB
TypeScript
import {
|
|
analyzeArgvCommand,
|
|
evaluateExecAllowlist,
|
|
evaluateShellAllowlist,
|
|
resolvePlannedSegmentArgv,
|
|
resolveExecApprovals,
|
|
type ExecAllowlistEntry,
|
|
type ExecCommandSegment,
|
|
type ExecSecurity,
|
|
type SkillBinTrustEntry,
|
|
} from "../infra/exec-approvals.js";
|
|
import { resolveExecSafeBinRuntimePolicy } from "../infra/exec-safe-bin-runtime-policy.js";
|
|
import type { RunResult } from "./invoke-types.js";
|
|
|
|
export type SystemRunAllowlistAnalysis = {
|
|
analysisOk: boolean;
|
|
allowlistMatches: ExecAllowlistEntry[];
|
|
allowlistSatisfied: boolean;
|
|
segments: ExecCommandSegment[];
|
|
};
|
|
|
|
export function evaluateSystemRunAllowlist(params: {
|
|
shellCommand: string | null;
|
|
argv: string[];
|
|
approvals: ReturnType<typeof resolveExecApprovals>;
|
|
security: ExecSecurity;
|
|
safeBins: ReturnType<typeof resolveExecSafeBinRuntimePolicy>["safeBins"];
|
|
safeBinProfiles: ReturnType<typeof resolveExecSafeBinRuntimePolicy>["safeBinProfiles"];
|
|
trustedSafeBinDirs: ReturnType<typeof resolveExecSafeBinRuntimePolicy>["trustedSafeBinDirs"];
|
|
cwd: string | undefined;
|
|
env: Record<string, string> | undefined;
|
|
skillBins: SkillBinTrustEntry[];
|
|
autoAllowSkills: boolean;
|
|
}): SystemRunAllowlistAnalysis {
|
|
if (params.shellCommand) {
|
|
const allowlistEval = evaluateShellAllowlist({
|
|
command: params.shellCommand,
|
|
allowlist: params.approvals.allowlist,
|
|
safeBins: params.safeBins,
|
|
safeBinProfiles: params.safeBinProfiles,
|
|
cwd: params.cwd,
|
|
env: params.env,
|
|
trustedSafeBinDirs: params.trustedSafeBinDirs,
|
|
skillBins: params.skillBins,
|
|
autoAllowSkills: params.autoAllowSkills,
|
|
platform: process.platform,
|
|
});
|
|
return {
|
|
analysisOk: allowlistEval.analysisOk,
|
|
allowlistMatches: allowlistEval.allowlistMatches,
|
|
allowlistSatisfied:
|
|
params.security === "allowlist" && allowlistEval.analysisOk
|
|
? allowlistEval.allowlistSatisfied
|
|
: false,
|
|
segments: allowlistEval.segments,
|
|
};
|
|
}
|
|
|
|
const analysis = analyzeArgvCommand({ argv: params.argv, cwd: params.cwd, env: params.env });
|
|
const allowlistEval = evaluateExecAllowlist({
|
|
analysis,
|
|
allowlist: params.approvals.allowlist,
|
|
safeBins: params.safeBins,
|
|
safeBinProfiles: params.safeBinProfiles,
|
|
cwd: params.cwd,
|
|
trustedSafeBinDirs: params.trustedSafeBinDirs,
|
|
skillBins: params.skillBins,
|
|
autoAllowSkills: params.autoAllowSkills,
|
|
});
|
|
return {
|
|
analysisOk: analysis.ok,
|
|
allowlistMatches: allowlistEval.allowlistMatches,
|
|
allowlistSatisfied:
|
|
params.security === "allowlist" && analysis.ok ? allowlistEval.allowlistSatisfied : false,
|
|
segments: analysis.segments,
|
|
};
|
|
}
|
|
|
|
export function resolvePlannedAllowlistArgv(params: {
|
|
security: ExecSecurity;
|
|
shellCommand: string | null;
|
|
policy: {
|
|
approvedByAsk: boolean;
|
|
analysisOk: boolean;
|
|
allowlistSatisfied: boolean;
|
|
};
|
|
segments: ExecCommandSegment[];
|
|
}): string[] | undefined | null {
|
|
if (
|
|
params.security !== "allowlist" ||
|
|
params.policy.approvedByAsk ||
|
|
params.shellCommand ||
|
|
!params.policy.analysisOk ||
|
|
!params.policy.allowlistSatisfied ||
|
|
params.segments.length !== 1
|
|
) {
|
|
return undefined;
|
|
}
|
|
const plannedAllowlistArgv = resolvePlannedSegmentArgv(params.segments[0]);
|
|
return plannedAllowlistArgv && plannedAllowlistArgv.length > 0 ? plannedAllowlistArgv : null;
|
|
}
|
|
|
|
export function resolveSystemRunExecArgv(params: {
|
|
plannedAllowlistArgv: string[] | undefined;
|
|
argv: string[];
|
|
security: ExecSecurity;
|
|
isWindows: boolean;
|
|
policy: {
|
|
approvedByAsk: boolean;
|
|
analysisOk: boolean;
|
|
allowlistSatisfied: boolean;
|
|
};
|
|
shellCommand: string | null;
|
|
segments: ExecCommandSegment[];
|
|
}): string[] {
|
|
let execArgv = params.plannedAllowlistArgv ?? params.argv;
|
|
if (
|
|
params.security === "allowlist" &&
|
|
params.isWindows &&
|
|
!params.policy.approvedByAsk &&
|
|
params.shellCommand &&
|
|
params.policy.analysisOk &&
|
|
params.policy.allowlistSatisfied &&
|
|
params.segments.length === 1 &&
|
|
params.segments[0]?.argv.length > 0
|
|
) {
|
|
execArgv = params.segments[0].argv;
|
|
}
|
|
return execArgv;
|
|
}
|
|
|
|
export function applyOutputTruncation(result: RunResult): void {
|
|
if (!result.truncated) {
|
|
return;
|
|
}
|
|
const suffix = "... (truncated)";
|
|
if (result.stderr.trim().length > 0) {
|
|
result.stderr = `${result.stderr}\n${suffix}`;
|
|
} else {
|
|
result.stdout = `${result.stdout}\n${suffix}`;
|
|
}
|
|
}
|