fix(security): keep plain audit off plugin runtimes

Keep routine security audit on config/filesystem checks by default, reserving plugin runtime collectors for deep audit paths.\n\nThanks @vincentkoc
This commit is contained in:
Vincent Koc
2026-05-01 08:22:06 -07:00
committed by GitHub
parent bbc3384fda
commit f858b5de22
5 changed files with 32 additions and 4 deletions

View File

@@ -42,6 +42,7 @@ Docs: https://docs.openclaw.ai
- Discord/voice: lengthen the default voice join Ready wait, add configurable `voice.connectTimeoutMs`/`voice.reconnectGraceMs`, and warn before destroying unrecovered disconnected sessions so slow Discord voice handshakes and reconnects no longer fail silently. Fixes #63098; refs #39825 and #65039. Thanks @darealgege, @kzicherman, and @ayochim.
- Gateway/health: refresh cached health RPC snapshots when channel runtime state diverges, so Discord and other channel status reads no longer report stale running or connected values until the cache TTL expires. (#75423) Thanks @clawsweeper.
- Gateway/sessions: keep session-store reads from running stale prune and entry-count cap maintenance during startup, so oversized stores no longer block chat history readiness after updates while writes and `sessions cleanup --enforce` still preserve the cleanup safeguards. Fixes #70050. Thanks @tangda18.
- Security/audit: keep plain `security audit` on the cold config/filesystem path and reserve plugin runtime security collectors for `--deep`, so large plugin installs cannot execute every plugin runtime during routine audits. Thanks @vincentkoc.
- Discord/voice: merge configured media-understanding providers such as Deepgram into partial active provider registries, so follow-up voice turns keep transcribing after another media plugin is already active. Fixes #65687. Thanks @OneMintJulep.
- WhatsApp: stage `qrcode` through root mirrored runtime dependencies so packaged QR pairing can render from staged plugin-runtime-deps installs. Fixes #75394. Thanks @FelipeX2001.
- Discord/voice: apply per-channel Discord `systemPrompt` overrides to voice transcript turns by forwarding the trusted channel prompt through the voice agent run. Fixes #47095. Thanks @qearlyao.

View File

@@ -25,6 +25,8 @@ openclaw security audit --fix
openclaw security audit --json
```
Plain `security audit` stays on the cold config/filesystem/read-only path. It does not discover plugin runtime security collectors by default, so routine audits do not load every installed plugin runtime. Use `--deep` to include best-effort live Gateway probes and plugin-owned security audit collectors; explicit internal callers may also opt into those plugin-owned collectors when they already have an appropriate runtime scope.
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.
This is for cooperative/shared inbox hardening. A single Gateway shared by mutually untrusted/adversarial operators is not a recommended setup; split trust boundaries with separate gateways (or separate OS users/hosts).
It also emits `security.trust_model.multi_user_heuristic` when config suggests likely shared-user ingress (for example open DM/group policy, configured group targets, or wildcard sender rules), and reminds you that OpenClaw is a personal-assistant trust model by default.

View File

@@ -41,7 +41,10 @@ export function registerSecurityCli(program: Command) {
() =>
`\n${theme.heading("Examples:")}\n${formatHelpExamples([
["openclaw security audit", "Run a local security audit."],
["openclaw security audit --deep", "Include best-effort live Gateway probe checks."],
[
"openclaw security audit --deep",
"Include best-effort live Gateway probes and plugin-owned security audit collectors.",
],
["openclaw security audit --deep --token <token>", "Use explicit token for deep probe."],
[
"openclaw security audit --deep --password <password>",
@@ -55,7 +58,7 @@ export function registerSecurityCli(program: Command) {
security
.command("audit")
.description("Audit config + local state for common security foot-guns")
.option("--deep", "Attempt live Gateway probe (best-effort)", false)
.option("--deep", "Attempt live Gateway probes and plugin-owned collector checks", false)
.option("--token <token>", "Use explicit gateway token for deep probe auth")
.option("--password <password>", "Use explicit gateway password for deep probe auth")
.option("--fix", "Apply safe fixes (tighten defaults + chmod state/config)", false)

View File

@@ -23,7 +23,7 @@ vi.mock("../plugins/runtime/metadata-registry-loader.js", () => ({
loadPluginMetadataRegistrySnapshotMock(...args),
}));
const { collectPluginSecurityAuditFindings } = await import("./audit.js");
const { collectPluginSecurityAuditFindings, runSecurityAudit } = await import("./audit.js");
function createAuditContext(params: {
sourceConfig: Parameters<typeof collectPluginSecurityAuditFindings>[0]["sourceConfig"];
@@ -152,4 +152,26 @@ describe("security audit read-only plugin scope", () => {
expect(applyPluginAutoEnableMock).not.toHaveBeenCalled();
expect(loadPluginMetadataRegistrySnapshotMock).not.toHaveBeenCalled();
});
it("keeps plain security audit off plugin collector runtime discovery by default", async () => {
const sourceConfig = {
plugins: {
allow: ["audit-plugin"],
},
};
await runSecurityAudit({
config: sourceConfig,
sourceConfig,
env: {},
includeFilesystem: false,
includeChannelSecurity: false,
stateDir: "/tmp/openclaw-test-state",
configPath: "/tmp/openclaw-test-config.json",
});
expect(getActivePluginRegistryMock).not.toHaveBeenCalled();
expect(applyPluginAutoEnableMock).not.toHaveBeenCalled();
expect(loadPluginMetadataRegistrySnapshotMock).not.toHaveBeenCalled();
});
});

View File

@@ -946,7 +946,7 @@ async function createAuditExecutionContext(
execDockerRawFn: opts.execDockerRawFn,
probeGatewayFn: opts.probeGatewayFn,
plugins: opts.plugins,
loadPluginSecurityCollectors: opts.loadPluginSecurityCollectors !== false,
loadPluginSecurityCollectors: opts.loadPluginSecurityCollectors ?? deep,
workspaceDir,
configSnapshot,
codeSafetySummaryCache: opts.codeSafetySummaryCache ?? new Map<string, Promise<unknown>>(),