fix(security): extend audit hardening checks

This commit is contained in:
Peter Steinberger
2026-02-13 16:26:37 +01:00
parent faa4959111
commit 1def8c5448
8 changed files with 599 additions and 2 deletions

View File

@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
- Security/Gateway + ACP: block high-risk tools (`sessions_spawn`, `sessions_send`, `gateway`, `whatsapp_login`) from HTTP `/tools/invoke` by default with `gateway.tools.{allow,deny}` overrides, and harden ACP permission selection to fail closed when tool identity/options are ambiguous while supporting `allow_always`/`reject_always`. (#15390) Thanks @aether-ai-agent.
- MS Teams: preserve parsed mention entities/text when appending OneDrive fallback file links, and accept broader real-world Teams mention ID formats (`29:...`, `8:orgid:...`) while still rejecting placeholder patterns. (#15436) Thanks @hyojin.
- Security/Audit: distinguish external webhooks (`hooks.enabled`) from internal hooks (`hooks.internal.enabled`) in attack-surface summaries to avoid false exposure signals when only internal hooks are enabled. (#13474) Thanks @mcaxtr.
- Security/Audit: add misconfiguration checks for sandbox Docker config with sandbox mode off, ineffective `gateway.nodes.denyCommands` entries, global minimal tool-profile overrides by agent profiles, and permissive extension-plugin tool reachability.
- Auto-reply/Threading: auto-inject implicit reply threading so `replyToMode` works without requiring model-emitted `[[reply_to_current]]`, while preserving `replyToMode: "off"` behavior for implicit Slack replies and keeping block-streaming chunk coalescing stable under `replyToMode: "first"`. (#14976) Thanks @Diaspar4u.
- Sandbox: pass configured `sandbox.docker.env` variables to sandbox containers at `docker create` time. (#15138) Thanks @stevebot-alive.
- Onboarding/CLI: restore terminal state without resuming paused `stdin`, so onboarding exits cleanly after choosing Web UI and the installer returns instead of appearing stuck.

View File

@@ -25,3 +25,4 @@ openclaw security audit --fix
The audit warns when multiple DM senders share the main session and recommends **secure DM mode**: `session.dmScope="per-channel-peer"` (or `per-account-channel-peer` for multi-account channels) for shared inboxes.
It also warns when small models (`<=300B`) are used without sandboxing and with web/browser tools enabled.
For webhook ingress, it warns when `hooks.defaultSessionKey` is unset, when request `sessionKey` overrides are enabled, and when overrides are enabled without `hooks.allowedSessionKeyPrefixes`.
It also warns when sandbox Docker settings are configured while sandbox mode is off, when `gateway.nodes.denyCommands` uses ineffective pattern-like/unknown entries, when global `tools.profile="minimal"` is overridden by agent tool profiles, and when installed extension plugin tools may be reachable under permissive tool policy.

View File

@@ -45,6 +45,7 @@ Start with the smallest access that still works, then widen it as you gain confi
- **Browser control exposure** (remote nodes, relay ports, remote CDP endpoints).
- **Local disk hygiene** (permissions, symlinks, config includes, “synced folder” paths).
- **Plugins** (extensions exist without an explicit allowlist).
- **Policy drift/misconfig** (sandbox docker settings configured but sandbox mode off; ineffective `gateway.nodes.denyCommands` patterns; global `tools.profile="minimal"` overridden by per-agent profiles; extension plugin tools reachable under permissive tool policy).
- **Model hygiene** (warn when configured models look legacy; not a hard block).
If you run `--deep`, OpenClaw also attempts a best-effort live Gateway probe.

View File

@@ -6,15 +6,24 @@
import JSON5 from "json5";
import fs from "node:fs/promises";
import path from "node:path";
import type { SandboxToolPolicy } from "../agents/sandbox/types.js";
import type { OpenClawConfig, ConfigFileSnapshot } from "../config/config.js";
import type { AgentToolsConfig } from "../config/types.tools.js";
import type { ExecFn } from "./windows-acl.js";
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
import { isToolAllowedByPolicies } from "../agents/pi-tools.policy.js";
import {
resolveSandboxConfigForAgent,
resolveSandboxToolPolicyForAgent,
} from "../agents/sandbox.js";
import { loadWorkspaceSkillEntries } from "../agents/skills.js";
import { resolveToolProfilePolicy } from "../agents/tool-policy.js";
import { MANIFEST_KEY } from "../compat/legacy-names.js";
import { resolveNativeSkillsEnabled } from "../config/commands.js";
import { createConfigIO } from "../config/config.js";
import { INCLUDE_KEY, MAX_INCLUDE_DEPTH } from "../config/includes.js";
import { resolveOAuthDir } from "../config/paths.js";
import { normalizePluginsConfig } from "../plugins/config-state.js";
import { normalizeAgentId } from "../routing/session-key.js";
import {
formatPermissionDetail,
@@ -196,6 +205,135 @@ function formatCodeSafetyDetails(findings: SkillScanFinding[], rootDir: string):
.join("\n");
}
function unionAllow(base?: string[], extra?: string[]): string[] | undefined {
if (!Array.isArray(extra) || extra.length === 0) {
return base;
}
if (!Array.isArray(base) || base.length === 0) {
return Array.from(new Set(["*", ...extra]));
}
return Array.from(new Set([...base, ...extra]));
}
function pickToolPolicy(config?: {
allow?: string[];
alsoAllow?: string[];
deny?: string[];
}): SandboxToolPolicy | undefined {
if (!config) {
return undefined;
}
const allow = Array.isArray(config.allow)
? unionAllow(config.allow, config.alsoAllow)
: Array.isArray(config.alsoAllow) && config.alsoAllow.length > 0
? unionAllow(undefined, config.alsoAllow)
: undefined;
const deny = Array.isArray(config.deny) ? config.deny : undefined;
if (!allow && !deny) {
return undefined;
}
return { allow, deny };
}
function resolveToolPolicies(params: {
cfg: OpenClawConfig;
agentTools?: AgentToolsConfig;
sandboxMode?: "off" | "non-main" | "all";
agentId?: string | null;
}): Array<SandboxToolPolicy | undefined> {
const profile = params.agentTools?.profile ?? params.cfg.tools?.profile;
const profilePolicy = resolveToolProfilePolicy(profile);
const policies: Array<SandboxToolPolicy | undefined> = [
profilePolicy,
pickToolPolicy(params.cfg.tools ?? undefined),
pickToolPolicy(params.agentTools),
];
if (params.sandboxMode === "all") {
policies.push(resolveSandboxToolPolicyForAgent(params.cfg, params.agentId ?? undefined));
}
return policies;
}
function normalizePluginIdSet(entries: string[]): Set<string> {
return new Set(entries.map((entry) => entry.trim().toLowerCase()).filter(Boolean));
}
function resolveEnabledExtensionPluginIds(params: {
cfg: OpenClawConfig;
pluginDirs: string[];
}): string[] {
const normalized = normalizePluginsConfig(params.cfg.plugins);
if (!normalized.enabled) {
return [];
}
const allowSet = normalizePluginIdSet(normalized.allow);
const denySet = normalizePluginIdSet(normalized.deny);
const entryById = new Map<string, { enabled?: boolean }>();
for (const [id, entry] of Object.entries(normalized.entries)) {
entryById.set(id.trim().toLowerCase(), entry);
}
const enabled: string[] = [];
for (const id of params.pluginDirs) {
const normalizedId = id.trim().toLowerCase();
if (!normalizedId) {
continue;
}
if (denySet.has(normalizedId)) {
continue;
}
if (allowSet.size > 0 && !allowSet.has(normalizedId)) {
continue;
}
if (entryById.get(normalizedId)?.enabled === false) {
continue;
}
enabled.push(normalizedId);
}
return enabled;
}
function collectAllowEntries(config?: { allow?: string[]; alsoAllow?: string[] }): string[] {
const out: string[] = [];
if (Array.isArray(config?.allow)) {
out.push(...config.allow);
}
if (Array.isArray(config?.alsoAllow)) {
out.push(...config.alsoAllow);
}
return out.map((entry) => entry.trim().toLowerCase()).filter(Boolean);
}
function hasExplicitPluginAllow(params: {
allowEntries: string[];
enabledPluginIds: Set<string>;
}): boolean {
return params.allowEntries.some(
(entry) => entry === "group:plugins" || params.enabledPluginIds.has(entry),
);
}
function hasProviderPluginAllow(params: {
byProvider?: Record<string, { allow?: string[]; alsoAllow?: string[]; deny?: string[] }>;
enabledPluginIds: Set<string>;
}): boolean {
if (!params.byProvider) {
return false;
}
for (const policy of Object.values(params.byProvider)) {
if (
hasExplicitPluginAllow({
allowEntries: collectAllowEntries(policy),
enabledPluginIds: params.enabledPluginIds,
})
) {
return true;
}
}
return false;
}
// --------------------------------------------------------------------------
// Exported collectors
// --------------------------------------------------------------------------
@@ -297,6 +435,78 @@ export async function collectPluginsTrustFindings(params: {
});
}
const enabledExtensionPluginIds = resolveEnabledExtensionPluginIds({
cfg: params.cfg,
pluginDirs,
});
if (enabledExtensionPluginIds.length > 0) {
const enabledPluginSet = new Set(enabledExtensionPluginIds);
const contexts: Array<{
label: string;
agentId?: string;
tools?: AgentToolsConfig;
}> = [{ label: "default" }];
for (const entry of params.cfg.agents?.list ?? []) {
if (!entry || typeof entry !== "object" || typeof entry.id !== "string") {
continue;
}
contexts.push({
label: `agents.list.${entry.id}`,
agentId: entry.id,
tools: entry.tools,
});
}
const permissiveContexts: string[] = [];
for (const context of contexts) {
const profile = context.tools?.profile ?? params.cfg.tools?.profile;
const restrictiveProfile = Boolean(resolveToolProfilePolicy(profile));
const sandboxMode = resolveSandboxConfigForAgent(params.cfg, context.agentId).mode;
const policies = resolveToolPolicies({
cfg: params.cfg,
agentTools: context.tools,
sandboxMode,
agentId: context.agentId,
});
const broadPolicy = isToolAllowedByPolicies("__openclaw_plugin_probe__", policies);
const explicitPluginAllow =
!restrictiveProfile &&
(hasExplicitPluginAllow({
allowEntries: collectAllowEntries(params.cfg.tools),
enabledPluginIds: enabledPluginSet,
}) ||
hasProviderPluginAllow({
byProvider: params.cfg.tools?.byProvider,
enabledPluginIds: enabledPluginSet,
}) ||
hasExplicitPluginAllow({
allowEntries: collectAllowEntries(context.tools),
enabledPluginIds: enabledPluginSet,
}) ||
hasProviderPluginAllow({
byProvider: context.tools?.byProvider,
enabledPluginIds: enabledPluginSet,
}));
if (broadPolicy || explicitPluginAllow) {
permissiveContexts.push(context.label);
}
}
if (permissiveContexts.length > 0) {
findings.push({
checkId: "plugins.tools_reachable_permissive_policy",
severity: "warn",
title: "Extension plugin tools may be reachable under permissive tool policy",
detail:
`Enabled extension plugins: ${enabledExtensionPluginIds.join(", ")}.\n` +
`Permissive tool policy contexts:\n${permissiveContexts.map((entry) => `- ${entry}`).join("\n")}`,
remediation:
"Use restrictive profiles (`minimal`/`coding`) or explicit tool allowlists that exclude plugin tools for agents handling untrusted input.",
});
}
}
return findings;
}

View File

@@ -15,6 +15,7 @@ import { resolveToolProfilePolicy } from "../agents/tool-policy.js";
import { resolveBrowserConfig } from "../browser/config.js";
import { formatCliCommand } from "../cli/command-format.js";
import { resolveGatewayAuth } from "../gateway/auth.js";
import { resolveNodeCommandAllowlist } from "../gateway/node-command-policy.js";
export type SecurityAuditFinding = {
checkId: string;
@@ -185,11 +186,29 @@ function extractAgentIdFromSource(source: string): string | null {
return match?.[1] ?? null;
}
function pickToolPolicy(config?: { allow?: string[]; deny?: string[] }): SandboxToolPolicy | null {
function unionAllow(base?: string[], extra?: string[]): string[] | undefined {
if (!Array.isArray(extra) || extra.length === 0) {
return base;
}
if (!Array.isArray(base) || base.length === 0) {
return Array.from(new Set(["*", ...extra]));
}
return Array.from(new Set([...base, ...extra]));
}
function pickToolPolicy(config?: {
allow?: string[];
alsoAllow?: string[];
deny?: string[];
}): SandboxToolPolicy | null {
if (!config) {
return null;
}
const allow = Array.isArray(config.allow) ? config.allow : undefined;
const allow = Array.isArray(config.allow)
? unionAllow(config.allow, config.alsoAllow)
: Array.isArray(config.alsoAllow) && config.alsoAllow.length > 0
? unionAllow(undefined, config.alsoAllow)
: undefined;
const deny = Array.isArray(config.deny) ? config.deny : undefined;
if (!allow && !deny) {
return null;
@@ -197,6 +216,61 @@ function pickToolPolicy(config?: { allow?: string[]; deny?: string[] }): Sandbox
return { allow, deny };
}
function hasConfiguredDockerConfig(
docker: Record<string, unknown> | undefined | null,
): docker is Record<string, unknown> {
if (!docker || typeof docker !== "object") {
return false;
}
return Object.values(docker).some((value) => value !== undefined);
}
function normalizeNodeCommand(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
}
function listKnownNodeCommands(cfg: OpenClawConfig): Set<string> {
const baseCfg: OpenClawConfig = {
...cfg,
gateway: {
...cfg.gateway,
nodes: {
...cfg.gateway?.nodes,
denyCommands: [],
},
},
};
const out = new Set<string>();
for (const platform of ["ios", "android", "macos", "linux", "windows", "unknown"]) {
const allow = resolveNodeCommandAllowlist(baseCfg, { platform });
for (const cmd of allow) {
const normalized = normalizeNodeCommand(cmd);
if (normalized) {
out.add(normalized);
}
}
}
return out;
}
function looksLikeNodeCommandPattern(value: string): boolean {
if (!value) {
return false;
}
if (/[?*[\]{}(),|]/.test(value)) {
return true;
}
if (
value.startsWith("/") ||
value.endsWith("/") ||
value.startsWith("^") ||
value.endsWith("$")
) {
return true;
}
return /\s/.test(value) || value.includes("group:");
}
function resolveToolPolicies(params: {
cfg: OpenClawConfig;
agentTools?: AgentToolsConfig;
@@ -471,6 +545,141 @@ export function collectHooksHardeningFindings(cfg: OpenClawConfig): SecurityAudi
return findings;
}
export function collectSandboxDockerNoopFindings(cfg: OpenClawConfig): SecurityAuditFinding[] {
const findings: SecurityAuditFinding[] = [];
const configuredPaths: string[] = [];
const agents = Array.isArray(cfg.agents?.list) ? cfg.agents.list : [];
const defaultsSandbox = cfg.agents?.defaults?.sandbox;
const hasDefaultDocker = hasConfiguredDockerConfig(
defaultsSandbox?.docker as Record<string, unknown> | undefined,
);
const defaultMode = defaultsSandbox?.mode ?? "off";
const hasAnySandboxEnabledAgent = agents.some((entry) => {
if (!entry || typeof entry !== "object" || typeof entry.id !== "string") {
return false;
}
return resolveSandboxConfigForAgent(cfg, entry.id).mode !== "off";
});
if (hasDefaultDocker && defaultMode === "off" && !hasAnySandboxEnabledAgent) {
configuredPaths.push("agents.defaults.sandbox.docker");
}
for (const entry of agents) {
if (!entry || typeof entry !== "object" || typeof entry.id !== "string") {
continue;
}
if (!hasConfiguredDockerConfig(entry.sandbox?.docker as Record<string, unknown> | undefined)) {
continue;
}
if (resolveSandboxConfigForAgent(cfg, entry.id).mode === "off") {
configuredPaths.push(`agents.list.${entry.id}.sandbox.docker`);
}
}
if (configuredPaths.length === 0) {
return findings;
}
findings.push({
checkId: "sandbox.docker_config_mode_off",
severity: "warn",
title: "Sandbox docker settings configured while sandbox mode is off",
detail:
"These docker settings will not take effect until sandbox mode is enabled:\n" +
configuredPaths.map((entry) => `- ${entry}`).join("\n"),
remediation:
'Enable sandbox mode (`agents.defaults.sandbox.mode="non-main"` or `"all"`) where needed, or remove unused docker settings.',
});
return findings;
}
export function collectNodeDenyCommandPatternFindings(cfg: OpenClawConfig): SecurityAuditFinding[] {
const findings: SecurityAuditFinding[] = [];
const denyListRaw = cfg.gateway?.nodes?.denyCommands;
if (!Array.isArray(denyListRaw) || denyListRaw.length === 0) {
return findings;
}
const denyList = denyListRaw.map(normalizeNodeCommand).filter(Boolean);
if (denyList.length === 0) {
return findings;
}
const knownCommands = listKnownNodeCommands(cfg);
const patternLike = denyList.filter((entry) => looksLikeNodeCommandPattern(entry));
const unknownExact = denyList.filter(
(entry) => !looksLikeNodeCommandPattern(entry) && !knownCommands.has(entry),
);
if (patternLike.length === 0 && unknownExact.length === 0) {
return findings;
}
const detailParts: string[] = [];
if (patternLike.length > 0) {
detailParts.push(
`Pattern-like entries (not supported by exact matching): ${patternLike.join(", ")}`,
);
}
if (unknownExact.length > 0) {
detailParts.push(
`Unknown command names (not in defaults/allowCommands): ${unknownExact.join(", ")}`,
);
}
const examples = Array.from(knownCommands).slice(0, 8);
findings.push({
checkId: "gateway.nodes.deny_commands_ineffective",
severity: "warn",
title: "Some gateway.nodes.denyCommands entries are ineffective",
detail:
"gateway.nodes.denyCommands uses exact command-name matching only.\n" +
detailParts.map((entry) => `- ${entry}`).join("\n"),
remediation:
`Use exact command names (for example: ${examples.join(", ")}). ` +
"If you need broader restrictions, remove risky commands from allowCommands/default workflows.",
});
return findings;
}
export function collectMinimalProfileOverrideFindings(cfg: OpenClawConfig): SecurityAuditFinding[] {
const findings: SecurityAuditFinding[] = [];
if (cfg.tools?.profile !== "minimal") {
return findings;
}
const overrides = (cfg.agents?.list ?? [])
.filter((entry): entry is { id: string; tools?: AgentToolsConfig } => {
return Boolean(
entry &&
typeof entry === "object" &&
typeof entry.id === "string" &&
entry.tools?.profile &&
entry.tools.profile !== "minimal",
);
})
.map((entry) => `${entry.id}=${entry.tools?.profile}`);
if (overrides.length === 0) {
return findings;
}
findings.push({
checkId: "tools.profile_minimal_overridden",
severity: "warn",
title: "Global tools.profile=minimal is overridden by agent profiles",
detail:
"Global minimal profile is set, but these agent profiles take precedence:\n" +
overrides.map((entry) => `- agents.list.${entry}`).join("\n"),
remediation:
'Set those agents to `tools.profile="minimal"` (or remove the agent override) if you want minimal tools enforced globally.',
});
return findings;
}
export function collectModelHygieneFindings(cfg: OpenClawConfig): SecurityAuditFinding[] {
const findings: SecurityAuditFinding[] = [];
const models = collectModels(cfg);

View File

@@ -12,7 +12,10 @@ export {
collectAttackSurfaceSummaryFindings,
collectExposureMatrixFindings,
collectHooksHardeningFindings,
collectMinimalProfileOverrideFindings,
collectModelHygieneFindings,
collectNodeDenyCommandPatternFindings,
collectSandboxDockerNoopFindings,
collectSecretsInConfigFindings,
collectSmallModelRiskFindings,
collectSyncedFolderFindings,

View File

@@ -303,6 +303,110 @@ describe("security audit", () => {
expect(finding?.detail).toContain("sandbox=all");
});
it("flags sandbox docker config when sandbox mode is off", async () => {
const cfg: OpenClawConfig = {
agents: {
defaults: {
sandbox: {
mode: "off",
docker: { image: "ghcr.io/example/sandbox:latest" },
},
},
},
};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: false,
includeChannelSecurity: false,
});
expect(res.findings).toEqual(
expect.arrayContaining([
expect.objectContaining({
checkId: "sandbox.docker_config_mode_off",
severity: "warn",
}),
]),
);
});
it("does not flag global sandbox docker config when an agent enables sandbox mode", async () => {
const cfg: OpenClawConfig = {
agents: {
defaults: {
sandbox: {
mode: "off",
docker: { image: "ghcr.io/example/sandbox:latest" },
},
},
list: [{ id: "ops", sandbox: { mode: "all" } }],
},
};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: false,
includeChannelSecurity: false,
});
expect(res.findings.some((f) => f.checkId === "sandbox.docker_config_mode_off")).toBe(false);
});
it("flags ineffective gateway.nodes.denyCommands entries", async () => {
const cfg: OpenClawConfig = {
gateway: {
nodes: {
denyCommands: ["system.*", "system.runx"],
},
},
};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: false,
includeChannelSecurity: false,
});
const finding = res.findings.find(
(f) => f.checkId === "gateway.nodes.deny_commands_ineffective",
);
expect(finding?.severity).toBe("warn");
expect(finding?.detail).toContain("system.*");
expect(finding?.detail).toContain("system.runx");
});
it("flags agent profile overrides when global tools.profile is minimal", async () => {
const cfg: OpenClawConfig = {
tools: {
profile: "minimal",
},
agents: {
list: [
{
id: "owner",
tools: { profile: "full" },
},
],
},
};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: false,
includeChannelSecurity: false,
});
expect(res.findings).toEqual(
expect.arrayContaining([
expect.objectContaining({
checkId: "tools.profile_minimal_overridden",
severity: "warn",
}),
]),
);
});
it("flags tools.elevated allowFrom wildcard as critical", async () => {
const cfg: OpenClawConfig = {
tools: {
@@ -1149,6 +1253,68 @@ describe("security audit", () => {
}
});
it("flags enabled extensions when tool policy can expose plugin tools", async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-security-audit-plugins-"));
const stateDir = path.join(tmp, "state");
await fs.mkdir(path.join(stateDir, "extensions", "some-plugin"), {
recursive: true,
mode: 0o700,
});
try {
const cfg: OpenClawConfig = {
plugins: { allow: ["some-plugin"] },
};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: true,
includeChannelSecurity: false,
stateDir,
configPath: path.join(stateDir, "openclaw.json"),
});
expect(res.findings).toEqual(
expect.arrayContaining([
expect.objectContaining({
checkId: "plugins.tools_reachable_permissive_policy",
severity: "warn",
}),
]),
);
} finally {
await fs.rm(tmp, { recursive: true, force: true });
}
});
it("does not flag plugin tool reachability when profile is restrictive", async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-security-audit-plugins-"));
const stateDir = path.join(tmp, "state");
await fs.mkdir(path.join(stateDir, "extensions", "some-plugin"), {
recursive: true,
mode: 0o700,
});
try {
const cfg: OpenClawConfig = {
plugins: { allow: ["some-plugin"] },
tools: { profile: "coding" },
};
const res = await runSecurityAudit({
config: cfg,
includeFilesystem: true,
includeChannelSecurity: false,
stateDir,
configPath: path.join(stateDir, "openclaw.json"),
});
expect(
res.findings.some((f) => f.checkId === "plugins.tools_reachable_permissive_policy"),
).toBe(false);
} finally {
await fs.rm(tmp, { recursive: true, force: true });
}
});
it("flags unallowlisted extensions as critical when native skill commands are exposed", async () => {
const prevDiscordToken = process.env.DISCORD_BOT_TOKEN;
delete process.env.DISCORD_BOT_TOKEN;

View File

@@ -18,8 +18,11 @@ import {
collectHooksHardeningFindings,
collectIncludeFilePermFindings,
collectInstalledSkillsCodeSafetyFindings,
collectMinimalProfileOverrideFindings,
collectModelHygieneFindings,
collectNodeDenyCommandPatternFindings,
collectSmallModelRiskFindings,
collectSandboxDockerNoopFindings,
collectPluginsTrustFindings,
collectSecretsInConfigFindings,
collectPluginsCodeSafetyFindings,
@@ -980,6 +983,9 @@ export async function runSecurityAudit(opts: SecurityAuditOptions): Promise<Secu
findings.push(...collectLoggingFindings(cfg));
findings.push(...collectElevatedFindings(cfg));
findings.push(...collectHooksHardeningFindings(cfg));
findings.push(...collectSandboxDockerNoopFindings(cfg));
findings.push(...collectNodeDenyCommandPatternFindings(cfg));
findings.push(...collectMinimalProfileOverrideFindings(cfg));
findings.push(...collectSecretsInConfigFindings(cfg));
findings.push(...collectModelHygieneFindings(cfg));
findings.push(...collectSmallModelRiskFindings({ cfg, env }));