refactor(security): centralize audit execution context

This commit is contained in:
Peter Steinberger
2026-03-03 02:42:35 +00:00
parent 2d033d2aa8
commit 8ac924c769

View File

@@ -110,6 +110,24 @@ export type SecurityAuditOptions = {
codeSafetySummaryCache?: Map<string, Promise<unknown>>;
};
type AuditExecutionContext = {
cfg: OpenClawConfig;
env: NodeJS.ProcessEnv;
platform: NodeJS.Platform;
includeFilesystem: boolean;
includeChannelSecurity: boolean;
deep: boolean;
deepTimeoutMs: number;
stateDir: string;
configPath: string;
execIcacls?: ExecFn;
execDockerRawFn?: typeof execDockerRaw;
probeGatewayFn?: typeof probeGateway;
plugins?: ReturnType<typeof listChannelPlugins>;
configSnapshot: ConfigFileSnapshot | null;
codeSafetySummaryCache: Map<string, Promise<unknown>>;
};
function countBySeverity(findings: SecurityAuditFinding[]): SecurityAuditSummary {
let critical = 0;
let warn = 0;
@@ -1004,14 +1022,46 @@ async function maybeProbeGateway(params: {
};
}
export async function runSecurityAudit(opts: SecurityAuditOptions): Promise<SecurityAuditReport> {
const findings: SecurityAuditFinding[] = [];
async function createAuditExecutionContext(
opts: SecurityAuditOptions,
): Promise<AuditExecutionContext> {
const cfg = opts.config;
const env = opts.env ?? process.env;
const platform = opts.platform ?? process.platform;
const execIcacls = opts.execIcacls;
const includeFilesystem = opts.includeFilesystem !== false;
const includeChannelSecurity = opts.includeChannelSecurity !== false;
const deep = opts.deep === true;
const deepTimeoutMs = Math.max(250, opts.deepTimeoutMs ?? 5000);
const stateDir = opts.stateDir ?? resolveStateDir(env);
const configPath = opts.configPath ?? resolveConfigPath(env, stateDir);
const configSnapshot = includeFilesystem
? opts.configSnapshot !== undefined
? opts.configSnapshot
: await readConfigSnapshotForAudit({ env, configPath }).catch(() => null)
: null;
return {
cfg,
env,
platform,
includeFilesystem,
includeChannelSecurity,
deep,
deepTimeoutMs,
stateDir,
configPath,
execIcacls: opts.execIcacls,
execDockerRawFn: opts.execDockerRawFn,
probeGatewayFn: opts.probeGatewayFn,
plugins: opts.plugins,
configSnapshot,
codeSafetySummaryCache: opts.codeSafetySummaryCache ?? new Map<string, Promise<unknown>>(),
};
}
export async function runSecurityAudit(opts: SecurityAuditOptions): Promise<SecurityAuditReport> {
const findings: SecurityAuditFinding[] = [];
const context = await createAuditExecutionContext(opts);
const { cfg, env, platform, stateDir, configPath } = context;
findings.push(...collectAttackSurfaceSummaryFindings(cfg));
findings.push(...collectSyncedFolderFindings({ stateDir, configPath }));
@@ -1035,71 +1085,72 @@ export async function runSecurityAudit(opts: SecurityAuditOptions): Promise<Secu
findings.push(...collectExposureMatrixFindings(cfg));
findings.push(...collectLikelyMultiUserSetupFindings(cfg));
const configSnapshot =
opts.includeFilesystem !== false
? opts.configSnapshot !== undefined
? opts.configSnapshot
: await readConfigSnapshotForAudit({ env, configPath }).catch(() => null)
: null;
if (opts.includeFilesystem !== false) {
const codeSafetySummaryCache =
opts.codeSafetySummaryCache ?? new Map<string, Promise<unknown>>();
if (context.includeFilesystem) {
findings.push(
...(await collectFilesystemFindings({
stateDir,
configPath,
env,
platform,
execIcacls,
execIcacls: context.execIcacls,
})),
);
if (configSnapshot) {
if (context.configSnapshot) {
findings.push(
...(await collectIncludeFilePermFindings({ configSnapshot, env, platform, execIcacls })),
...(await collectIncludeFilePermFindings({
configSnapshot: context.configSnapshot,
env,
platform,
execIcacls: context.execIcacls,
})),
);
}
findings.push(
...(await collectStateDeepFilesystemFindings({ cfg, env, stateDir, platform, execIcacls })),
...(await collectStateDeepFilesystemFindings({
cfg,
env,
stateDir,
platform,
execIcacls: context.execIcacls,
})),
);
findings.push(...(await collectWorkspaceSkillSymlinkEscapeFindings({ cfg })));
findings.push(
...(await collectSandboxBrowserHashLabelFindings({
execDockerRawFn: opts.execDockerRawFn,
execDockerRawFn: context.execDockerRawFn,
})),
);
findings.push(...(await collectPluginsTrustFindings({ cfg, stateDir })));
if (opts.deep === true) {
if (context.deep) {
findings.push(
...(await collectPluginsCodeSafetyFindings({
stateDir,
summaryCache: codeSafetySummaryCache,
summaryCache: context.codeSafetySummaryCache,
})),
);
findings.push(
...(await collectInstalledSkillsCodeSafetyFindings({
cfg,
stateDir,
summaryCache: codeSafetySummaryCache,
summaryCache: context.codeSafetySummaryCache,
})),
);
}
}
if (opts.includeChannelSecurity !== false) {
const plugins = opts.plugins ?? listChannelPlugins();
if (context.includeChannelSecurity) {
const plugins = context.plugins ?? listChannelPlugins();
findings.push(...(await collectChannelSecurityFindings({ cfg, plugins })));
}
const deep =
opts.deep === true
? await maybeProbeGateway({
cfg,
env,
timeoutMs: Math.max(250, opts.deepTimeoutMs ?? 5000),
probe: opts.probeGatewayFn ?? probeGateway,
})
: undefined;
const deep = context.deep
? await maybeProbeGateway({
cfg,
env,
timeoutMs: context.deepTimeoutMs,
probe: context.probeGatewayFn ?? probeGateway,
})
: undefined;
if (deep?.gateway?.attempted && !deep.gateway.ok) {
findings.push({