@poolzin/pool-bot 2026.2.23 → 2026.2.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/acp/client.js +207 -18
- package/dist/acp/secret-file.js +22 -0
- package/dist/agents/agent-scope.js +10 -0
- package/dist/agents/bash-process-registry.test-helpers.js +29 -0
- package/dist/agents/bash-tools.exec-approval-request.js +20 -0
- package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
- package/dist/agents/bash-tools.exec-host-node.js +235 -0
- package/dist/agents/bash-tools.exec-types.js +1 -0
- package/dist/agents/bash-tools.process.js +224 -218
- package/dist/agents/content-blocks.js +16 -0
- package/dist/agents/model-fallback.js +96 -101
- package/dist/agents/models-config.providers.js +299 -182
- package/dist/agents/pi-embedded-payloads.js +1 -0
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
- package/dist/agents/skills.test-helpers.js +13 -0
- package/dist/agents/stable-stringify.js +12 -0
- package/dist/agents/subagent-registry.mocks.shared.js +12 -0
- package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
- package/dist/agents/tool-policy-shared.js +108 -0
- package/dist/agents/tools/browser-tool.js +160 -54
- package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
- package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
- package/dist/agents/tools/image-tool.js +214 -99
- package/dist/agents/tools/sessions-history-tool.js +140 -108
- package/dist/agents/workspace.js +222 -46
- package/dist/auto-reply/commands-registry.js +15 -18
- package/dist/auto-reply/fallback-state.js +114 -0
- package/dist/auto-reply/model-runtime.js +68 -0
- package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
- package/dist/auto-reply/reply/agent-runner.js +165 -39
- package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/browser/config.js +26 -0
- package/dist/browser/navigation-guard.js +31 -0
- package/dist/browser/routes/agent.act.js +431 -424
- package/dist/browser/routes/agent.shared.js +47 -3
- package/dist/browser/routes/agent.snapshot.js +122 -116
- package/dist/browser/routes/agent.storage.js +303 -297
- package/dist/browser/routes/tabs.js +154 -100
- package/dist/browser/server-lifecycle.js +37 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/allow-from.js +25 -0
- package/dist/channels/plugins/account-action-gate.js +13 -0
- package/dist/channels/plugins/message-actions.js +10 -0
- package/dist/channels/telegram/api.js +18 -0
- package/dist/cli/argv.js +84 -21
- package/dist/cli/banner.js +2 -1
- package/dist/cli/exec-approvals-cli.js +92 -124
- package/dist/cli/memory-cli.js +158 -61
- package/dist/cli/nodes-cli/register.push.js +63 -0
- package/dist/cli/nodes-media-utils.js +21 -0
- package/dist/cli/plugins-cli.js +245 -61
- package/dist/cli/program/build-program.js +3 -1
- package/dist/cli/program/command-registry.js +223 -136
- package/dist/cli/program/help.js +43 -12
- package/dist/cli/route.js +1 -1
- package/dist/cli/test-runtime-capture.js +24 -0
- package/dist/commands/agent.js +163 -87
- package/dist/commands/channels.mock-harness.js +23 -0
- package/dist/commands/daemon-install-runtime-warning.js +11 -0
- package/dist/commands/onboard-helpers.js +4 -4
- package/dist/commands/sessions.test-helpers.js +61 -0
- package/dist/compat/legacy-names.js +2 -2
- package/dist/config/commands.js +3 -0
- package/dist/config/config.js +1 -1
- package/dist/config/env-substitution.js +62 -34
- package/dist/config/env-vars.js +9 -0
- package/dist/config/io.js +571 -171
- package/dist/config/merge-patch.js +50 -4
- package/dist/config/redact-snapshot.js +404 -76
- package/dist/config/schema.js +58 -570
- package/dist/config/validation.js +140 -85
- package/dist/config/zod-schema.hooks.js +40 -11
- package/dist/config/zod-schema.installs.js +20 -0
- package/dist/config/zod-schema.js +8 -7
- package/dist/control-ui/assets/{index-HRr1grwl.js → index-Dvkl4Xlx.js} +2 -1
- package/dist/control-ui/assets/{index-HRr1grwl.js.map → index-Dvkl4Xlx.js.map} +1 -1
- package/dist/control-ui/index.html +1 -1
- package/dist/daemon/cmd-argv.js +21 -0
- package/dist/daemon/cmd-set.js +58 -0
- package/dist/daemon/service-types.js +1 -0
- package/dist/discord/monitor/exec-approvals.js +357 -162
- package/dist/gateway/auth.js +38 -3
- package/dist/gateway/call.js +149 -68
- package/dist/gateway/canvas-capability.js +75 -0
- package/dist/gateway/control-plane-audit.js +28 -0
- package/dist/gateway/control-plane-rate-limit.js +53 -0
- package/dist/gateway/events.js +1 -0
- package/dist/gateway/hooks.js +109 -54
- package/dist/gateway/http-common.js +22 -0
- package/dist/gateway/method-scopes.js +169 -0
- package/dist/gateway/net.js +23 -0
- package/dist/gateway/openresponses-http.js +120 -110
- package/dist/gateway/probe-auth.js +2 -0
- package/dist/gateway/protocol/index.js +3 -2
- package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
- package/dist/gateway/protocol/schema/push.js +18 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-http.js +236 -52
- package/dist/gateway/server-methods/agent.js +162 -24
- package/dist/gateway/server-methods/chat.js +461 -130
- package/dist/gateway/server-methods/config.js +193 -150
- package/dist/gateway/server-methods/nodes.helpers.js +12 -0
- package/dist/gateway/server-methods/nodes.js +251 -69
- package/dist/gateway/server-methods/push.js +53 -0
- package/dist/gateway/server-reload-handlers.js +2 -3
- package/dist/gateway/server-runtime-config.js +5 -0
- package/dist/gateway/server-runtime-state.js +2 -0
- package/dist/gateway/server-ws-runtime.js +1 -0
- package/dist/gateway/server.impl.js +296 -139
- package/dist/gateway/session-preview.test-helpers.js +11 -0
- package/dist/gateway/startup-auth.js +126 -0
- package/dist/gateway/test-helpers.agent-results.js +15 -0
- package/dist/gateway/test-helpers.mocks.js +37 -14
- package/dist/gateway/test-helpers.server.js +161 -77
- package/dist/hooks/bundled/session-memory/handler.js +165 -34
- package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
- package/dist/infra/archive-path.js +49 -0
- package/dist/infra/device-pairing.js +148 -167
- package/dist/infra/exec-approvals-allowlist.js +19 -70
- package/dist/infra/exec-approvals-analysis.js +44 -17
- package/dist/infra/exec-safe-bin-policy.js +269 -0
- package/dist/infra/fixed-window-rate-limit.js +33 -0
- package/dist/infra/git-root.js +61 -0
- package/dist/infra/heartbeat-active-hours.js +2 -2
- package/dist/infra/heartbeat-reason.js +40 -0
- package/dist/infra/heartbeat-runner.js +72 -32
- package/dist/infra/install-source-utils.js +91 -7
- package/dist/infra/node-pairing.js +50 -105
- package/dist/infra/npm-integrity.js +45 -0
- package/dist/infra/npm-pack-install.js +40 -0
- package/dist/infra/outbound/channel-adapters.js +20 -7
- package/dist/infra/outbound/message-action-runner.js +107 -327
- package/dist/infra/outbound/message.js +59 -36
- package/dist/infra/outbound/outbound-policy.js +52 -25
- package/dist/infra/outbound/outbound-send-service.js +58 -71
- package/dist/infra/pairing-files.js +10 -0
- package/dist/infra/plain-object.js +9 -0
- package/dist/infra/push-apns.js +365 -0
- package/dist/infra/restart-sentinel.js +16 -1
- package/dist/infra/restart.js +229 -26
- package/dist/infra/scp-host.js +54 -0
- package/dist/infra/update-startup.js +86 -9
- package/dist/media/inbound-path-policy.js +114 -0
- package/dist/media/input-files.js +16 -0
- package/dist/memory/test-manager.js +8 -0
- package/dist/plugin-sdk/temp-path.js +47 -0
- package/dist/plugins/discovery.js +217 -23
- package/dist/plugins/hook-runner-global.js +16 -0
- package/dist/plugins/loader.js +192 -26
- package/dist/plugins/logger.js +8 -0
- package/dist/plugins/manifest-registry.js +3 -0
- package/dist/plugins/path-safety.js +34 -0
- package/dist/plugins/registry.js +5 -2
- package/dist/plugins/runtime/index.js +271 -206
- package/dist/providers/github-copilot-models.js +4 -1
- package/dist/security/audit-channel.js +8 -19
- package/dist/security/audit-extra.async.js +354 -182
- package/dist/security/audit-extra.js +11 -1
- package/dist/security/audit-extra.sync.js +340 -33
- package/dist/security/audit-fs.js +31 -13
- package/dist/security/audit.js +145 -371
- package/dist/security/dm-policy-shared.js +24 -0
- package/dist/security/external-content.js +20 -8
- package/dist/security/fix.js +49 -85
- package/dist/security/scan-paths.js +20 -0
- package/dist/security/secret-equal.js +3 -7
- package/dist/security/windows-acl.js +30 -15
- package/dist/shared/node-list-parse.js +13 -0
- package/dist/shared/operator-scope-compat.js +37 -0
- package/dist/shared/text-chunking.js +29 -0
- package/dist/slack/blocks.test-helpers.js +31 -0
- package/dist/slack/monitor/mrkdwn.js +8 -0
- package/dist/telegram/bot-message-dispatch.js +366 -164
- package/dist/telegram/draft-stream.js +30 -7
- package/dist/telegram/reasoning-lane-coordinator.js +128 -0
- package/dist/terminal/prompt-select-styled.js +9 -0
- package/dist/test-utils/command-runner.js +6 -0
- package/dist/test-utils/internal-hook-event-payload.js +10 -0
- package/dist/test-utils/model-auth-mock.js +12 -0
- package/dist/test-utils/provider-usage-fetch.js +14 -0
- package/dist/test-utils/temp-home.js +33 -0
- package/dist/tui/components/chat-log.js +9 -0
- package/dist/tui/tui-command-handlers.js +36 -27
- package/dist/tui/tui-event-handlers.js +122 -32
- package/dist/tui/tui.js +181 -45
- package/dist/utils/mask-api-key.js +10 -0
- package/dist/utils/run-with-concurrency.js +39 -0
- package/dist/web/media.js +4 -0
- package/docs/tools/slash-commands.md +5 -1
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/feishu/src/external-keys.ts +19 -0
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/lobster/src/windows-spawn.ts +193 -0
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
|
@@ -3,19 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These functions perform I/O (filesystem, config reads) to detect security issues.
|
|
5
5
|
*/
|
|
6
|
-
import JSON5 from "json5";
|
|
7
6
|
import fs from "node:fs/promises";
|
|
8
7
|
import path from "node:path";
|
|
9
|
-
import {
|
|
8
|
+
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
9
|
+
import { isToolAllowedByPolicies } from "../agents/pi-tools.policy.js";
|
|
10
|
+
import { resolveSandboxConfigForAgent, resolveSandboxToolPolicyForAgent, } from "../agents/sandbox.js";
|
|
10
11
|
import { loadWorkspaceSkillEntries } from "../agents/skills.js";
|
|
11
|
-
import {
|
|
12
|
+
import { resolveToolProfilePolicy } from "../agents/tool-policy.js";
|
|
13
|
+
import { listAgentWorkspaceDirs } from "../agents/workspace-dirs.js";
|
|
14
|
+
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
|
12
15
|
import { resolveNativeSkillsEnabled } from "../config/commands.js";
|
|
13
16
|
import { createConfigIO } from "../config/config.js";
|
|
14
|
-
import {
|
|
17
|
+
import { collectIncludePathsRecursive } from "../config/includes-scan.js";
|
|
15
18
|
import { resolveOAuthDir } from "../config/paths.js";
|
|
19
|
+
import { normalizePluginsConfig } from "../plugins/config-state.js";
|
|
16
20
|
import { normalizeAgentId } from "../routing/session-key.js";
|
|
17
21
|
import { formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, safeStat, } from "./audit-fs.js";
|
|
18
|
-
import {
|
|
22
|
+
import { pickSandboxToolPolicy } from "./audit-tool-policy.js";
|
|
23
|
+
import { extensionUsesSkippedScannerPath, isPathInside } from "./scan-paths.js";
|
|
24
|
+
import * as skillScanner from "./skill-scanner.js";
|
|
19
25
|
// --------------------------------------------------------------------------
|
|
20
26
|
// Helpers
|
|
21
27
|
// --------------------------------------------------------------------------
|
|
@@ -35,91 +41,6 @@ function expandTilde(p, env) {
|
|
|
35
41
|
}
|
|
36
42
|
return null;
|
|
37
43
|
}
|
|
38
|
-
function resolveIncludePath(baseConfigPath, includePath) {
|
|
39
|
-
return path.normalize(path.isAbsolute(includePath)
|
|
40
|
-
? includePath
|
|
41
|
-
: path.resolve(path.dirname(baseConfigPath), includePath));
|
|
42
|
-
}
|
|
43
|
-
function listDirectIncludes(parsed) {
|
|
44
|
-
const out = [];
|
|
45
|
-
const visit = (value) => {
|
|
46
|
-
if (!value) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (Array.isArray(value)) {
|
|
50
|
-
for (const item of value) {
|
|
51
|
-
visit(item);
|
|
52
|
-
}
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (typeof value !== "object") {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
const rec = value;
|
|
59
|
-
const includeVal = rec[INCLUDE_KEY];
|
|
60
|
-
if (typeof includeVal === "string") {
|
|
61
|
-
out.push(includeVal);
|
|
62
|
-
}
|
|
63
|
-
else if (Array.isArray(includeVal)) {
|
|
64
|
-
for (const item of includeVal) {
|
|
65
|
-
if (typeof item === "string") {
|
|
66
|
-
out.push(item);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
for (const v of Object.values(rec)) {
|
|
71
|
-
visit(v);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
visit(parsed);
|
|
75
|
-
return out;
|
|
76
|
-
}
|
|
77
|
-
async function collectIncludePathsRecursive(params) {
|
|
78
|
-
const visited = new Set();
|
|
79
|
-
const result = [];
|
|
80
|
-
const walk = async (basePath, parsed, depth) => {
|
|
81
|
-
if (depth > MAX_INCLUDE_DEPTH) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
for (const raw of listDirectIncludes(parsed)) {
|
|
85
|
-
const resolved = resolveIncludePath(basePath, raw);
|
|
86
|
-
if (visited.has(resolved)) {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
visited.add(resolved);
|
|
90
|
-
result.push(resolved);
|
|
91
|
-
const rawText = await fs.readFile(resolved, "utf-8").catch(() => null);
|
|
92
|
-
if (!rawText) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
const nestedParsed = (() => {
|
|
96
|
-
try {
|
|
97
|
-
return JSON5.parse(rawText);
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
})();
|
|
103
|
-
if (nestedParsed) {
|
|
104
|
-
// eslint-disable-next-line no-await-in-loop
|
|
105
|
-
await walk(resolved, nestedParsed, depth + 1);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
await walk(params.configPath, params.parsed, 0);
|
|
110
|
-
return result;
|
|
111
|
-
}
|
|
112
|
-
function isPathInside(basePath, candidatePath) {
|
|
113
|
-
const base = path.resolve(basePath);
|
|
114
|
-
const candidate = path.resolve(candidatePath);
|
|
115
|
-
const rel = path.relative(base, candidate);
|
|
116
|
-
return rel === "" || (!rel.startsWith(`..${path.sep}`) && rel !== ".." && !path.isAbsolute(rel));
|
|
117
|
-
}
|
|
118
|
-
function extensionUsesSkippedScannerPath(entry) {
|
|
119
|
-
const segments = entry.split(/[\\/]+/).filter(Boolean);
|
|
120
|
-
return segments.some((segment) => segment === "node_modules" ||
|
|
121
|
-
(segment.startsWith(".") && segment !== "." && segment !== ".."));
|
|
122
|
-
}
|
|
123
44
|
async function readPluginManifestExtensions(pluginPath) {
|
|
124
45
|
const manifestPath = path.join(pluginPath, "package.json");
|
|
125
46
|
const raw = await fs.readFile(manifestPath, "utf-8").catch(() => "");
|
|
@@ -127,25 +48,12 @@ async function readPluginManifestExtensions(pluginPath) {
|
|
|
127
48
|
return [];
|
|
128
49
|
}
|
|
129
50
|
const parsed = JSON.parse(raw);
|
|
130
|
-
const extensions = parsed?.[
|
|
51
|
+
const extensions = parsed?.[MANIFEST_KEY]?.extensions;
|
|
131
52
|
if (!Array.isArray(extensions)) {
|
|
132
53
|
return [];
|
|
133
54
|
}
|
|
134
55
|
return extensions.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
|
135
56
|
}
|
|
136
|
-
function listWorkspaceDirs(cfg) {
|
|
137
|
-
const dirs = new Set();
|
|
138
|
-
const list = cfg.agents?.list;
|
|
139
|
-
if (Array.isArray(list)) {
|
|
140
|
-
for (const entry of list) {
|
|
141
|
-
if (entry && typeof entry === "object" && typeof entry.id === "string") {
|
|
142
|
-
dirs.add(resolveAgentWorkspaceDir(cfg, entry.id));
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
dirs.add(resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)));
|
|
147
|
-
return [...dirs];
|
|
148
|
-
}
|
|
149
57
|
function formatCodeSafetyDetails(findings, rootDir) {
|
|
150
58
|
return findings
|
|
151
59
|
.map((finding) => {
|
|
@@ -158,74 +66,340 @@ function formatCodeSafetyDetails(findings, rootDir) {
|
|
|
158
66
|
})
|
|
159
67
|
.join("\n");
|
|
160
68
|
}
|
|
161
|
-
|
|
162
|
-
// Exported collectors
|
|
163
|
-
// --------------------------------------------------------------------------
|
|
164
|
-
export async function collectPluginsTrustFindings(params) {
|
|
165
|
-
const findings = [];
|
|
69
|
+
async function listInstalledPluginDirs(params) {
|
|
166
70
|
const extensionsDir = path.join(params.stateDir, "extensions");
|
|
167
71
|
const st = await safeStat(extensionsDir);
|
|
168
72
|
if (!st.ok || !st.isDir) {
|
|
169
|
-
return
|
|
73
|
+
return { extensionsDir, pluginDirs: [] };
|
|
170
74
|
}
|
|
171
|
-
const entries = await fs.readdir(extensionsDir, { withFileTypes: true }).catch(() =>
|
|
75
|
+
const entries = await fs.readdir(extensionsDir, { withFileTypes: true }).catch((err) => {
|
|
76
|
+
params.onReadError?.(err);
|
|
77
|
+
return [];
|
|
78
|
+
});
|
|
172
79
|
const pluginDirs = entries
|
|
173
|
-
.filter((
|
|
174
|
-
.map((
|
|
80
|
+
.filter((entry) => entry.isDirectory())
|
|
81
|
+
.map((entry) => entry.name)
|
|
175
82
|
.filter(Boolean);
|
|
176
|
-
|
|
177
|
-
|
|
83
|
+
return { extensionsDir, pluginDirs };
|
|
84
|
+
}
|
|
85
|
+
function resolveToolPolicies(params) {
|
|
86
|
+
const profile = params.agentTools?.profile ?? params.cfg.tools?.profile;
|
|
87
|
+
const profilePolicy = resolveToolProfilePolicy(profile);
|
|
88
|
+
const policies = [
|
|
89
|
+
profilePolicy,
|
|
90
|
+
pickSandboxToolPolicy(params.cfg.tools ?? undefined),
|
|
91
|
+
pickSandboxToolPolicy(params.agentTools),
|
|
92
|
+
];
|
|
93
|
+
if (params.sandboxMode === "all") {
|
|
94
|
+
policies.push(resolveSandboxToolPolicyForAgent(params.cfg, params.agentId ?? undefined));
|
|
95
|
+
}
|
|
96
|
+
return policies;
|
|
97
|
+
}
|
|
98
|
+
function normalizePluginIdSet(entries) {
|
|
99
|
+
return new Set(entries.map((entry) => entry.trim().toLowerCase()).filter(Boolean));
|
|
100
|
+
}
|
|
101
|
+
function resolveEnabledExtensionPluginIds(params) {
|
|
102
|
+
const normalized = normalizePluginsConfig(params.cfg.plugins);
|
|
103
|
+
if (!normalized.enabled) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
const allowSet = normalizePluginIdSet(normalized.allow);
|
|
107
|
+
const denySet = normalizePluginIdSet(normalized.deny);
|
|
108
|
+
const entryById = new Map();
|
|
109
|
+
for (const [id, entry] of Object.entries(normalized.entries)) {
|
|
110
|
+
entryById.set(id.trim().toLowerCase(), entry);
|
|
111
|
+
}
|
|
112
|
+
const enabled = [];
|
|
113
|
+
for (const id of params.pluginDirs) {
|
|
114
|
+
const normalizedId = id.trim().toLowerCase();
|
|
115
|
+
if (!normalizedId) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (denySet.has(normalizedId)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (allowSet.size > 0 && !allowSet.has(normalizedId)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (entryById.get(normalizedId)?.enabled === false) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
enabled.push(normalizedId);
|
|
178
128
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
129
|
+
return enabled;
|
|
130
|
+
}
|
|
131
|
+
function collectAllowEntries(config) {
|
|
132
|
+
const out = [];
|
|
133
|
+
if (Array.isArray(config?.allow)) {
|
|
134
|
+
out.push(...config.allow);
|
|
135
|
+
}
|
|
136
|
+
if (Array.isArray(config?.alsoAllow)) {
|
|
137
|
+
out.push(...config.alsoAllow);
|
|
138
|
+
}
|
|
139
|
+
return out.map((entry) => entry.trim().toLowerCase()).filter(Boolean);
|
|
140
|
+
}
|
|
141
|
+
function hasExplicitPluginAllow(params) {
|
|
142
|
+
return params.allowEntries.some((entry) => entry === "group:plugins" || params.enabledPluginIds.has(entry));
|
|
143
|
+
}
|
|
144
|
+
function hasProviderPluginAllow(params) {
|
|
145
|
+
if (!params.byProvider) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
for (const policy of Object.values(params.byProvider)) {
|
|
149
|
+
if (hasExplicitPluginAllow({
|
|
150
|
+
allowEntries: collectAllowEntries(policy),
|
|
151
|
+
enabledPluginIds: params.enabledPluginIds,
|
|
152
|
+
})) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
function isPinnedRegistrySpec(spec) {
|
|
159
|
+
const value = spec.trim();
|
|
160
|
+
if (!value) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const at = value.lastIndexOf("@");
|
|
164
|
+
if (at <= 0 || at >= value.length - 1) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
const version = value.slice(at + 1).trim();
|
|
168
|
+
return /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version);
|
|
169
|
+
}
|
|
170
|
+
async function readInstalledPackageVersion(dir) {
|
|
171
|
+
try {
|
|
172
|
+
const raw = await fs.readFile(path.join(dir, "package.json"), "utf-8");
|
|
173
|
+
const parsed = JSON.parse(raw);
|
|
174
|
+
return typeof parsed.version === "string" ? parsed.version : undefined;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// --------------------------------------------------------------------------
|
|
181
|
+
// Exported collectors
|
|
182
|
+
// --------------------------------------------------------------------------
|
|
183
|
+
export async function collectPluginsTrustFindings(params) {
|
|
184
|
+
const findings = [];
|
|
185
|
+
const { extensionsDir, pluginDirs } = await listInstalledPluginDirs({
|
|
186
|
+
stateDir: params.stateDir,
|
|
187
|
+
});
|
|
188
|
+
if (pluginDirs.length > 0) {
|
|
189
|
+
const allow = params.cfg.plugins?.allow;
|
|
190
|
+
const allowConfigured = Array.isArray(allow) && allow.length > 0;
|
|
191
|
+
if (!allowConfigured) {
|
|
192
|
+
const hasString = (value) => typeof value === "string" && value.trim().length > 0;
|
|
193
|
+
const hasAccountStringKey = (account, key) => Boolean(account &&
|
|
194
|
+
typeof account === "object" &&
|
|
195
|
+
hasString(account[key]));
|
|
196
|
+
const discordConfigured = hasString(params.cfg.channels?.discord?.token) ||
|
|
197
|
+
Boolean(params.cfg.channels?.discord?.accounts &&
|
|
198
|
+
Object.values(params.cfg.channels.discord.accounts).some((a) => hasAccountStringKey(a, "token"))) ||
|
|
199
|
+
hasString(process.env.DISCORD_BOT_TOKEN);
|
|
200
|
+
const telegramConfigured = hasString(params.cfg.channels?.telegram?.botToken) ||
|
|
201
|
+
hasString(params.cfg.channels?.telegram?.tokenFile) ||
|
|
202
|
+
Boolean(params.cfg.channels?.telegram?.accounts &&
|
|
203
|
+
Object.values(params.cfg.channels.telegram.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "tokenFile"))) ||
|
|
204
|
+
hasString(process.env.TELEGRAM_BOT_TOKEN);
|
|
205
|
+
const slackConfigured = hasString(params.cfg.channels?.slack?.botToken) ||
|
|
206
|
+
hasString(params.cfg.channels?.slack?.appToken) ||
|
|
207
|
+
Boolean(params.cfg.channels?.slack?.accounts &&
|
|
208
|
+
Object.values(params.cfg.channels.slack.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "appToken"))) ||
|
|
209
|
+
hasString(process.env.SLACK_BOT_TOKEN) ||
|
|
210
|
+
hasString(process.env.SLACK_APP_TOKEN);
|
|
211
|
+
const skillCommandsLikelyExposed = (discordConfigured &&
|
|
208
212
|
resolveNativeSkillsEnabled({
|
|
209
|
-
providerId: "
|
|
210
|
-
providerSetting: params.cfg.channels?.
|
|
213
|
+
providerId: "discord",
|
|
214
|
+
providerSetting: params.cfg.channels?.discord?.commands?.nativeSkills,
|
|
211
215
|
globalSetting: params.cfg.commands?.nativeSkills,
|
|
212
216
|
})) ||
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
217
|
+
(telegramConfigured &&
|
|
218
|
+
resolveNativeSkillsEnabled({
|
|
219
|
+
providerId: "telegram",
|
|
220
|
+
providerSetting: params.cfg.channels?.telegram?.commands?.nativeSkills,
|
|
221
|
+
globalSetting: params.cfg.commands?.nativeSkills,
|
|
222
|
+
})) ||
|
|
223
|
+
(slackConfigured &&
|
|
224
|
+
resolveNativeSkillsEnabled({
|
|
225
|
+
providerId: "slack",
|
|
226
|
+
providerSetting: params.cfg.channels?.slack?.commands?.nativeSkills,
|
|
227
|
+
globalSetting: params.cfg.commands?.nativeSkills,
|
|
228
|
+
}));
|
|
229
|
+
findings.push({
|
|
230
|
+
checkId: "plugins.extensions_no_allowlist",
|
|
231
|
+
severity: skillCommandsLikelyExposed ? "critical" : "warn",
|
|
232
|
+
title: "Extensions exist but plugins.allow is not set",
|
|
233
|
+
detail: `Found ${pluginDirs.length} extension(s) under ${extensionsDir}. Without plugins.allow, any discovered plugin id may load (depending on config and plugin behavior).` +
|
|
234
|
+
(skillCommandsLikelyExposed
|
|
235
|
+
? "\nNative skill commands are enabled on at least one configured chat surface; treat unpinned/unallowlisted extensions as high risk."
|
|
236
|
+
: ""),
|
|
237
|
+
remediation: "Set plugins.allow to an explicit list of plugin ids you trust.",
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
const enabledExtensionPluginIds = resolveEnabledExtensionPluginIds({
|
|
241
|
+
cfg: params.cfg,
|
|
242
|
+
pluginDirs,
|
|
228
243
|
});
|
|
244
|
+
if (enabledExtensionPluginIds.length > 0) {
|
|
245
|
+
const enabledPluginSet = new Set(enabledExtensionPluginIds);
|
|
246
|
+
const contexts = [{ label: "default" }];
|
|
247
|
+
for (const entry of params.cfg.agents?.list ?? []) {
|
|
248
|
+
if (!entry || typeof entry !== "object" || typeof entry.id !== "string") {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
contexts.push({
|
|
252
|
+
label: `agents.list.${entry.id}`,
|
|
253
|
+
agentId: entry.id,
|
|
254
|
+
tools: entry.tools,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
const permissiveContexts = [];
|
|
258
|
+
for (const context of contexts) {
|
|
259
|
+
const profile = context.tools?.profile ?? params.cfg.tools?.profile;
|
|
260
|
+
const restrictiveProfile = Boolean(resolveToolProfilePolicy(profile));
|
|
261
|
+
const sandboxMode = resolveSandboxConfigForAgent(params.cfg, context.agentId).mode;
|
|
262
|
+
const policies = resolveToolPolicies({
|
|
263
|
+
cfg: params.cfg,
|
|
264
|
+
agentTools: context.tools,
|
|
265
|
+
sandboxMode,
|
|
266
|
+
agentId: context.agentId,
|
|
267
|
+
});
|
|
268
|
+
const broadPolicy = isToolAllowedByPolicies("__openclaw_plugin_probe__", policies);
|
|
269
|
+
const explicitPluginAllow = !restrictiveProfile &&
|
|
270
|
+
(hasExplicitPluginAllow({
|
|
271
|
+
allowEntries: collectAllowEntries(params.cfg.tools),
|
|
272
|
+
enabledPluginIds: enabledPluginSet,
|
|
273
|
+
}) ||
|
|
274
|
+
hasProviderPluginAllow({
|
|
275
|
+
byProvider: params.cfg.tools?.byProvider,
|
|
276
|
+
enabledPluginIds: enabledPluginSet,
|
|
277
|
+
}) ||
|
|
278
|
+
hasExplicitPluginAllow({
|
|
279
|
+
allowEntries: collectAllowEntries(context.tools),
|
|
280
|
+
enabledPluginIds: enabledPluginSet,
|
|
281
|
+
}) ||
|
|
282
|
+
hasProviderPluginAllow({
|
|
283
|
+
byProvider: context.tools?.byProvider,
|
|
284
|
+
enabledPluginIds: enabledPluginSet,
|
|
285
|
+
}));
|
|
286
|
+
if (broadPolicy || explicitPluginAllow) {
|
|
287
|
+
permissiveContexts.push(context.label);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (permissiveContexts.length > 0) {
|
|
291
|
+
findings.push({
|
|
292
|
+
checkId: "plugins.tools_reachable_permissive_policy",
|
|
293
|
+
severity: "warn",
|
|
294
|
+
title: "Extension plugin tools may be reachable under permissive tool policy",
|
|
295
|
+
detail: `Enabled extension plugins: ${enabledExtensionPluginIds.join(", ")}.\n` +
|
|
296
|
+
`Permissive tool policy contexts:\n${permissiveContexts.map((entry) => `- ${entry}`).join("\n")}`,
|
|
297
|
+
remediation: "Use restrictive profiles (`minimal`/`coding`) or explicit tool allowlists that exclude plugin tools for agents handling untrusted input.",
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const pluginInstalls = params.cfg.plugins?.installs ?? {};
|
|
303
|
+
const npmPluginInstalls = Object.entries(pluginInstalls).filter(([, record]) => record?.source === "npm");
|
|
304
|
+
if (npmPluginInstalls.length > 0) {
|
|
305
|
+
const unpinned = npmPluginInstalls
|
|
306
|
+
.filter(([, record]) => typeof record.spec === "string" && !isPinnedRegistrySpec(record.spec))
|
|
307
|
+
.map(([pluginId, record]) => `${pluginId} (${record.spec})`);
|
|
308
|
+
if (unpinned.length > 0) {
|
|
309
|
+
findings.push({
|
|
310
|
+
checkId: "plugins.installs_unpinned_npm_specs",
|
|
311
|
+
severity: "warn",
|
|
312
|
+
title: "Plugin installs include unpinned npm specs",
|
|
313
|
+
detail: `Unpinned plugin install records:\n${unpinned.map((entry) => `- ${entry}`).join("\n")}`,
|
|
314
|
+
remediation: "Pin install specs to exact versions (for example, `@scope/pkg@1.2.3`) for higher supply-chain stability.",
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const missingIntegrity = npmPluginInstalls
|
|
318
|
+
.filter(([, record]) => typeof record.integrity !== "string" || record.integrity.trim() === "")
|
|
319
|
+
.map(([pluginId]) => pluginId);
|
|
320
|
+
if (missingIntegrity.length > 0) {
|
|
321
|
+
findings.push({
|
|
322
|
+
checkId: "plugins.installs_missing_integrity",
|
|
323
|
+
severity: "warn",
|
|
324
|
+
title: "Plugin installs are missing integrity metadata",
|
|
325
|
+
detail: `Plugin install records missing integrity:\n${missingIntegrity.map((entry) => `- ${entry}`).join("\n")}`,
|
|
326
|
+
remediation: "Reinstall or update plugins to refresh install metadata with resolved integrity hashes.",
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
const pluginVersionDrift = [];
|
|
330
|
+
for (const [pluginId, record] of npmPluginInstalls) {
|
|
331
|
+
const recordedVersion = record.resolvedVersion ?? record.version;
|
|
332
|
+
if (!recordedVersion) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const installPath = record.installPath ?? path.join(params.stateDir, "extensions", pluginId);
|
|
336
|
+
// eslint-disable-next-line no-await-in-loop
|
|
337
|
+
const installedVersion = await readInstalledPackageVersion(installPath);
|
|
338
|
+
if (!installedVersion || installedVersion === recordedVersion) {
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
pluginVersionDrift.push(`${pluginId} (recorded ${recordedVersion}, installed ${installedVersion})`);
|
|
342
|
+
}
|
|
343
|
+
if (pluginVersionDrift.length > 0) {
|
|
344
|
+
findings.push({
|
|
345
|
+
checkId: "plugins.installs_version_drift",
|
|
346
|
+
severity: "warn",
|
|
347
|
+
title: "Plugin install records drift from installed package versions",
|
|
348
|
+
detail: `Detected plugin install metadata drift:\n${pluginVersionDrift.map((entry) => `- ${entry}`).join("\n")}`,
|
|
349
|
+
remediation: "Run `openclaw plugins update --all` (or reinstall affected plugins) to refresh install metadata.",
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const hookInstalls = params.cfg.hooks?.internal?.installs ?? {};
|
|
354
|
+
const npmHookInstalls = Object.entries(hookInstalls).filter(([, record]) => record?.source === "npm");
|
|
355
|
+
if (npmHookInstalls.length > 0) {
|
|
356
|
+
const unpinned = npmHookInstalls
|
|
357
|
+
.filter(([, record]) => typeof record.spec === "string" && !isPinnedRegistrySpec(record.spec))
|
|
358
|
+
.map(([hookId, record]) => `${hookId} (${record.spec})`);
|
|
359
|
+
if (unpinned.length > 0) {
|
|
360
|
+
findings.push({
|
|
361
|
+
checkId: "hooks.installs_unpinned_npm_specs",
|
|
362
|
+
severity: "warn",
|
|
363
|
+
title: "Hook installs include unpinned npm specs",
|
|
364
|
+
detail: `Unpinned hook install records:\n${unpinned.map((entry) => `- ${entry}`).join("\n")}`,
|
|
365
|
+
remediation: "Pin hook install specs to exact versions (for example, `@scope/pkg@1.2.3`) for higher supply-chain stability.",
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
const missingIntegrity = npmHookInstalls
|
|
369
|
+
.filter(([, record]) => typeof record.integrity !== "string" || record.integrity.trim() === "")
|
|
370
|
+
.map(([hookId]) => hookId);
|
|
371
|
+
if (missingIntegrity.length > 0) {
|
|
372
|
+
findings.push({
|
|
373
|
+
checkId: "hooks.installs_missing_integrity",
|
|
374
|
+
severity: "warn",
|
|
375
|
+
title: "Hook installs are missing integrity metadata",
|
|
376
|
+
detail: `Hook install records missing integrity:\n${missingIntegrity.map((entry) => `- ${entry}`).join("\n")}`,
|
|
377
|
+
remediation: "Reinstall or update hooks to refresh install metadata with resolved integrity hashes.",
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
const hookVersionDrift = [];
|
|
381
|
+
for (const [hookId, record] of npmHookInstalls) {
|
|
382
|
+
const recordedVersion = record.resolvedVersion ?? record.version;
|
|
383
|
+
if (!recordedVersion) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
const installPath = record.installPath ?? path.join(params.stateDir, "hooks", hookId);
|
|
387
|
+
// eslint-disable-next-line no-await-in-loop
|
|
388
|
+
const installedVersion = await readInstalledPackageVersion(installPath);
|
|
389
|
+
if (!installedVersion || installedVersion === recordedVersion) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
hookVersionDrift.push(`${hookId} (recorded ${recordedVersion}, installed ${installedVersion})`);
|
|
393
|
+
}
|
|
394
|
+
if (hookVersionDrift.length > 0) {
|
|
395
|
+
findings.push({
|
|
396
|
+
checkId: "hooks.installs_version_drift",
|
|
397
|
+
severity: "warn",
|
|
398
|
+
title: "Hook install records drift from installed package versions",
|
|
399
|
+
detail: `Detected hook install metadata drift:\n${hookVersionDrift.map((entry) => `- ${entry}`).join("\n")}`,
|
|
400
|
+
remediation: "Run `openclaw hooks update --all` (or reinstall affected hooks) to refresh install metadata.",
|
|
401
|
+
});
|
|
402
|
+
}
|
|
229
403
|
}
|
|
230
404
|
return findings;
|
|
231
405
|
}
|
|
@@ -452,22 +626,18 @@ export async function readConfigSnapshotForAudit(params) {
|
|
|
452
626
|
}
|
|
453
627
|
export async function collectPluginsCodeSafetyFindings(params) {
|
|
454
628
|
const findings = [];
|
|
455
|
-
const extensionsDir =
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
remediation: "Check file permissions and plugin layout, then rerun `poolbot security audit --deep`.",
|
|
467
|
-
});
|
|
468
|
-
return [];
|
|
629
|
+
const { extensionsDir, pluginDirs } = await listInstalledPluginDirs({
|
|
630
|
+
stateDir: params.stateDir,
|
|
631
|
+
onReadError: (err) => {
|
|
632
|
+
findings.push({
|
|
633
|
+
checkId: "plugins.code_safety.scan_failed",
|
|
634
|
+
severity: "warn",
|
|
635
|
+
title: "Plugin extensions directory scan failed",
|
|
636
|
+
detail: `Static code scan could not list extensions directory: ${String(err)}`,
|
|
637
|
+
remediation: "Check file permissions and plugin layout, then rerun `poolbot security audit --deep`.",
|
|
638
|
+
});
|
|
639
|
+
},
|
|
469
640
|
});
|
|
470
|
-
const pluginDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
471
641
|
for (const pluginName of pluginDirs) {
|
|
472
642
|
const pluginPath = path.join(extensionsDir, pluginName);
|
|
473
643
|
const extensionEntries = await readPluginManifestExtensions(pluginPath).catch(() => []);
|
|
@@ -496,12 +666,14 @@ export async function collectPluginsCodeSafetyFindings(params) {
|
|
|
496
666
|
severity: "critical",
|
|
497
667
|
title: `Plugin "${pluginName}" has extension entry path traversal`,
|
|
498
668
|
detail: `Found extension entries that escape the plugin directory:\n${escapedEntries.map((entry) => ` - ${entry}`).join("\n")}`,
|
|
499
|
-
remediation: "Update the plugin manifest so all
|
|
669
|
+
remediation: "Update the plugin manifest so all openclaw.extensions entries stay inside the plugin directory.",
|
|
500
670
|
});
|
|
501
671
|
}
|
|
502
|
-
const summary = await
|
|
672
|
+
const summary = await skillScanner
|
|
673
|
+
.scanDirectoryWithSummary(pluginPath, {
|
|
503
674
|
includeFiles: forcedScanEntries,
|
|
504
|
-
})
|
|
675
|
+
})
|
|
676
|
+
.catch((err) => {
|
|
505
677
|
findings.push({
|
|
506
678
|
checkId: "plugins.code_safety.scan_failed",
|
|
507
679
|
severity: "warn",
|
|
@@ -522,7 +694,7 @@ export async function collectPluginsCodeSafetyFindings(params) {
|
|
|
522
694
|
severity: "critical",
|
|
523
695
|
title: `Plugin "${pluginName}" contains dangerous code patterns`,
|
|
524
696
|
detail: `Found ${summary.critical} critical issue(s) in ${summary.scannedFiles} scanned file(s):\n${details}`,
|
|
525
|
-
remediation: "Review the plugin source code carefully before use. If untrusted, remove the plugin from your
|
|
697
|
+
remediation: "Review the plugin source code carefully before use. If untrusted, remove the plugin from your OpenClaw extensions state directory.",
|
|
526
698
|
});
|
|
527
699
|
}
|
|
528
700
|
else if (summary.warn > 0) {
|
|
@@ -543,7 +715,7 @@ export async function collectInstalledSkillsCodeSafetyFindings(params) {
|
|
|
543
715
|
const findings = [];
|
|
544
716
|
const pluginExtensionsDir = path.join(params.stateDir, "extensions");
|
|
545
717
|
const scannedSkillDirs = new Set();
|
|
546
|
-
const workspaceDirs =
|
|
718
|
+
const workspaceDirs = listAgentWorkspaceDirs(params.cfg);
|
|
547
719
|
for (const workspaceDir of workspaceDirs) {
|
|
548
720
|
const entries = loadWorkspaceSkillEntries(workspaceDir, { config: params.cfg });
|
|
549
721
|
for (const entry of entries) {
|
|
@@ -560,7 +732,7 @@ export async function collectInstalledSkillsCodeSafetyFindings(params) {
|
|
|
560
732
|
}
|
|
561
733
|
scannedSkillDirs.add(skillDir);
|
|
562
734
|
const skillName = entry.skill.name;
|
|
563
|
-
const summary = await scanDirectoryWithSummary(skillDir).catch((err) => {
|
|
735
|
+
const summary = await skillScanner.scanDirectoryWithSummary(skillDir).catch((err) => {
|
|
564
736
|
findings.push({
|
|
565
737
|
checkId: "skills.code_safety.scan_failed",
|
|
566
738
|
severity: "warn",
|
|
@@ -1,2 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Re-export barrel for security audit collector functions.
|
|
3
|
+
*
|
|
4
|
+
* Maintains backward compatibility with existing imports from audit-extra.
|
|
5
|
+
* Implementation split into:
|
|
6
|
+
* - audit-extra.sync.ts: Config-based checks (no I/O)
|
|
7
|
+
* - audit-extra.async.ts: Filesystem/plugin checks (async I/O)
|
|
8
|
+
*/
|
|
9
|
+
// Sync collectors
|
|
10
|
+
export { collectAttackSurfaceSummaryFindings, collectExposureMatrixFindings, collectGatewayHttpNoAuthFindings, collectGatewayHttpSessionKeyOverrideFindings, collectHooksHardeningFindings, collectMinimalProfileOverrideFindings, collectModelHygieneFindings, collectNodeDenyCommandPatternFindings, collectSandboxDangerousConfigFindings, collectSandboxDockerNoopFindings, collectSecretsInConfigFindings, collectSmallModelRiskFindings, collectSyncedFolderFindings, } from "./audit-extra.sync.js";
|
|
11
|
+
// Async collectors
|
|
2
12
|
export { collectIncludeFilePermFindings, collectInstalledSkillsCodeSafetyFindings, collectPluginsCodeSafetyFindings, collectPluginsTrustFindings, collectStateDeepFilesystemFindings, readConfigSnapshotForAudit, } from "./audit-extra.async.js";
|