From 8ac924c7691d395da0ef1d792627d7c9f09498e5 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 3 Mar 2026 02:42:35 +0000 Subject: [PATCH] refactor(security): centralize audit execution context --- src/security/audit.ts | 115 ++++++++++++++++++++++++++++++------------ 1 file changed, 83 insertions(+), 32 deletions(-) diff --git a/src/security/audit.ts b/src/security/audit.ts index a27289879e0..5bd1a025f76 100644 --- a/src/security/audit.ts +++ b/src/security/audit.ts @@ -110,6 +110,24 @@ export type SecurityAuditOptions = { codeSafetySummaryCache?: Map>; }; +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; + configSnapshot: ConfigFileSnapshot | null; + codeSafetySummaryCache: Map>; +}; + 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 { - const findings: SecurityAuditFinding[] = []; +async function createAuditExecutionContext( + opts: SecurityAuditOptions, +): Promise { 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>(), + }; +} + +export async function runSecurityAudit(opts: SecurityAuditOptions): Promise { + 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 null) - : null; - - if (opts.includeFilesystem !== false) { - const codeSafetySummaryCache = - opts.codeSafetySummaryCache ?? new Map>(); + 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({