Security: split audit runtime surfaces

This commit is contained in:
Vincent Koc
2026-03-15 23:30:04 -07:00
parent d163278e9c
commit c7137270d1
5 changed files with 81 additions and 52 deletions

View File

@@ -0,0 +1 @@
export { collectChannelSecurityFindings } from "./audit-channel.js";

View File

@@ -1,9 +1,9 @@
export {
isNumericTelegramUserId,
normalizeTelegramAllowFromEntry,
} from "../../extensions/telegram/src/allow-from.js";
export { readChannelAllowFromStore } from "../pairing/pairing-store.js";
export {
isDiscordMutableAllowEntry,
isZalouserMutableGroupEntry,
} from "./mutable-allowlist-detectors.js";
export {
isNumericTelegramUserId,
normalizeTelegramAllowFromEntry,
} from "../../extensions/telegram/src/allow-from.js";

View File

@@ -0,0 +1,4 @@
export {
collectInstalledSkillsCodeSafetyFindings,
collectPluginsCodeSafetyFindings,
} from "./audit-extra.async.js";

View File

@@ -0,0 +1,26 @@
export {
collectAttackSurfaceSummaryFindings,
collectExposureMatrixFindings,
collectGatewayHttpNoAuthFindings,
collectGatewayHttpSessionKeyOverrideFindings,
collectHooksHardeningFindings,
collectLikelyMultiUserSetupFindings,
collectMinimalProfileOverrideFindings,
collectModelHygieneFindings,
collectNodeDangerousAllowCommandFindings,
collectNodeDenyCommandPatternFindings,
collectSandboxDangerousConfigFindings,
collectSandboxDockerNoopFindings,
collectSecretsInConfigFindings,
collectSmallModelRiskFindings,
collectSyncedFolderFindings,
} from "./audit-extra.sync.js";
export {
collectSandboxBrowserHashLabelFindings,
collectIncludeFilePermFindings,
collectPluginsTrustFindings,
collectStateDeepFilesystemFindings,
collectWorkspaceSkillSymlinkEscapeFindings,
readConfigSnapshotForAudit,
} from "./audit-extra.async.js";

View File

@@ -21,32 +21,6 @@ import {
} from "../infra/exec-safe-bin-runtime-policy.js";
import { normalizeTrustedSafeBinDirs } from "../infra/exec-safe-bin-trust.js";
import { isBlockedHostnameOrIp, isPrivateNetworkAllowedByPolicy } from "../infra/net/ssrf.js";
import { collectChannelSecurityFindings } from "./audit-channel.js";
import {
collectAttackSurfaceSummaryFindings,
collectExposureMatrixFindings,
collectGatewayHttpNoAuthFindings,
collectGatewayHttpSessionKeyOverrideFindings,
collectHooksHardeningFindings,
collectIncludeFilePermFindings,
collectInstalledSkillsCodeSafetyFindings,
collectLikelyMultiUserSetupFindings,
collectSandboxBrowserHashLabelFindings,
collectMinimalProfileOverrideFindings,
collectModelHygieneFindings,
collectNodeDangerousAllowCommandFindings,
collectNodeDenyCommandPatternFindings,
collectSmallModelRiskFindings,
collectSandboxDangerousConfigFindings,
collectSandboxDockerNoopFindings,
collectPluginsTrustFindings,
collectSecretsInConfigFindings,
collectPluginsCodeSafetyFindings,
collectStateDeepFilesystemFindings,
collectSyncedFolderFindings,
collectWorkspaceSkillSymlinkEscapeFindings,
readConfigSnapshotForAudit,
} from "./audit-extra.js";
import {
formatPermissionDetail,
formatPermissionRemediation,
@@ -138,12 +112,32 @@ type AuditExecutionContext = {
};
let channelPluginsModulePromise: Promise<typeof import("../channels/plugins/index.js")> | undefined;
let auditNonDeepModulePromise: Promise<typeof import("./audit.nondeep.runtime.js")> | undefined;
let auditDeepModulePromise: Promise<typeof import("./audit.deep.runtime.js")> | undefined;
let auditChannelModulePromise:
| Promise<typeof import("./audit-channel.collect.runtime.js")>
| undefined;
async function loadChannelPlugins() {
channelPluginsModulePromise ??= import("../channels/plugins/index.js");
return await channelPluginsModulePromise;
}
async function loadAuditNonDeepModule() {
auditNonDeepModulePromise ??= import("./audit.nondeep.runtime.js");
return await auditNonDeepModulePromise;
}
async function loadAuditDeepModule() {
auditDeepModulePromise ??= import("./audit.deep.runtime.js");
return await auditDeepModulePromise;
}
async function loadAuditChannelModule() {
auditChannelModulePromise ??= import("./audit-channel.collect.runtime.js");
return await auditChannelModulePromise;
}
function countBySeverity(findings: SecurityAuditFinding[]): SecurityAuditSummary {
let critical = 0;
let warn = 0;
@@ -1144,6 +1138,7 @@ async function createAuditExecutionContext(
const deepTimeoutMs = Math.max(250, opts.deepTimeoutMs ?? 5000);
const stateDir = opts.stateDir ?? resolveStateDir(env);
const configPath = opts.configPath ?? resolveConfigPath(env, stateDir);
const { readConfigSnapshotForAudit } = await loadAuditNonDeepModule();
const configSnapshot = includeFilesystem
? opts.configSnapshot !== undefined
? opts.configSnapshot
@@ -1174,28 +1169,29 @@ export async function runSecurityAudit(opts: SecurityAuditOptions): Promise<Secu
const findings: SecurityAuditFinding[] = [];
const context = await createAuditExecutionContext(opts);
const { cfg, env, platform, stateDir, configPath } = context;
const auditNonDeep = await loadAuditNonDeepModule();
findings.push(...collectAttackSurfaceSummaryFindings(cfg));
findings.push(...collectSyncedFolderFindings({ stateDir, configPath }));
findings.push(...auditNonDeep.collectAttackSurfaceSummaryFindings(cfg));
findings.push(...auditNonDeep.collectSyncedFolderFindings({ stateDir, configPath }));
findings.push(...collectGatewayConfigFindings(cfg, context.sourceConfig, env));
findings.push(...collectBrowserControlFindings(cfg, env));
findings.push(...collectLoggingFindings(cfg));
findings.push(...collectElevatedFindings(cfg));
findings.push(...collectExecRuntimeFindings(cfg));
findings.push(...collectHooksHardeningFindings(cfg, env));
findings.push(...collectGatewayHttpNoAuthFindings(cfg, env));
findings.push(...collectGatewayHttpSessionKeyOverrideFindings(cfg));
findings.push(...collectSandboxDockerNoopFindings(cfg));
findings.push(...collectSandboxDangerousConfigFindings(cfg));
findings.push(...collectNodeDenyCommandPatternFindings(cfg));
findings.push(...collectNodeDangerousAllowCommandFindings(cfg));
findings.push(...collectMinimalProfileOverrideFindings(cfg));
findings.push(...collectSecretsInConfigFindings(cfg));
findings.push(...collectModelHygieneFindings(cfg));
findings.push(...collectSmallModelRiskFindings({ cfg, env }));
findings.push(...collectExposureMatrixFindings(cfg));
findings.push(...collectLikelyMultiUserSetupFindings(cfg));
findings.push(...auditNonDeep.collectHooksHardeningFindings(cfg, env));
findings.push(...auditNonDeep.collectGatewayHttpNoAuthFindings(cfg, env));
findings.push(...auditNonDeep.collectGatewayHttpSessionKeyOverrideFindings(cfg));
findings.push(...auditNonDeep.collectSandboxDockerNoopFindings(cfg));
findings.push(...auditNonDeep.collectSandboxDangerousConfigFindings(cfg));
findings.push(...auditNonDeep.collectNodeDenyCommandPatternFindings(cfg));
findings.push(...auditNonDeep.collectNodeDangerousAllowCommandFindings(cfg));
findings.push(...auditNonDeep.collectMinimalProfileOverrideFindings(cfg));
findings.push(...auditNonDeep.collectSecretsInConfigFindings(cfg));
findings.push(...auditNonDeep.collectModelHygieneFindings(cfg));
findings.push(...auditNonDeep.collectSmallModelRiskFindings({ cfg, env }));
findings.push(...auditNonDeep.collectExposureMatrixFindings(cfg));
findings.push(...auditNonDeep.collectLikelyMultiUserSetupFindings(cfg));
if (context.includeFilesystem) {
findings.push(
@@ -1209,7 +1205,7 @@ export async function runSecurityAudit(opts: SecurityAuditOptions): Promise<Secu
);
if (context.configSnapshot) {
findings.push(
...(await collectIncludeFilePermFindings({
...(await auditNonDeep.collectIncludeFilePermFindings({
configSnapshot: context.configSnapshot,
env,
platform,
@@ -1218,7 +1214,7 @@ export async function runSecurityAudit(opts: SecurityAuditOptions): Promise<Secu
);
}
findings.push(
...(await collectStateDeepFilesystemFindings({
...(await auditNonDeep.collectStateDeepFilesystemFindings({
cfg,
env,
stateDir,
@@ -1226,22 +1222,23 @@ export async function runSecurityAudit(opts: SecurityAuditOptions): Promise<Secu
execIcacls: context.execIcacls,
})),
);
findings.push(...(await collectWorkspaceSkillSymlinkEscapeFindings({ cfg })));
findings.push(...(await auditNonDeep.collectWorkspaceSkillSymlinkEscapeFindings({ cfg })));
findings.push(
...(await collectSandboxBrowserHashLabelFindings({
...(await auditNonDeep.collectSandboxBrowserHashLabelFindings({
execDockerRawFn: context.execDockerRawFn,
})),
);
findings.push(...(await collectPluginsTrustFindings({ cfg, stateDir })));
findings.push(...(await auditNonDeep.collectPluginsTrustFindings({ cfg, stateDir })));
if (context.deep) {
const auditDeep = await loadAuditDeepModule();
findings.push(
...(await collectPluginsCodeSafetyFindings({
...(await auditDeep.collectPluginsCodeSafetyFindings({
stateDir,
summaryCache: context.codeSafetySummaryCache,
})),
);
findings.push(
...(await collectInstalledSkillsCodeSafetyFindings({
...(await auditDeep.collectInstalledSkillsCodeSafetyFindings({
cfg,
stateDir,
summaryCache: context.codeSafetySummaryCache,
@@ -1255,6 +1252,7 @@ export async function runSecurityAudit(opts: SecurityAuditOptions): Promise<Secu
(context.plugins !== undefined || hasPotentialConfiguredChannels(cfg, env));
if (shouldAuditChannelSecurity) {
const channelPlugins = context.plugins ?? (await loadChannelPlugins()).listChannelPlugins();
const { collectChannelSecurityFindings } = await loadAuditChannelModule();
findings.push(
...(await collectChannelSecurityFindings({
cfg,