@poolzin/pool-bot 2026.2.0 → 2026.2.1
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/dist/agents/bash-tools.exec.js +76 -25
- package/dist/agents/cli-runner/helpers.js +9 -11
- package/dist/agents/identity.js +47 -7
- package/dist/agents/memory-search.js +25 -8
- package/dist/agents/model-selection.js +21 -0
- package/dist/agents/pi-embedded-block-chunker.js +117 -42
- package/dist/agents/pi-embedded-helpers/errors.js +183 -78
- package/dist/agents/pi-embedded-helpers.js +1 -1
- package/dist/agents/pi-embedded-runner/compact.js +1 -0
- package/dist/agents/pi-embedded-runner/model.js +61 -2
- package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
- package/dist/agents/pi-embedded-runner/run.js +199 -46
- package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
- package/dist/agents/pi-embedded-subscribe.js +118 -29
- package/dist/agents/pi-tools.js +10 -5
- package/dist/agents/poolbot-tools.js +15 -10
- package/dist/agents/sandbox-paths.js +31 -0
- package/dist/agents/session-tool-result-guard.js +94 -15
- package/dist/agents/shell-utils.js +51 -0
- package/dist/agents/skills/bundled-context.js +23 -0
- package/dist/agents/skills/bundled-dir.js +41 -7
- package/dist/agents/skills-install.js +60 -23
- package/dist/agents/subagent-announce.js +79 -34
- package/dist/agents/tool-policy.conformance.js +14 -0
- package/dist/agents/tool-policy.js +24 -0
- package/dist/agents/tools/cron-tool.js +166 -19
- package/dist/agents/tools/discord-actions-presence.js +78 -0
- package/dist/agents/tools/message-tool.js +56 -2
- package/dist/agents/tools/sessions-history-tool.js +69 -1
- package/dist/agents/tools/web-search.js +211 -42
- package/dist/agents/usage.js +23 -1
- package/dist/agents/workspace-run.js +67 -0
- package/dist/agents/workspace-templates.js +44 -0
- package/dist/auto-reply/command-auth.js +121 -6
- package/dist/auto-reply/envelope.js +50 -72
- package/dist/auto-reply/reply/commands-compact.js +1 -0
- package/dist/auto-reply/reply/commands-context-report.js +1 -0
- package/dist/auto-reply/reply/commands-context.js +1 -0
- package/dist/auto-reply/reply/commands-models.js +107 -60
- package/dist/auto-reply/reply/commands-ptt.js +171 -0
- package/dist/auto-reply/reply/get-reply-run.js +2 -1
- package/dist/auto-reply/reply/inbound-context.js +5 -1
- package/dist/auto-reply/reply/model-selection.js +3 -3
- package/dist/auto-reply/thinking.js +88 -43
- package/dist/browser/bridge-server.js +13 -0
- package/dist/browser/cdp.helpers.js +38 -24
- package/dist/browser/client-fetch.js +50 -7
- package/dist/browser/config.js +1 -10
- package/dist/browser/extension-relay.js +101 -40
- package/dist/browser/pw-ai.js +1 -1
- package/dist/browser/pw-session.js +143 -8
- package/dist/browser/pw-tools-core.interactions.js +125 -27
- package/dist/browser/pw-tools-core.responses.js +1 -1
- package/dist/browser/pw-tools-core.state.js +1 -1
- package/dist/browser/routes/agent.act.js +86 -41
- package/dist/browser/routes/dispatcher.js +4 -4
- package/dist/browser/screenshot.js +1 -1
- package/dist/browser/server.js +13 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/reply-prefix.js +8 -1
- package/dist/cli/cron-cli/register.cron-add.js +61 -40
- package/dist/cli/cron-cli/register.cron-edit.js +60 -34
- package/dist/cli/cron-cli/shared.js +56 -41
- package/dist/cli/dns-cli.js +26 -14
- package/dist/cli/gateway-cli/register.js +37 -19
- package/dist/cli/memory-cli.js +5 -5
- package/dist/cli/parse-bytes.js +37 -0
- package/dist/cli/update-cli.js +173 -52
- package/dist/commands/agent.js +1 -0
- package/dist/commands/doctor-config-flow.js +61 -5
- package/dist/commands/doctor-state-migrations.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/model-allowlist.js +29 -0
- package/dist/commands/model-picker.js +2 -1
- package/dist/commands/models/list.status-command.js +43 -23
- package/dist/commands/models/shared.js +15 -0
- package/dist/commands/onboard-custom.js +384 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
- package/dist/commands/onboard-skills.js +63 -38
- package/dist/commands/openai-model-default.js +41 -0
- package/dist/config/defaults.js +3 -2
- package/dist/config/paths.js +136 -35
- package/dist/config/plugin-auto-enable.js +21 -5
- package/dist/config/redact-snapshot.js +153 -0
- package/dist/config/schema.field-metadata.js +590 -0
- package/dist/config/schema.js +2 -2
- package/dist/config/sessions/store.js +291 -23
- package/dist/config/zod-schema.agent-defaults.js +3 -0
- package/dist/config/zod-schema.agent-runtime.js +13 -2
- package/dist/config/zod-schema.providers-core.js +142 -0
- package/dist/config/zod-schema.session.js +3 -0
- package/dist/cron/delivery.js +57 -0
- package/dist/cron/isolated-agent/delivery-target.js +18 -3
- package/dist/cron/isolated-agent/helpers.js +22 -5
- package/dist/cron/isolated-agent/run.js +171 -63
- package/dist/cron/isolated-agent/session.js +2 -0
- package/dist/cron/normalize.js +356 -28
- package/dist/cron/parse.js +10 -5
- package/dist/cron/run-log.js +35 -10
- package/dist/cron/schedule.js +41 -6
- package/dist/cron/service/jobs.js +208 -35
- package/dist/cron/service/ops.js +72 -16
- package/dist/cron/service/state.js +2 -0
- package/dist/cron/service/store.js +386 -14
- package/dist/cron/service/timer.js +390 -147
- package/dist/cron/session-reaper.js +86 -0
- package/dist/cron/store.js +23 -8
- package/dist/cron/validate-timestamp.js +43 -0
- package/dist/discord/monitor/agent-components.js +438 -0
- package/dist/discord/monitor/allow-list.js +28 -5
- package/dist/discord/monitor/gateway-registry.js +29 -0
- package/dist/discord/monitor/native-command.js +44 -23
- package/dist/discord/monitor/sender-identity.js +45 -0
- package/dist/discord/pluralkit.js +27 -0
- package/dist/discord/send.outbound.js +92 -5
- package/dist/discord/send.shared.js +60 -23
- package/dist/discord/targets.js +84 -1
- package/dist/entry.js +15 -9
- package/dist/extensionAPI.js +8 -0
- package/dist/gateway/control-ui.js +8 -1
- package/dist/gateway/hooks-mapping.js +3 -0
- package/dist/gateway/hooks.js +65 -0
- package/dist/gateway/net.js +96 -31
- package/dist/gateway/node-command-policy.js +50 -15
- package/dist/gateway/origin-check.js +56 -0
- package/dist/gateway/protocol/client-info.js +9 -0
- package/dist/gateway/protocol/index.js +9 -2
- package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
- package/dist/gateway/protocol/schema/cron.js +22 -10
- package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
- package/dist/gateway/protocol/schema/sessions.js +12 -0
- package/dist/gateway/server/hooks.js +1 -1
- package/dist/gateway/server-broadcast.js +26 -9
- package/dist/gateway/server-chat.js +112 -23
- package/dist/gateway/server-discovery-runtime.js +10 -2
- package/dist/gateway/server-http.js +109 -11
- package/dist/gateway/server-methods/agent-timestamp.js +60 -0
- package/dist/gateway/server-methods/agents.js +321 -2
- package/dist/gateway/server-methods/usage.js +559 -16
- package/dist/gateway/server-runtime-state.js +22 -8
- package/dist/gateway/server-startup-memory.js +16 -0
- package/dist/gateway/server.impl.js +5 -1
- package/dist/gateway/session-utils.fs.js +23 -25
- package/dist/gateway/session-utils.js +20 -10
- package/dist/gateway/sessions-patch.js +7 -22
- package/dist/gateway/test-helpers.server.js +35 -2
- package/dist/imessage/constants.js +2 -0
- package/dist/imessage/monitor/deliver.js +4 -1
- package/dist/imessage/monitor/monitor-provider.js +51 -1
- package/dist/infra/bonjour-discovery.js +131 -70
- package/dist/infra/control-ui-assets.js +134 -12
- package/dist/infra/errors.js +12 -0
- package/dist/infra/exec-approvals.js +266 -57
- package/dist/infra/format-time/format-datetime.js +79 -0
- package/dist/infra/format-time/format-duration.js +81 -0
- package/dist/infra/format-time/format-relative.js +80 -0
- package/dist/infra/heartbeat-runner.js +140 -49
- package/dist/infra/home-dir.js +54 -0
- package/dist/infra/net/fetch-guard.js +122 -0
- package/dist/infra/net/ssrf.js +65 -29
- package/dist/infra/outbound/abort.js +14 -0
- package/dist/infra/outbound/message-action-runner.js +77 -13
- package/dist/infra/outbound/outbound-session.js +143 -37
- package/dist/infra/poolbot-root.js +43 -1
- package/dist/infra/session-cost-usage.js +631 -41
- package/dist/infra/state-migrations.js +317 -47
- package/dist/infra/update-global.js +35 -0
- package/dist/infra/update-runner.js +149 -43
- package/dist/infra/warning-filter.js +65 -0
- package/dist/infra/widearea-dns.js +30 -9
- package/dist/logging/redact-identifier.js +12 -0
- package/dist/media/fetch.js +81 -58
- package/dist/media-understanding/apply.js +403 -3
- package/dist/media-understanding/attachments.js +38 -27
- package/dist/media-understanding/defaults.js +16 -0
- package/dist/media-understanding/providers/deepgram/audio.js +22 -14
- package/dist/media-understanding/providers/google/audio.js +24 -17
- package/dist/media-understanding/providers/google/video.js +24 -17
- package/dist/media-understanding/providers/image.js +2 -2
- package/dist/media-understanding/providers/index.js +4 -1
- package/dist/media-understanding/providers/openai/audio.js +22 -14
- package/dist/media-understanding/providers/shared.js +16 -11
- package/dist/media-understanding/providers/zai/index.js +6 -0
- package/dist/media-understanding/runner.js +158 -90
- package/dist/memory/batch-voyage.js +277 -0
- package/dist/memory/embeddings-voyage.js +75 -0
- package/dist/memory/embeddings.js +28 -16
- package/dist/memory/internal.js +101 -18
- package/dist/memory/manager.js +154 -48
- package/dist/memory/search-manager.js +173 -0
- package/dist/memory/session-files.js +9 -3
- package/dist/node-host/runner.js +34 -24
- package/dist/node-host/with-timeout.js +27 -0
- package/dist/plugins/commands.js +5 -1
- package/dist/plugins/config-state.js +86 -7
- package/dist/plugins/source-display.js +51 -0
- package/dist/process/exec.js +20 -2
- package/dist/routing/resolve-route.js +12 -0
- package/dist/routing/session-key.js +15 -0
- package/dist/runtime.js +2 -0
- package/dist/security/audit-extra.async.js +601 -0
- package/dist/security/audit-extra.js +2 -830
- package/dist/security/audit-extra.sync.js +505 -0
- package/dist/security/channel-metadata.js +34 -0
- package/dist/security/external-content.js +88 -6
- package/dist/security/skill-scanner.js +330 -0
- package/dist/sessions/session-key-utils.js +7 -0
- package/dist/signal/monitor/event-handler.js +80 -1
- package/dist/slack/monitor/media.js +85 -15
- package/dist/tailscale/detect.js +1 -2
- package/dist/telegram/bot/helpers.js +109 -28
- package/dist/telegram/bot-handlers.js +144 -3
- package/dist/telegram/bot-message-context.js +37 -10
- package/dist/telegram/bot-message-dispatch.js +48 -15
- package/dist/telegram/bot-native-commands.js +86 -29
- package/dist/telegram/bot.js +30 -29
- package/dist/telegram/model-buttons.js +163 -0
- package/dist/telegram/monitor.js +110 -85
- package/dist/telegram/send.js +129 -47
- package/dist/terminal/restore.js +45 -0
- package/dist/test-helpers/state-dir-env.js +16 -0
- package/dist/tts/tts.js +12 -6
- package/dist/tui/tui-session-actions.js +166 -54
- package/dist/utils/fetch-timeout.js +20 -0
- package/dist/utils/normalize-secret-input.js +19 -0
- package/dist/utils/transcript-tools.js +58 -0
- package/dist/utils.js +45 -14
- package/dist/version.js +42 -5
- package/package.json +1 -1
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asynchronous security audit collector functions.
|
|
3
|
+
*
|
|
4
|
+
* These functions perform I/O (filesystem, config reads) to detect security issues.
|
|
5
|
+
*/
|
|
6
|
+
import JSON5 from "json5";
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
10
|
+
import { loadWorkspaceSkillEntries } from "../agents/skills.js";
|
|
11
|
+
import { LEGACY_MANIFEST_KEY } from "../compat/legacy-names.js";
|
|
12
|
+
import { resolveNativeSkillsEnabled } from "../config/commands.js";
|
|
13
|
+
import { createConfigIO } from "../config/config.js";
|
|
14
|
+
import { INCLUDE_KEY, MAX_INCLUDE_DEPTH } from "../config/includes.js";
|
|
15
|
+
import { resolveOAuthDir } from "../config/paths.js";
|
|
16
|
+
import { normalizeAgentId } from "../routing/session-key.js";
|
|
17
|
+
import { formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, safeStat, } from "./audit-fs.js";
|
|
18
|
+
import { scanDirectoryWithSummary } from "./skill-scanner.js";
|
|
19
|
+
// --------------------------------------------------------------------------
|
|
20
|
+
// Helpers
|
|
21
|
+
// --------------------------------------------------------------------------
|
|
22
|
+
function expandTilde(p, env) {
|
|
23
|
+
if (!p.startsWith("~")) {
|
|
24
|
+
return p;
|
|
25
|
+
}
|
|
26
|
+
const home = typeof env.HOME === "string" && env.HOME.trim() ? env.HOME.trim() : null;
|
|
27
|
+
if (!home) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (p === "~") {
|
|
31
|
+
return home;
|
|
32
|
+
}
|
|
33
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) {
|
|
34
|
+
return path.join(home, p.slice(2));
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
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
|
+
async function readPluginManifestExtensions(pluginPath) {
|
|
124
|
+
const manifestPath = path.join(pluginPath, "package.json");
|
|
125
|
+
const raw = await fs.readFile(manifestPath, "utf-8").catch(() => "");
|
|
126
|
+
if (!raw.trim()) {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
const parsed = JSON.parse(raw);
|
|
130
|
+
const extensions = parsed?.[LEGACY_MANIFEST_KEY]?.extensions;
|
|
131
|
+
if (!Array.isArray(extensions)) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
return extensions.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
|
135
|
+
}
|
|
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
|
+
function formatCodeSafetyDetails(findings, rootDir) {
|
|
150
|
+
return findings
|
|
151
|
+
.map((finding) => {
|
|
152
|
+
const relPath = path.relative(rootDir, finding.file);
|
|
153
|
+
const filePath = relPath && relPath !== "." && !relPath.startsWith("..")
|
|
154
|
+
? relPath
|
|
155
|
+
: path.basename(finding.file);
|
|
156
|
+
const normalizedPath = filePath.replaceAll("\\", "/");
|
|
157
|
+
return ` - [${finding.ruleId}] ${finding.message} (${normalizedPath}:${finding.line})`;
|
|
158
|
+
})
|
|
159
|
+
.join("\n");
|
|
160
|
+
}
|
|
161
|
+
// --------------------------------------------------------------------------
|
|
162
|
+
// Exported collectors
|
|
163
|
+
// --------------------------------------------------------------------------
|
|
164
|
+
export async function collectPluginsTrustFindings(params) {
|
|
165
|
+
const findings = [];
|
|
166
|
+
const extensionsDir = path.join(params.stateDir, "extensions");
|
|
167
|
+
const st = await safeStat(extensionsDir);
|
|
168
|
+
if (!st.ok || !st.isDir) {
|
|
169
|
+
return findings;
|
|
170
|
+
}
|
|
171
|
+
const entries = await fs.readdir(extensionsDir, { withFileTypes: true }).catch(() => []);
|
|
172
|
+
const pluginDirs = entries
|
|
173
|
+
.filter((e) => e.isDirectory())
|
|
174
|
+
.map((e) => e.name)
|
|
175
|
+
.filter(Boolean);
|
|
176
|
+
if (pluginDirs.length === 0) {
|
|
177
|
+
return findings;
|
|
178
|
+
}
|
|
179
|
+
const allow = params.cfg.plugins?.allow;
|
|
180
|
+
const allowConfigured = Array.isArray(allow) && allow.length > 0;
|
|
181
|
+
if (!allowConfigured) {
|
|
182
|
+
const hasString = (value) => typeof value === "string" && value.trim().length > 0;
|
|
183
|
+
const hasAccountStringKey = (account, key) => Boolean(account &&
|
|
184
|
+
typeof account === "object" &&
|
|
185
|
+
hasString(account[key]));
|
|
186
|
+
const discordConfigured = hasString(params.cfg.channels?.discord?.token) ||
|
|
187
|
+
Boolean(params.cfg.channels?.discord?.accounts &&
|
|
188
|
+
Object.values(params.cfg.channels.discord.accounts).some((a) => hasAccountStringKey(a, "token"))) ||
|
|
189
|
+
hasString(process.env.DISCORD_BOT_TOKEN);
|
|
190
|
+
const telegramConfigured = hasString(params.cfg.channels?.telegram?.botToken) ||
|
|
191
|
+
hasString(params.cfg.channels?.telegram?.tokenFile) ||
|
|
192
|
+
Boolean(params.cfg.channels?.telegram?.accounts &&
|
|
193
|
+
Object.values(params.cfg.channels.telegram.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "tokenFile"))) ||
|
|
194
|
+
hasString(process.env.TELEGRAM_BOT_TOKEN);
|
|
195
|
+
const slackConfigured = hasString(params.cfg.channels?.slack?.botToken) ||
|
|
196
|
+
hasString(params.cfg.channels?.slack?.appToken) ||
|
|
197
|
+
Boolean(params.cfg.channels?.slack?.accounts &&
|
|
198
|
+
Object.values(params.cfg.channels.slack.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "appToken"))) ||
|
|
199
|
+
hasString(process.env.SLACK_BOT_TOKEN) ||
|
|
200
|
+
hasString(process.env.SLACK_APP_TOKEN);
|
|
201
|
+
const skillCommandsLikelyExposed = (discordConfigured &&
|
|
202
|
+
resolveNativeSkillsEnabled({
|
|
203
|
+
providerId: "discord",
|
|
204
|
+
providerSetting: params.cfg.channels?.discord?.commands?.nativeSkills,
|
|
205
|
+
globalSetting: params.cfg.commands?.nativeSkills,
|
|
206
|
+
})) ||
|
|
207
|
+
(telegramConfigured &&
|
|
208
|
+
resolveNativeSkillsEnabled({
|
|
209
|
+
providerId: "telegram",
|
|
210
|
+
providerSetting: params.cfg.channels?.telegram?.commands?.nativeSkills,
|
|
211
|
+
globalSetting: params.cfg.commands?.nativeSkills,
|
|
212
|
+
})) ||
|
|
213
|
+
(slackConfigured &&
|
|
214
|
+
resolveNativeSkillsEnabled({
|
|
215
|
+
providerId: "slack",
|
|
216
|
+
providerSetting: params.cfg.channels?.slack?.commands?.nativeSkills,
|
|
217
|
+
globalSetting: params.cfg.commands?.nativeSkills,
|
|
218
|
+
}));
|
|
219
|
+
findings.push({
|
|
220
|
+
checkId: "plugins.extensions_no_allowlist",
|
|
221
|
+
severity: skillCommandsLikelyExposed ? "critical" : "warn",
|
|
222
|
+
title: "Extensions exist but plugins.allow is not set",
|
|
223
|
+
detail: `Found ${pluginDirs.length} extension(s) under ${extensionsDir}. Without plugins.allow, any discovered plugin id may load (depending on config and plugin behavior).` +
|
|
224
|
+
(skillCommandsLikelyExposed
|
|
225
|
+
? "\nNative skill commands are enabled on at least one configured chat surface; treat unpinned/unallowlisted extensions as high risk."
|
|
226
|
+
: ""),
|
|
227
|
+
remediation: "Set plugins.allow to an explicit list of plugin ids you trust.",
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return findings;
|
|
231
|
+
}
|
|
232
|
+
export async function collectIncludeFilePermFindings(params) {
|
|
233
|
+
const findings = [];
|
|
234
|
+
if (!params.configSnapshot.exists) {
|
|
235
|
+
return findings;
|
|
236
|
+
}
|
|
237
|
+
const configPath = params.configSnapshot.path;
|
|
238
|
+
const includePaths = await collectIncludePathsRecursive({
|
|
239
|
+
configPath,
|
|
240
|
+
parsed: params.configSnapshot.parsed,
|
|
241
|
+
});
|
|
242
|
+
if (includePaths.length === 0) {
|
|
243
|
+
return findings;
|
|
244
|
+
}
|
|
245
|
+
for (const p of includePaths) {
|
|
246
|
+
// eslint-disable-next-line no-await-in-loop
|
|
247
|
+
const perms = await inspectPathPermissions(p, {
|
|
248
|
+
env: params.env,
|
|
249
|
+
platform: params.platform,
|
|
250
|
+
exec: params.execIcacls,
|
|
251
|
+
});
|
|
252
|
+
if (!perms.ok) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (perms.worldWritable || perms.groupWritable) {
|
|
256
|
+
findings.push({
|
|
257
|
+
checkId: "fs.config_include.perms_writable",
|
|
258
|
+
severity: "critical",
|
|
259
|
+
title: "Config include file is writable by others",
|
|
260
|
+
detail: `${formatPermissionDetail(p, perms)}; another user could influence your effective config.`,
|
|
261
|
+
remediation: formatPermissionRemediation({
|
|
262
|
+
targetPath: p,
|
|
263
|
+
perms,
|
|
264
|
+
isDir: false,
|
|
265
|
+
posixMode: 0o600,
|
|
266
|
+
env: params.env,
|
|
267
|
+
}),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else if (perms.worldReadable) {
|
|
271
|
+
findings.push({
|
|
272
|
+
checkId: "fs.config_include.perms_world_readable",
|
|
273
|
+
severity: "critical",
|
|
274
|
+
title: "Config include file is world-readable",
|
|
275
|
+
detail: `${formatPermissionDetail(p, perms)}; include files can contain tokens and private settings.`,
|
|
276
|
+
remediation: formatPermissionRemediation({
|
|
277
|
+
targetPath: p,
|
|
278
|
+
perms,
|
|
279
|
+
isDir: false,
|
|
280
|
+
posixMode: 0o600,
|
|
281
|
+
env: params.env,
|
|
282
|
+
}),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
else if (perms.groupReadable) {
|
|
286
|
+
findings.push({
|
|
287
|
+
checkId: "fs.config_include.perms_group_readable",
|
|
288
|
+
severity: "warn",
|
|
289
|
+
title: "Config include file is group-readable",
|
|
290
|
+
detail: `${formatPermissionDetail(p, perms)}; include files can contain tokens and private settings.`,
|
|
291
|
+
remediation: formatPermissionRemediation({
|
|
292
|
+
targetPath: p,
|
|
293
|
+
perms,
|
|
294
|
+
isDir: false,
|
|
295
|
+
posixMode: 0o600,
|
|
296
|
+
env: params.env,
|
|
297
|
+
}),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return findings;
|
|
302
|
+
}
|
|
303
|
+
export async function collectStateDeepFilesystemFindings(params) {
|
|
304
|
+
const findings = [];
|
|
305
|
+
const oauthDir = resolveOAuthDir(params.env, params.stateDir);
|
|
306
|
+
const oauthPerms = await inspectPathPermissions(oauthDir, {
|
|
307
|
+
env: params.env,
|
|
308
|
+
platform: params.platform,
|
|
309
|
+
exec: params.execIcacls,
|
|
310
|
+
});
|
|
311
|
+
if (oauthPerms.ok && oauthPerms.isDir) {
|
|
312
|
+
if (oauthPerms.worldWritable || oauthPerms.groupWritable) {
|
|
313
|
+
findings.push({
|
|
314
|
+
checkId: "fs.credentials_dir.perms_writable",
|
|
315
|
+
severity: "critical",
|
|
316
|
+
title: "Credentials dir is writable by others",
|
|
317
|
+
detail: `${formatPermissionDetail(oauthDir, oauthPerms)}; another user could drop/modify credential files.`,
|
|
318
|
+
remediation: formatPermissionRemediation({
|
|
319
|
+
targetPath: oauthDir,
|
|
320
|
+
perms: oauthPerms,
|
|
321
|
+
isDir: true,
|
|
322
|
+
posixMode: 0o700,
|
|
323
|
+
env: params.env,
|
|
324
|
+
}),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
else if (oauthPerms.groupReadable || oauthPerms.worldReadable) {
|
|
328
|
+
findings.push({
|
|
329
|
+
checkId: "fs.credentials_dir.perms_readable",
|
|
330
|
+
severity: "warn",
|
|
331
|
+
title: "Credentials dir is readable by others",
|
|
332
|
+
detail: `${formatPermissionDetail(oauthDir, oauthPerms)}; credentials and allowlists can be sensitive.`,
|
|
333
|
+
remediation: formatPermissionRemediation({
|
|
334
|
+
targetPath: oauthDir,
|
|
335
|
+
perms: oauthPerms,
|
|
336
|
+
isDir: true,
|
|
337
|
+
posixMode: 0o700,
|
|
338
|
+
env: params.env,
|
|
339
|
+
}),
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const agentIds = Array.isArray(params.cfg.agents?.list)
|
|
344
|
+
? params.cfg.agents?.list
|
|
345
|
+
.map((a) => (a && typeof a === "object" && typeof a.id === "string" ? a.id.trim() : ""))
|
|
346
|
+
.filter(Boolean)
|
|
347
|
+
: [];
|
|
348
|
+
const defaultAgentId = resolveDefaultAgentId(params.cfg);
|
|
349
|
+
const ids = Array.from(new Set([defaultAgentId, ...agentIds])).map((id) => normalizeAgentId(id));
|
|
350
|
+
for (const agentId of ids) {
|
|
351
|
+
const agentDir = path.join(params.stateDir, "agents", agentId, "agent");
|
|
352
|
+
const authPath = path.join(agentDir, "auth-profiles.json");
|
|
353
|
+
// eslint-disable-next-line no-await-in-loop
|
|
354
|
+
const authPerms = await inspectPathPermissions(authPath, {
|
|
355
|
+
env: params.env,
|
|
356
|
+
platform: params.platform,
|
|
357
|
+
exec: params.execIcacls,
|
|
358
|
+
});
|
|
359
|
+
if (authPerms.ok) {
|
|
360
|
+
if (authPerms.worldWritable || authPerms.groupWritable) {
|
|
361
|
+
findings.push({
|
|
362
|
+
checkId: "fs.auth_profiles.perms_writable",
|
|
363
|
+
severity: "critical",
|
|
364
|
+
title: "auth-profiles.json is writable by others",
|
|
365
|
+
detail: `${formatPermissionDetail(authPath, authPerms)}; another user could inject credentials.`,
|
|
366
|
+
remediation: formatPermissionRemediation({
|
|
367
|
+
targetPath: authPath,
|
|
368
|
+
perms: authPerms,
|
|
369
|
+
isDir: false,
|
|
370
|
+
posixMode: 0o600,
|
|
371
|
+
env: params.env,
|
|
372
|
+
}),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
else if (authPerms.worldReadable || authPerms.groupReadable) {
|
|
376
|
+
findings.push({
|
|
377
|
+
checkId: "fs.auth_profiles.perms_readable",
|
|
378
|
+
severity: "warn",
|
|
379
|
+
title: "auth-profiles.json is readable by others",
|
|
380
|
+
detail: `${formatPermissionDetail(authPath, authPerms)}; auth-profiles.json contains API keys and OAuth tokens.`,
|
|
381
|
+
remediation: formatPermissionRemediation({
|
|
382
|
+
targetPath: authPath,
|
|
383
|
+
perms: authPerms,
|
|
384
|
+
isDir: false,
|
|
385
|
+
posixMode: 0o600,
|
|
386
|
+
env: params.env,
|
|
387
|
+
}),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
const storePath = path.join(params.stateDir, "agents", agentId, "sessions", "sessions.json");
|
|
392
|
+
// eslint-disable-next-line no-await-in-loop
|
|
393
|
+
const storePerms = await inspectPathPermissions(storePath, {
|
|
394
|
+
env: params.env,
|
|
395
|
+
platform: params.platform,
|
|
396
|
+
exec: params.execIcacls,
|
|
397
|
+
});
|
|
398
|
+
if (storePerms.ok) {
|
|
399
|
+
if (storePerms.worldReadable || storePerms.groupReadable) {
|
|
400
|
+
findings.push({
|
|
401
|
+
checkId: "fs.sessions_store.perms_readable",
|
|
402
|
+
severity: "warn",
|
|
403
|
+
title: "sessions.json is readable by others",
|
|
404
|
+
detail: `${formatPermissionDetail(storePath, storePerms)}; routing and transcript metadata can be sensitive.`,
|
|
405
|
+
remediation: formatPermissionRemediation({
|
|
406
|
+
targetPath: storePath,
|
|
407
|
+
perms: storePerms,
|
|
408
|
+
isDir: false,
|
|
409
|
+
posixMode: 0o600,
|
|
410
|
+
env: params.env,
|
|
411
|
+
}),
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const logFile = typeof params.cfg.logging?.file === "string" ? params.cfg.logging.file.trim() : "";
|
|
417
|
+
if (logFile) {
|
|
418
|
+
const expanded = logFile.startsWith("~") ? expandTilde(logFile, params.env) : logFile;
|
|
419
|
+
if (expanded) {
|
|
420
|
+
const logPath = path.resolve(expanded);
|
|
421
|
+
const logPerms = await inspectPathPermissions(logPath, {
|
|
422
|
+
env: params.env,
|
|
423
|
+
platform: params.platform,
|
|
424
|
+
exec: params.execIcacls,
|
|
425
|
+
});
|
|
426
|
+
if (logPerms.ok) {
|
|
427
|
+
if (logPerms.worldReadable || logPerms.groupReadable) {
|
|
428
|
+
findings.push({
|
|
429
|
+
checkId: "fs.log_file.perms_readable",
|
|
430
|
+
severity: "warn",
|
|
431
|
+
title: "Log file is readable by others",
|
|
432
|
+
detail: `${formatPermissionDetail(logPath, logPerms)}; logs can contain private messages and tool output.`,
|
|
433
|
+
remediation: formatPermissionRemediation({
|
|
434
|
+
targetPath: logPath,
|
|
435
|
+
perms: logPerms,
|
|
436
|
+
isDir: false,
|
|
437
|
+
posixMode: 0o600,
|
|
438
|
+
env: params.env,
|
|
439
|
+
}),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return findings;
|
|
446
|
+
}
|
|
447
|
+
export async function readConfigSnapshotForAudit(params) {
|
|
448
|
+
return await createConfigIO({
|
|
449
|
+
env: params.env,
|
|
450
|
+
configPath: params.configPath,
|
|
451
|
+
}).readConfigFileSnapshot();
|
|
452
|
+
}
|
|
453
|
+
export async function collectPluginsCodeSafetyFindings(params) {
|
|
454
|
+
const findings = [];
|
|
455
|
+
const extensionsDir = path.join(params.stateDir, "extensions");
|
|
456
|
+
const st = await safeStat(extensionsDir);
|
|
457
|
+
if (!st.ok || !st.isDir) {
|
|
458
|
+
return findings;
|
|
459
|
+
}
|
|
460
|
+
const entries = await fs.readdir(extensionsDir, { withFileTypes: true }).catch((err) => {
|
|
461
|
+
findings.push({
|
|
462
|
+
checkId: "plugins.code_safety.scan_failed",
|
|
463
|
+
severity: "warn",
|
|
464
|
+
title: "Plugin extensions directory scan failed",
|
|
465
|
+
detail: `Static code scan could not list extensions directory: ${String(err)}`,
|
|
466
|
+
remediation: "Check file permissions and plugin layout, then rerun `poolbot security audit --deep`.",
|
|
467
|
+
});
|
|
468
|
+
return [];
|
|
469
|
+
});
|
|
470
|
+
const pluginDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
471
|
+
for (const pluginName of pluginDirs) {
|
|
472
|
+
const pluginPath = path.join(extensionsDir, pluginName);
|
|
473
|
+
const extensionEntries = await readPluginManifestExtensions(pluginPath).catch(() => []);
|
|
474
|
+
const forcedScanEntries = [];
|
|
475
|
+
const escapedEntries = [];
|
|
476
|
+
for (const entry of extensionEntries) {
|
|
477
|
+
const resolvedEntry = path.resolve(pluginPath, entry);
|
|
478
|
+
if (!isPathInside(pluginPath, resolvedEntry)) {
|
|
479
|
+
escapedEntries.push(entry);
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (extensionUsesSkippedScannerPath(entry)) {
|
|
483
|
+
findings.push({
|
|
484
|
+
checkId: "plugins.code_safety.entry_path",
|
|
485
|
+
severity: "warn",
|
|
486
|
+
title: `Plugin "${pluginName}" entry path is hidden or node_modules`,
|
|
487
|
+
detail: `Extension entry "${entry}" points to a hidden or node_modules path. Deep code scan will cover this entry explicitly, but review this path choice carefully.`,
|
|
488
|
+
remediation: "Prefer extension entrypoints under normal source paths like dist/ or src/.",
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
forcedScanEntries.push(resolvedEntry);
|
|
492
|
+
}
|
|
493
|
+
if (escapedEntries.length > 0) {
|
|
494
|
+
findings.push({
|
|
495
|
+
checkId: "plugins.code_safety.entry_escape",
|
|
496
|
+
severity: "critical",
|
|
497
|
+
title: `Plugin "${pluginName}" has extension entry path traversal`,
|
|
498
|
+
detail: `Found extension entries that escape the plugin directory:\n${escapedEntries.map((entry) => ` - ${entry}`).join("\n")}`,
|
|
499
|
+
remediation: "Update the plugin manifest so all poolbot.extensions entries stay inside the plugin directory.",
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
const summary = await scanDirectoryWithSummary(pluginPath, {
|
|
503
|
+
includeFiles: forcedScanEntries,
|
|
504
|
+
}).catch((err) => {
|
|
505
|
+
findings.push({
|
|
506
|
+
checkId: "plugins.code_safety.scan_failed",
|
|
507
|
+
severity: "warn",
|
|
508
|
+
title: `Plugin "${pluginName}" code scan failed`,
|
|
509
|
+
detail: `Static code scan could not complete: ${String(err)}`,
|
|
510
|
+
remediation: "Check file permissions and plugin layout, then rerun `poolbot security audit --deep`.",
|
|
511
|
+
});
|
|
512
|
+
return null;
|
|
513
|
+
});
|
|
514
|
+
if (!summary) {
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (summary.critical > 0) {
|
|
518
|
+
const criticalFindings = summary.findings.filter((f) => f.severity === "critical");
|
|
519
|
+
const details = formatCodeSafetyDetails(criticalFindings, pluginPath);
|
|
520
|
+
findings.push({
|
|
521
|
+
checkId: "plugins.code_safety",
|
|
522
|
+
severity: "critical",
|
|
523
|
+
title: `Plugin "${pluginName}" contains dangerous code patterns`,
|
|
524
|
+
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 Pool Bot extensions state directory.",
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
else if (summary.warn > 0) {
|
|
529
|
+
const warnFindings = summary.findings.filter((f) => f.severity === "warn");
|
|
530
|
+
const details = formatCodeSafetyDetails(warnFindings, pluginPath);
|
|
531
|
+
findings.push({
|
|
532
|
+
checkId: "plugins.code_safety",
|
|
533
|
+
severity: "warn",
|
|
534
|
+
title: `Plugin "${pluginName}" contains suspicious code patterns`,
|
|
535
|
+
detail: `Found ${summary.warn} warning(s) in ${summary.scannedFiles} scanned file(s):\n${details}`,
|
|
536
|
+
remediation: `Review the flagged code to ensure it is intentional and safe.`,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return findings;
|
|
541
|
+
}
|
|
542
|
+
export async function collectInstalledSkillsCodeSafetyFindings(params) {
|
|
543
|
+
const findings = [];
|
|
544
|
+
const pluginExtensionsDir = path.join(params.stateDir, "extensions");
|
|
545
|
+
const scannedSkillDirs = new Set();
|
|
546
|
+
const workspaceDirs = listWorkspaceDirs(params.cfg);
|
|
547
|
+
for (const workspaceDir of workspaceDirs) {
|
|
548
|
+
const entries = loadWorkspaceSkillEntries(workspaceDir, { config: params.cfg });
|
|
549
|
+
for (const entry of entries) {
|
|
550
|
+
if (entry.skill.source === "poolbot-bundled") {
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
const skillDir = path.resolve(entry.skill.baseDir);
|
|
554
|
+
if (isPathInside(pluginExtensionsDir, skillDir)) {
|
|
555
|
+
// Plugin code is already covered by plugins.code_safety checks.
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (scannedSkillDirs.has(skillDir)) {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
scannedSkillDirs.add(skillDir);
|
|
562
|
+
const skillName = entry.skill.name;
|
|
563
|
+
const summary = await scanDirectoryWithSummary(skillDir).catch((err) => {
|
|
564
|
+
findings.push({
|
|
565
|
+
checkId: "skills.code_safety.scan_failed",
|
|
566
|
+
severity: "warn",
|
|
567
|
+
title: `Skill "${skillName}" code scan failed`,
|
|
568
|
+
detail: `Static code scan could not complete for ${skillDir}: ${String(err)}`,
|
|
569
|
+
remediation: "Check file permissions and skill layout, then rerun `poolbot security audit --deep`.",
|
|
570
|
+
});
|
|
571
|
+
return null;
|
|
572
|
+
});
|
|
573
|
+
if (!summary) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
if (summary.critical > 0) {
|
|
577
|
+
const criticalFindings = summary.findings.filter((finding) => finding.severity === "critical");
|
|
578
|
+
const details = formatCodeSafetyDetails(criticalFindings, skillDir);
|
|
579
|
+
findings.push({
|
|
580
|
+
checkId: "skills.code_safety",
|
|
581
|
+
severity: "critical",
|
|
582
|
+
title: `Skill "${skillName}" contains dangerous code patterns`,
|
|
583
|
+
detail: `Found ${summary.critical} critical issue(s) in ${summary.scannedFiles} scanned file(s) under ${skillDir}:\n${details}`,
|
|
584
|
+
remediation: `Review the skill source code before use. If untrusted, remove "${skillDir}".`,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
else if (summary.warn > 0) {
|
|
588
|
+
const warnFindings = summary.findings.filter((finding) => finding.severity === "warn");
|
|
589
|
+
const details = formatCodeSafetyDetails(warnFindings, skillDir);
|
|
590
|
+
findings.push({
|
|
591
|
+
checkId: "skills.code_safety",
|
|
592
|
+
severity: "warn",
|
|
593
|
+
title: `Skill "${skillName}" contains suspicious code patterns`,
|
|
594
|
+
detail: `Found ${summary.warn} warning(s) in ${summary.scannedFiles} scanned file(s) under ${skillDir}:\n${details}`,
|
|
595
|
+
remediation: "Review flagged lines to ensure the behavior is intentional and safe.",
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return findings;
|
|
601
|
+
}
|