@poolzin/pool-bot 2026.2.0 → 2026.2.2
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 +118 -0
- package/README-header.png +0 -0
- package/dist/agents/bash-tools.exec.js +76 -25
- package/dist/agents/cli-runner/helpers.js +9 -11
- package/dist/agents/context.js +1 -1
- package/dist/agents/identity.js +47 -7
- package/dist/agents/memory-search.js +25 -8
- package/dist/agents/model-catalog.js +1 -1
- 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 +8 -10
- package/dist/agents/pi-embedded-runner/model.js +62 -3
- 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/image-tool.js +1 -1
- 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 +74 -82
- 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/mentions.js +1 -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/canvas-host/a2ui/index.html +28 -28
- 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/auth-choice.apply.oauth.js +1 -1
- 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.registry.js +1 -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/compat/legacy-names.js +2 -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/control-ui/assets/{index-CIRDm-Lu.css → index-CSfXd2LO.css} +1 -1
- package/dist/control-ui/assets/{index-CmNMuoem.js → index-HRr1grwl.js} +446 -413
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -0
- package/dist/control-ui/index.html +4 -4
- 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 +172 -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.mocks.js +11 -7
- 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/store.js +2 -0
- 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 +3 -3
- 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 +54 -17
- 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/dist/wizard/clack-prompter.js +9 -6
- package/extensions/googlechat/node_modules/.bin/poolbot +21 -0
- package/extensions/googlechat/package.json +2 -2
- package/extensions/line/node_modules/.bin/poolbot +21 -0
- package/extensions/line/package.json +1 -1
- package/extensions/matrix/node_modules/.bin/poolbot +21 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/memory-core/node_modules/.bin/poolbot +21 -0
- package/extensions/memory-core/package.json +4 -1
- package/extensions/twitch/node_modules/.bin/poolbot +21 -0
- package/extensions/twitch/package.json +1 -1
- package/package.json +183 -24
- package/dist/control-ui/assets/index-CmNMuoem.js.map +0 -1
|
@@ -1,830 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import JSON5 from "json5";
|
|
4
|
-
import { createConfigIO } from "../config/config.js";
|
|
5
|
-
import { resolveNativeSkillsEnabled } from "../config/commands.js";
|
|
6
|
-
import { resolveOAuthDir } from "../config/paths.js";
|
|
7
|
-
import { formatCliCommand } from "../cli/command-format.js";
|
|
8
|
-
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
9
|
-
import { resolveBrowserConfig } from "../browser/config.js";
|
|
10
|
-
import { isToolAllowedByPolicies } from "../agents/pi-tools.policy.js";
|
|
11
|
-
import { resolveToolProfilePolicy } from "../agents/tool-policy.js";
|
|
12
|
-
import { resolveSandboxConfigForAgent, resolveSandboxToolPolicyForAgent, } from "../agents/sandbox.js";
|
|
13
|
-
import { resolveGatewayAuth } from "../gateway/auth.js";
|
|
14
|
-
import { INCLUDE_KEY, MAX_INCLUDE_DEPTH } from "../config/includes.js";
|
|
15
|
-
import { normalizeAgentId } from "../routing/session-key.js";
|
|
16
|
-
import { formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, safeStat, } from "./audit-fs.js";
|
|
17
|
-
const SMALL_MODEL_PARAM_B_MAX = 300;
|
|
18
|
-
function expandTilde(p, env) {
|
|
19
|
-
if (!p.startsWith("~"))
|
|
20
|
-
return p;
|
|
21
|
-
const home = typeof env.HOME === "string" && env.HOME.trim() ? env.HOME.trim() : null;
|
|
22
|
-
if (!home)
|
|
23
|
-
return null;
|
|
24
|
-
if (p === "~")
|
|
25
|
-
return home;
|
|
26
|
-
if (p.startsWith("~/") || p.startsWith("~\\"))
|
|
27
|
-
return path.join(home, p.slice(2));
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
function summarizeGroupPolicy(cfg) {
|
|
31
|
-
const channels = cfg.channels;
|
|
32
|
-
if (!channels || typeof channels !== "object")
|
|
33
|
-
return { open: 0, allowlist: 0, other: 0 };
|
|
34
|
-
let open = 0;
|
|
35
|
-
let allowlist = 0;
|
|
36
|
-
let other = 0;
|
|
37
|
-
for (const value of Object.values(channels)) {
|
|
38
|
-
if (!value || typeof value !== "object")
|
|
39
|
-
continue;
|
|
40
|
-
const section = value;
|
|
41
|
-
const policy = section.groupPolicy;
|
|
42
|
-
if (policy === "open")
|
|
43
|
-
open += 1;
|
|
44
|
-
else if (policy === "allowlist")
|
|
45
|
-
allowlist += 1;
|
|
46
|
-
else
|
|
47
|
-
other += 1;
|
|
48
|
-
}
|
|
49
|
-
return { open, allowlist, other };
|
|
50
|
-
}
|
|
51
|
-
export function collectAttackSurfaceSummaryFindings(cfg) {
|
|
52
|
-
const group = summarizeGroupPolicy(cfg);
|
|
53
|
-
const elevated = cfg.tools?.elevated?.enabled !== false;
|
|
54
|
-
const hooksEnabled = cfg.hooks?.enabled === true;
|
|
55
|
-
const browserEnabled = cfg.browser?.enabled ?? true;
|
|
56
|
-
const detail = `groups: open=${group.open}, allowlist=${group.allowlist}` +
|
|
57
|
-
`\n` +
|
|
58
|
-
`tools.elevated: ${elevated ? "enabled" : "disabled"}` +
|
|
59
|
-
`\n` +
|
|
60
|
-
`hooks: ${hooksEnabled ? "enabled" : "disabled"}` +
|
|
61
|
-
`\n` +
|
|
62
|
-
`browser control: ${browserEnabled ? "enabled" : "disabled"}`;
|
|
63
|
-
return [
|
|
64
|
-
{
|
|
65
|
-
checkId: "summary.attack_surface",
|
|
66
|
-
severity: "info",
|
|
67
|
-
title: "Attack surface summary",
|
|
68
|
-
detail,
|
|
69
|
-
},
|
|
70
|
-
];
|
|
71
|
-
}
|
|
72
|
-
function isProbablySyncedPath(p) {
|
|
73
|
-
const s = p.toLowerCase();
|
|
74
|
-
return (s.includes("icloud") ||
|
|
75
|
-
s.includes("dropbox") ||
|
|
76
|
-
s.includes("google drive") ||
|
|
77
|
-
s.includes("googledrive") ||
|
|
78
|
-
s.includes("onedrive"));
|
|
79
|
-
}
|
|
80
|
-
export function collectSyncedFolderFindings(params) {
|
|
81
|
-
const findings = [];
|
|
82
|
-
if (isProbablySyncedPath(params.stateDir) || isProbablySyncedPath(params.configPath)) {
|
|
83
|
-
findings.push({
|
|
84
|
-
checkId: "fs.synced_dir",
|
|
85
|
-
severity: "warn",
|
|
86
|
-
title: "State/config path looks like a synced folder",
|
|
87
|
-
detail: `stateDir=${params.stateDir}, configPath=${params.configPath}. Synced folders (iCloud/Dropbox/OneDrive/Google Drive) can leak tokens and transcripts onto other devices.`,
|
|
88
|
-
remediation: `Keep CLAWDBOT_STATE_DIR on a local-only volume and re-run "${formatCliCommand("poolbot security audit --fix")}".`,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
return findings;
|
|
92
|
-
}
|
|
93
|
-
function looksLikeEnvRef(value) {
|
|
94
|
-
const v = value.trim();
|
|
95
|
-
return v.startsWith("${") && v.endsWith("}");
|
|
96
|
-
}
|
|
97
|
-
export function collectSecretsInConfigFindings(cfg) {
|
|
98
|
-
const findings = [];
|
|
99
|
-
const password = typeof cfg.gateway?.auth?.password === "string" ? cfg.gateway.auth.password.trim() : "";
|
|
100
|
-
if (password && !looksLikeEnvRef(password)) {
|
|
101
|
-
findings.push({
|
|
102
|
-
checkId: "config.secrets.gateway_password_in_config",
|
|
103
|
-
severity: "warn",
|
|
104
|
-
title: "Gateway password is stored in config",
|
|
105
|
-
detail: "gateway.auth.password is set in the config file; prefer environment variables for secrets when possible.",
|
|
106
|
-
remediation: "Prefer CLAWDBOT_GATEWAY_PASSWORD (env) and remove gateway.auth.password from disk.",
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
const hooksToken = typeof cfg.hooks?.token === "string" ? cfg.hooks.token.trim() : "";
|
|
110
|
-
if (cfg.hooks?.enabled === true && hooksToken && !looksLikeEnvRef(hooksToken)) {
|
|
111
|
-
findings.push({
|
|
112
|
-
checkId: "config.secrets.hooks_token_in_config",
|
|
113
|
-
severity: "info",
|
|
114
|
-
title: "Hooks token is stored in config",
|
|
115
|
-
detail: "hooks.token is set in the config file; keep config perms tight and treat it like an API secret.",
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
return findings;
|
|
119
|
-
}
|
|
120
|
-
export function collectHooksHardeningFindings(cfg) {
|
|
121
|
-
const findings = [];
|
|
122
|
-
if (cfg.hooks?.enabled !== true)
|
|
123
|
-
return findings;
|
|
124
|
-
const token = typeof cfg.hooks?.token === "string" ? cfg.hooks.token.trim() : "";
|
|
125
|
-
if (token && token.length < 24) {
|
|
126
|
-
findings.push({
|
|
127
|
-
checkId: "hooks.token_too_short",
|
|
128
|
-
severity: "warn",
|
|
129
|
-
title: "Hooks token looks short",
|
|
130
|
-
detail: `hooks.token is ${token.length} chars; prefer a long random token.`,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
const gatewayAuth = resolveGatewayAuth({
|
|
134
|
-
authConfig: cfg.gateway?.auth,
|
|
135
|
-
tailscaleMode: cfg.gateway?.tailscale?.mode ?? "off",
|
|
136
|
-
});
|
|
137
|
-
const gatewayToken = gatewayAuth.mode === "token" &&
|
|
138
|
-
typeof gatewayAuth.token === "string" &&
|
|
139
|
-
gatewayAuth.token.trim()
|
|
140
|
-
? gatewayAuth.token.trim()
|
|
141
|
-
: null;
|
|
142
|
-
if (token && gatewayToken && token === gatewayToken) {
|
|
143
|
-
findings.push({
|
|
144
|
-
checkId: "hooks.token_reuse_gateway_token",
|
|
145
|
-
severity: "warn",
|
|
146
|
-
title: "Hooks token reuses the Gateway token",
|
|
147
|
-
detail: "hooks.token matches gateway.auth token; compromise of hooks expands blast radius to the Gateway API.",
|
|
148
|
-
remediation: "Use a separate hooks.token dedicated to hook ingress.",
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
const rawPath = typeof cfg.hooks?.path === "string" ? cfg.hooks.path.trim() : "";
|
|
152
|
-
if (rawPath === "/") {
|
|
153
|
-
findings.push({
|
|
154
|
-
checkId: "hooks.path_root",
|
|
155
|
-
severity: "critical",
|
|
156
|
-
title: "Hooks base path is '/'",
|
|
157
|
-
detail: "hooks.path='/' would shadow other HTTP endpoints and is unsafe.",
|
|
158
|
-
remediation: "Use a dedicated path like '/hooks'.",
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
return findings;
|
|
162
|
-
}
|
|
163
|
-
function addModel(models, raw, source) {
|
|
164
|
-
if (typeof raw !== "string")
|
|
165
|
-
return;
|
|
166
|
-
const id = raw.trim();
|
|
167
|
-
if (!id)
|
|
168
|
-
return;
|
|
169
|
-
models.push({ id, source });
|
|
170
|
-
}
|
|
171
|
-
function collectModels(cfg) {
|
|
172
|
-
const out = [];
|
|
173
|
-
addModel(out, cfg.agents?.defaults?.model?.primary, "agents.defaults.model.primary");
|
|
174
|
-
for (const f of cfg.agents?.defaults?.model?.fallbacks ?? [])
|
|
175
|
-
addModel(out, f, "agents.defaults.model.fallbacks");
|
|
176
|
-
addModel(out, cfg.agents?.defaults?.imageModel?.primary, "agents.defaults.imageModel.primary");
|
|
177
|
-
for (const f of cfg.agents?.defaults?.imageModel?.fallbacks ?? [])
|
|
178
|
-
addModel(out, f, "agents.defaults.imageModel.fallbacks");
|
|
179
|
-
const list = Array.isArray(cfg.agents?.list) ? cfg.agents?.list : [];
|
|
180
|
-
for (const agent of list ?? []) {
|
|
181
|
-
if (!agent || typeof agent !== "object")
|
|
182
|
-
continue;
|
|
183
|
-
const id = typeof agent.id === "string" ? agent.id : "";
|
|
184
|
-
const model = agent.model;
|
|
185
|
-
if (typeof model === "string") {
|
|
186
|
-
addModel(out, model, `agents.list.${id}.model`);
|
|
187
|
-
}
|
|
188
|
-
else if (model && typeof model === "object") {
|
|
189
|
-
addModel(out, model.primary, `agents.list.${id}.model.primary`);
|
|
190
|
-
const fallbacks = model.fallbacks;
|
|
191
|
-
if (Array.isArray(fallbacks)) {
|
|
192
|
-
for (const f of fallbacks)
|
|
193
|
-
addModel(out, f, `agents.list.${id}.model.fallbacks`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return out;
|
|
198
|
-
}
|
|
199
|
-
const LEGACY_MODEL_PATTERNS = [
|
|
200
|
-
{ id: "openai.gpt35", re: /\bgpt-3\.5\b/i, label: "GPT-3.5 family" },
|
|
201
|
-
{ id: "anthropic.claude2", re: /\bclaude-(instant|2)\b/i, label: "Claude 2/Instant family" },
|
|
202
|
-
{ id: "openai.gpt4_legacy", re: /\bgpt-4-(0314|0613)\b/i, label: "Legacy GPT-4 snapshots" },
|
|
203
|
-
];
|
|
204
|
-
const WEAK_TIER_MODEL_PATTERNS = [
|
|
205
|
-
{ id: "anthropic.haiku", re: /\bhaiku\b/i, label: "Haiku tier (smaller model)" },
|
|
206
|
-
];
|
|
207
|
-
function inferParamBFromIdOrName(text) {
|
|
208
|
-
const raw = text.toLowerCase();
|
|
209
|
-
const matches = raw.matchAll(/(?:^|[^a-z0-9])[a-z]?(\d+(?:\.\d+)?)b(?:[^a-z0-9]|$)/g);
|
|
210
|
-
let best = null;
|
|
211
|
-
for (const match of matches) {
|
|
212
|
-
const numRaw = match[1];
|
|
213
|
-
if (!numRaw)
|
|
214
|
-
continue;
|
|
215
|
-
const value = Number(numRaw);
|
|
216
|
-
if (!Number.isFinite(value) || value <= 0)
|
|
217
|
-
continue;
|
|
218
|
-
if (best === null || value > best)
|
|
219
|
-
best = value;
|
|
220
|
-
}
|
|
221
|
-
return best;
|
|
222
|
-
}
|
|
223
|
-
function isGptModel(id) {
|
|
224
|
-
return /\bgpt-/i.test(id);
|
|
225
|
-
}
|
|
226
|
-
function isGpt5OrHigher(id) {
|
|
227
|
-
return /\bgpt-5(?:\b|[.-])/i.test(id);
|
|
228
|
-
}
|
|
229
|
-
function isClaudeModel(id) {
|
|
230
|
-
return /\bclaude-/i.test(id);
|
|
231
|
-
}
|
|
232
|
-
function isClaude45OrHigher(id) {
|
|
233
|
-
return /\bclaude-[^\s/]*?(?:-4-5\b|4\.5\b)/i.test(id);
|
|
234
|
-
}
|
|
235
|
-
export function collectModelHygieneFindings(cfg) {
|
|
236
|
-
const findings = [];
|
|
237
|
-
const models = collectModels(cfg);
|
|
238
|
-
if (models.length === 0)
|
|
239
|
-
return findings;
|
|
240
|
-
const weakMatches = new Map();
|
|
241
|
-
const addWeakMatch = (model, source, reason) => {
|
|
242
|
-
const key = `${model}@@${source}`;
|
|
243
|
-
const existing = weakMatches.get(key);
|
|
244
|
-
if (!existing) {
|
|
245
|
-
weakMatches.set(key, { model, source, reasons: [reason] });
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
if (!existing.reasons.includes(reason))
|
|
249
|
-
existing.reasons.push(reason);
|
|
250
|
-
};
|
|
251
|
-
for (const entry of models) {
|
|
252
|
-
for (const pat of WEAK_TIER_MODEL_PATTERNS) {
|
|
253
|
-
if (pat.re.test(entry.id)) {
|
|
254
|
-
addWeakMatch(entry.id, entry.source, pat.label);
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
if (isGptModel(entry.id) && !isGpt5OrHigher(entry.id)) {
|
|
259
|
-
addWeakMatch(entry.id, entry.source, "Below GPT-5 family");
|
|
260
|
-
}
|
|
261
|
-
if (isClaudeModel(entry.id) && !isClaude45OrHigher(entry.id)) {
|
|
262
|
-
addWeakMatch(entry.id, entry.source, "Below Claude 4.5");
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
const matches = [];
|
|
266
|
-
for (const entry of models) {
|
|
267
|
-
for (const pat of LEGACY_MODEL_PATTERNS) {
|
|
268
|
-
if (pat.re.test(entry.id)) {
|
|
269
|
-
matches.push({ model: entry.id, source: entry.source, reason: pat.label });
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
if (matches.length > 0) {
|
|
275
|
-
const lines = matches
|
|
276
|
-
.slice(0, 12)
|
|
277
|
-
.map((m) => `- ${m.model} (${m.reason}) @ ${m.source}`)
|
|
278
|
-
.join("\n");
|
|
279
|
-
const more = matches.length > 12 ? `\n…${matches.length - 12} more` : "";
|
|
280
|
-
findings.push({
|
|
281
|
-
checkId: "models.legacy",
|
|
282
|
-
severity: "warn",
|
|
283
|
-
title: "Some configured models look legacy",
|
|
284
|
-
detail: "Older/legacy models can be less robust against prompt injection and tool misuse.\n" +
|
|
285
|
-
lines +
|
|
286
|
-
more,
|
|
287
|
-
remediation: "Prefer modern, instruction-hardened models for any bot that can run tools.",
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
if (weakMatches.size > 0) {
|
|
291
|
-
const lines = Array.from(weakMatches.values())
|
|
292
|
-
.slice(0, 12)
|
|
293
|
-
.map((m) => `- ${m.model} (${m.reasons.join("; ")}) @ ${m.source}`)
|
|
294
|
-
.join("\n");
|
|
295
|
-
const more = weakMatches.size > 12 ? `\n…${weakMatches.size - 12} more` : "";
|
|
296
|
-
findings.push({
|
|
297
|
-
checkId: "models.weak_tier",
|
|
298
|
-
severity: "warn",
|
|
299
|
-
title: "Some configured models are below recommended tiers",
|
|
300
|
-
detail: "Smaller/older models are generally more susceptible to prompt injection and tool misuse.\n" +
|
|
301
|
-
lines +
|
|
302
|
-
more,
|
|
303
|
-
remediation: "Use the latest, top-tier model for any bot with tools or untrusted inboxes. Avoid Haiku tiers; prefer GPT-5+ and Claude 4.5+.",
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
return findings;
|
|
307
|
-
}
|
|
308
|
-
function extractAgentIdFromSource(source) {
|
|
309
|
-
const match = source.match(/^agents\.list\.([^.]*)\./);
|
|
310
|
-
return match?.[1] ?? null;
|
|
311
|
-
}
|
|
312
|
-
function pickToolPolicy(config) {
|
|
313
|
-
if (!config)
|
|
314
|
-
return null;
|
|
315
|
-
const allow = Array.isArray(config.allow) ? config.allow : undefined;
|
|
316
|
-
const deny = Array.isArray(config.deny) ? config.deny : undefined;
|
|
317
|
-
if (!allow && !deny)
|
|
318
|
-
return null;
|
|
319
|
-
return { allow, deny };
|
|
320
|
-
}
|
|
321
|
-
function resolveToolPolicies(params) {
|
|
322
|
-
const policies = [];
|
|
323
|
-
const profile = params.agentTools?.profile ?? params.cfg.tools?.profile;
|
|
324
|
-
const profilePolicy = resolveToolProfilePolicy(profile);
|
|
325
|
-
if (profilePolicy)
|
|
326
|
-
policies.push(profilePolicy);
|
|
327
|
-
const globalPolicy = pickToolPolicy(params.cfg.tools ?? undefined);
|
|
328
|
-
if (globalPolicy)
|
|
329
|
-
policies.push(globalPolicy);
|
|
330
|
-
const agentPolicy = pickToolPolicy(params.agentTools);
|
|
331
|
-
if (agentPolicy)
|
|
332
|
-
policies.push(agentPolicy);
|
|
333
|
-
if (params.sandboxMode === "all") {
|
|
334
|
-
const sandboxPolicy = resolveSandboxToolPolicyForAgent(params.cfg, params.agentId ?? undefined);
|
|
335
|
-
policies.push(sandboxPolicy);
|
|
336
|
-
}
|
|
337
|
-
return policies;
|
|
338
|
-
}
|
|
339
|
-
function hasWebSearchKey(cfg, env) {
|
|
340
|
-
const search = cfg.tools?.web?.search;
|
|
341
|
-
return Boolean(search?.apiKey ||
|
|
342
|
-
search?.perplexity?.apiKey ||
|
|
343
|
-
env.BRAVE_API_KEY ||
|
|
344
|
-
env.PERPLEXITY_API_KEY ||
|
|
345
|
-
env.OPENROUTER_API_KEY);
|
|
346
|
-
}
|
|
347
|
-
function isWebSearchEnabled(cfg, env) {
|
|
348
|
-
const enabled = cfg.tools?.web?.search?.enabled;
|
|
349
|
-
if (enabled === false)
|
|
350
|
-
return false;
|
|
351
|
-
if (enabled === true)
|
|
352
|
-
return true;
|
|
353
|
-
return hasWebSearchKey(cfg, env);
|
|
354
|
-
}
|
|
355
|
-
function isWebFetchEnabled(cfg) {
|
|
356
|
-
const enabled = cfg.tools?.web?.fetch?.enabled;
|
|
357
|
-
if (enabled === false)
|
|
358
|
-
return false;
|
|
359
|
-
return true;
|
|
360
|
-
}
|
|
361
|
-
function isBrowserEnabled(cfg) {
|
|
362
|
-
try {
|
|
363
|
-
return resolveBrowserConfig(cfg.browser, cfg).enabled;
|
|
364
|
-
}
|
|
365
|
-
catch {
|
|
366
|
-
return true;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
export function collectSmallModelRiskFindings(params) {
|
|
370
|
-
const findings = [];
|
|
371
|
-
const models = collectModels(params.cfg).filter((entry) => !entry.source.includes("imageModel"));
|
|
372
|
-
if (models.length === 0)
|
|
373
|
-
return findings;
|
|
374
|
-
const smallModels = models
|
|
375
|
-
.map((entry) => {
|
|
376
|
-
const paramB = inferParamBFromIdOrName(entry.id);
|
|
377
|
-
if (!paramB || paramB > SMALL_MODEL_PARAM_B_MAX)
|
|
378
|
-
return null;
|
|
379
|
-
return { ...entry, paramB };
|
|
380
|
-
})
|
|
381
|
-
.filter((entry) => Boolean(entry));
|
|
382
|
-
if (smallModels.length === 0)
|
|
383
|
-
return findings;
|
|
384
|
-
let hasUnsafe = false;
|
|
385
|
-
const modelLines = [];
|
|
386
|
-
const exposureSet = new Set();
|
|
387
|
-
for (const entry of smallModels) {
|
|
388
|
-
const agentId = extractAgentIdFromSource(entry.source);
|
|
389
|
-
const sandboxMode = resolveSandboxConfigForAgent(params.cfg, agentId ?? undefined).mode;
|
|
390
|
-
const agentTools = agentId && params.cfg.agents?.list
|
|
391
|
-
? params.cfg.agents.list.find((agent) => agent?.id === agentId)?.tools
|
|
392
|
-
: undefined;
|
|
393
|
-
const policies = resolveToolPolicies({
|
|
394
|
-
cfg: params.cfg,
|
|
395
|
-
agentTools,
|
|
396
|
-
sandboxMode,
|
|
397
|
-
agentId,
|
|
398
|
-
});
|
|
399
|
-
const exposed = [];
|
|
400
|
-
if (isWebSearchEnabled(params.cfg, params.env)) {
|
|
401
|
-
if (isToolAllowedByPolicies("web_search", policies))
|
|
402
|
-
exposed.push("web_search");
|
|
403
|
-
}
|
|
404
|
-
if (isWebFetchEnabled(params.cfg)) {
|
|
405
|
-
if (isToolAllowedByPolicies("web_fetch", policies))
|
|
406
|
-
exposed.push("web_fetch");
|
|
407
|
-
}
|
|
408
|
-
if (isBrowserEnabled(params.cfg)) {
|
|
409
|
-
if (isToolAllowedByPolicies("browser", policies))
|
|
410
|
-
exposed.push("browser");
|
|
411
|
-
}
|
|
412
|
-
for (const tool of exposed)
|
|
413
|
-
exposureSet.add(tool);
|
|
414
|
-
const sandboxLabel = sandboxMode === "all" ? "sandbox=all" : `sandbox=${sandboxMode}`;
|
|
415
|
-
const exposureLabel = exposed.length > 0 ? ` web=[${exposed.join(", ")}]` : " web=[off]";
|
|
416
|
-
const safe = sandboxMode === "all" && exposed.length === 0;
|
|
417
|
-
if (!safe)
|
|
418
|
-
hasUnsafe = true;
|
|
419
|
-
const statusLabel = safe ? "ok" : "unsafe";
|
|
420
|
-
modelLines.push(`- ${entry.id} (${entry.paramB}B) @ ${entry.source} (${statusLabel}; ${sandboxLabel};${exposureLabel})`);
|
|
421
|
-
}
|
|
422
|
-
const exposureList = Array.from(exposureSet);
|
|
423
|
-
const exposureDetail = exposureList.length > 0
|
|
424
|
-
? `Uncontrolled input tools allowed: ${exposureList.join(", ")}.`
|
|
425
|
-
: "No web/browser tools detected for these models.";
|
|
426
|
-
findings.push({
|
|
427
|
-
checkId: "models.small_params",
|
|
428
|
-
severity: hasUnsafe ? "critical" : "info",
|
|
429
|
-
title: "Small models require sandboxing and web tools disabled",
|
|
430
|
-
detail: `Small models (<=${SMALL_MODEL_PARAM_B_MAX}B params) detected:\n` +
|
|
431
|
-
modelLines.join("\n") +
|
|
432
|
-
`\n` +
|
|
433
|
-
exposureDetail +
|
|
434
|
-
`\n` +
|
|
435
|
-
"Small models are not recommended for untrusted inputs.",
|
|
436
|
-
remediation: 'If you must use small models, enable sandboxing for all sessions (agents.defaults.sandbox.mode="all") and disable web_search/web_fetch/browser (tools.deny=["group:web","browser"]).',
|
|
437
|
-
});
|
|
438
|
-
return findings;
|
|
439
|
-
}
|
|
440
|
-
export async function collectPluginsTrustFindings(params) {
|
|
441
|
-
const findings = [];
|
|
442
|
-
const extensionsDir = path.join(params.stateDir, "extensions");
|
|
443
|
-
const st = await safeStat(extensionsDir);
|
|
444
|
-
if (!st.ok || !st.isDir)
|
|
445
|
-
return findings;
|
|
446
|
-
const entries = await fs.readdir(extensionsDir, { withFileTypes: true }).catch(() => []);
|
|
447
|
-
const pluginDirs = entries
|
|
448
|
-
.filter((e) => e.isDirectory())
|
|
449
|
-
.map((e) => e.name)
|
|
450
|
-
.filter(Boolean);
|
|
451
|
-
if (pluginDirs.length === 0)
|
|
452
|
-
return findings;
|
|
453
|
-
const allow = params.cfg.plugins?.allow;
|
|
454
|
-
const allowConfigured = Array.isArray(allow) && allow.length > 0;
|
|
455
|
-
if (!allowConfigured) {
|
|
456
|
-
const hasString = (value) => typeof value === "string" && value.trim().length > 0;
|
|
457
|
-
const hasAccountStringKey = (account, key) => Boolean(account &&
|
|
458
|
-
typeof account === "object" &&
|
|
459
|
-
hasString(account[key]));
|
|
460
|
-
const discordConfigured = hasString(params.cfg.channels?.discord?.token) ||
|
|
461
|
-
Boolean(params.cfg.channels?.discord?.accounts &&
|
|
462
|
-
Object.values(params.cfg.channels.discord.accounts).some((a) => hasAccountStringKey(a, "token"))) ||
|
|
463
|
-
hasString(process.env.DISCORD_BOT_TOKEN);
|
|
464
|
-
const telegramConfigured = hasString(params.cfg.channels?.telegram?.botToken) ||
|
|
465
|
-
hasString(params.cfg.channels?.telegram?.tokenFile) ||
|
|
466
|
-
Boolean(params.cfg.channels?.telegram?.accounts &&
|
|
467
|
-
Object.values(params.cfg.channels.telegram.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "tokenFile"))) ||
|
|
468
|
-
hasString(process.env.TELEGRAM_BOT_TOKEN);
|
|
469
|
-
const slackConfigured = hasString(params.cfg.channels?.slack?.botToken) ||
|
|
470
|
-
hasString(params.cfg.channels?.slack?.appToken) ||
|
|
471
|
-
Boolean(params.cfg.channels?.slack?.accounts &&
|
|
472
|
-
Object.values(params.cfg.channels.slack.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "appToken"))) ||
|
|
473
|
-
hasString(process.env.SLACK_BOT_TOKEN) ||
|
|
474
|
-
hasString(process.env.SLACK_APP_TOKEN);
|
|
475
|
-
const skillCommandsLikelyExposed = (discordConfigured &&
|
|
476
|
-
resolveNativeSkillsEnabled({
|
|
477
|
-
providerId: "discord",
|
|
478
|
-
providerSetting: params.cfg.channels?.discord?.commands?.nativeSkills,
|
|
479
|
-
globalSetting: params.cfg.commands?.nativeSkills,
|
|
480
|
-
})) ||
|
|
481
|
-
(telegramConfigured &&
|
|
482
|
-
resolveNativeSkillsEnabled({
|
|
483
|
-
providerId: "telegram",
|
|
484
|
-
providerSetting: params.cfg.channels?.telegram?.commands?.nativeSkills,
|
|
485
|
-
globalSetting: params.cfg.commands?.nativeSkills,
|
|
486
|
-
})) ||
|
|
487
|
-
(slackConfigured &&
|
|
488
|
-
resolveNativeSkillsEnabled({
|
|
489
|
-
providerId: "slack",
|
|
490
|
-
providerSetting: params.cfg.channels?.slack?.commands?.nativeSkills,
|
|
491
|
-
globalSetting: params.cfg.commands?.nativeSkills,
|
|
492
|
-
}));
|
|
493
|
-
findings.push({
|
|
494
|
-
checkId: "plugins.extensions_no_allowlist",
|
|
495
|
-
severity: skillCommandsLikelyExposed ? "critical" : "warn",
|
|
496
|
-
title: "Extensions exist but plugins.allow is not set",
|
|
497
|
-
detail: `Found ${pluginDirs.length} extension(s) under ${extensionsDir}. Without plugins.allow, any discovered plugin id may load (depending on config and plugin behavior).` +
|
|
498
|
-
(skillCommandsLikelyExposed
|
|
499
|
-
? "\nNative skill commands are enabled on at least one configured chat surface; treat unpinned/unallowlisted extensions as high risk."
|
|
500
|
-
: ""),
|
|
501
|
-
remediation: "Set plugins.allow to an explicit list of plugin ids you trust.",
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
return findings;
|
|
505
|
-
}
|
|
506
|
-
function resolveIncludePath(baseConfigPath, includePath) {
|
|
507
|
-
return path.normalize(path.isAbsolute(includePath)
|
|
508
|
-
? includePath
|
|
509
|
-
: path.resolve(path.dirname(baseConfigPath), includePath));
|
|
510
|
-
}
|
|
511
|
-
function listDirectIncludes(parsed) {
|
|
512
|
-
const out = [];
|
|
513
|
-
const visit = (value) => {
|
|
514
|
-
if (!value)
|
|
515
|
-
return;
|
|
516
|
-
if (Array.isArray(value)) {
|
|
517
|
-
for (const item of value)
|
|
518
|
-
visit(item);
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
if (typeof value !== "object")
|
|
522
|
-
return;
|
|
523
|
-
const rec = value;
|
|
524
|
-
const includeVal = rec[INCLUDE_KEY];
|
|
525
|
-
if (typeof includeVal === "string")
|
|
526
|
-
out.push(includeVal);
|
|
527
|
-
else if (Array.isArray(includeVal)) {
|
|
528
|
-
for (const item of includeVal) {
|
|
529
|
-
if (typeof item === "string")
|
|
530
|
-
out.push(item);
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
for (const v of Object.values(rec))
|
|
534
|
-
visit(v);
|
|
535
|
-
};
|
|
536
|
-
visit(parsed);
|
|
537
|
-
return out;
|
|
538
|
-
}
|
|
539
|
-
async function collectIncludePathsRecursive(params) {
|
|
540
|
-
const visited = new Set();
|
|
541
|
-
const result = [];
|
|
542
|
-
const walk = async (basePath, parsed, depth) => {
|
|
543
|
-
if (depth > MAX_INCLUDE_DEPTH)
|
|
544
|
-
return;
|
|
545
|
-
for (const raw of listDirectIncludes(parsed)) {
|
|
546
|
-
const resolved = resolveIncludePath(basePath, raw);
|
|
547
|
-
if (visited.has(resolved))
|
|
548
|
-
continue;
|
|
549
|
-
visited.add(resolved);
|
|
550
|
-
result.push(resolved);
|
|
551
|
-
const rawText = await fs.readFile(resolved, "utf-8").catch(() => null);
|
|
552
|
-
if (!rawText)
|
|
553
|
-
continue;
|
|
554
|
-
const nestedParsed = (() => {
|
|
555
|
-
try {
|
|
556
|
-
return JSON5.parse(rawText);
|
|
557
|
-
}
|
|
558
|
-
catch {
|
|
559
|
-
return null;
|
|
560
|
-
}
|
|
561
|
-
})();
|
|
562
|
-
if (nestedParsed) {
|
|
563
|
-
// eslint-disable-next-line no-await-in-loop
|
|
564
|
-
await walk(resolved, nestedParsed, depth + 1);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
};
|
|
568
|
-
await walk(params.configPath, params.parsed, 0);
|
|
569
|
-
return result;
|
|
570
|
-
}
|
|
571
|
-
export async function collectIncludeFilePermFindings(params) {
|
|
572
|
-
const findings = [];
|
|
573
|
-
if (!params.configSnapshot.exists)
|
|
574
|
-
return findings;
|
|
575
|
-
const configPath = params.configSnapshot.path;
|
|
576
|
-
const includePaths = await collectIncludePathsRecursive({
|
|
577
|
-
configPath,
|
|
578
|
-
parsed: params.configSnapshot.parsed,
|
|
579
|
-
});
|
|
580
|
-
if (includePaths.length === 0)
|
|
581
|
-
return findings;
|
|
582
|
-
for (const p of includePaths) {
|
|
583
|
-
// eslint-disable-next-line no-await-in-loop
|
|
584
|
-
const perms = await inspectPathPermissions(p, {
|
|
585
|
-
env: params.env,
|
|
586
|
-
platform: params.platform,
|
|
587
|
-
exec: params.execIcacls,
|
|
588
|
-
});
|
|
589
|
-
if (!perms.ok)
|
|
590
|
-
continue;
|
|
591
|
-
if (perms.worldWritable || perms.groupWritable) {
|
|
592
|
-
findings.push({
|
|
593
|
-
checkId: "fs.config_include.perms_writable",
|
|
594
|
-
severity: "critical",
|
|
595
|
-
title: "Config include file is writable by others",
|
|
596
|
-
detail: `${formatPermissionDetail(p, perms)}; another user could influence your effective config.`,
|
|
597
|
-
remediation: formatPermissionRemediation({
|
|
598
|
-
targetPath: p,
|
|
599
|
-
perms,
|
|
600
|
-
isDir: false,
|
|
601
|
-
posixMode: 0o600,
|
|
602
|
-
env: params.env,
|
|
603
|
-
}),
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
else if (perms.worldReadable) {
|
|
607
|
-
findings.push({
|
|
608
|
-
checkId: "fs.config_include.perms_world_readable",
|
|
609
|
-
severity: "critical",
|
|
610
|
-
title: "Config include file is world-readable",
|
|
611
|
-
detail: `${formatPermissionDetail(p, perms)}; include files can contain tokens and private settings.`,
|
|
612
|
-
remediation: formatPermissionRemediation({
|
|
613
|
-
targetPath: p,
|
|
614
|
-
perms,
|
|
615
|
-
isDir: false,
|
|
616
|
-
posixMode: 0o600,
|
|
617
|
-
env: params.env,
|
|
618
|
-
}),
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
else if (perms.groupReadable) {
|
|
622
|
-
findings.push({
|
|
623
|
-
checkId: "fs.config_include.perms_group_readable",
|
|
624
|
-
severity: "warn",
|
|
625
|
-
title: "Config include file is group-readable",
|
|
626
|
-
detail: `${formatPermissionDetail(p, perms)}; include files can contain tokens and private settings.`,
|
|
627
|
-
remediation: formatPermissionRemediation({
|
|
628
|
-
targetPath: p,
|
|
629
|
-
perms,
|
|
630
|
-
isDir: false,
|
|
631
|
-
posixMode: 0o600,
|
|
632
|
-
env: params.env,
|
|
633
|
-
}),
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
return findings;
|
|
638
|
-
}
|
|
639
|
-
export async function collectStateDeepFilesystemFindings(params) {
|
|
640
|
-
const findings = [];
|
|
641
|
-
const oauthDir = resolveOAuthDir(params.env, params.stateDir);
|
|
642
|
-
const oauthPerms = await inspectPathPermissions(oauthDir, {
|
|
643
|
-
env: params.env,
|
|
644
|
-
platform: params.platform,
|
|
645
|
-
exec: params.execIcacls,
|
|
646
|
-
});
|
|
647
|
-
if (oauthPerms.ok && oauthPerms.isDir) {
|
|
648
|
-
if (oauthPerms.worldWritable || oauthPerms.groupWritable) {
|
|
649
|
-
findings.push({
|
|
650
|
-
checkId: "fs.credentials_dir.perms_writable",
|
|
651
|
-
severity: "critical",
|
|
652
|
-
title: "Credentials dir is writable by others",
|
|
653
|
-
detail: `${formatPermissionDetail(oauthDir, oauthPerms)}; another user could drop/modify credential files.`,
|
|
654
|
-
remediation: formatPermissionRemediation({
|
|
655
|
-
targetPath: oauthDir,
|
|
656
|
-
perms: oauthPerms,
|
|
657
|
-
isDir: true,
|
|
658
|
-
posixMode: 0o700,
|
|
659
|
-
env: params.env,
|
|
660
|
-
}),
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
else if (oauthPerms.groupReadable || oauthPerms.worldReadable) {
|
|
664
|
-
findings.push({
|
|
665
|
-
checkId: "fs.credentials_dir.perms_readable",
|
|
666
|
-
severity: "warn",
|
|
667
|
-
title: "Credentials dir is readable by others",
|
|
668
|
-
detail: `${formatPermissionDetail(oauthDir, oauthPerms)}; credentials and allowlists can be sensitive.`,
|
|
669
|
-
remediation: formatPermissionRemediation({
|
|
670
|
-
targetPath: oauthDir,
|
|
671
|
-
perms: oauthPerms,
|
|
672
|
-
isDir: true,
|
|
673
|
-
posixMode: 0o700,
|
|
674
|
-
env: params.env,
|
|
675
|
-
}),
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
const agentIds = Array.isArray(params.cfg.agents?.list)
|
|
680
|
-
? params.cfg.agents?.list
|
|
681
|
-
.map((a) => a && typeof a === "object" && typeof a.id === "string" ? a.id.trim() : "")
|
|
682
|
-
.filter(Boolean)
|
|
683
|
-
: [];
|
|
684
|
-
const defaultAgentId = resolveDefaultAgentId(params.cfg);
|
|
685
|
-
const ids = Array.from(new Set([defaultAgentId, ...agentIds])).map((id) => normalizeAgentId(id));
|
|
686
|
-
for (const agentId of ids) {
|
|
687
|
-
const agentDir = path.join(params.stateDir, "agents", agentId, "agent");
|
|
688
|
-
const authPath = path.join(agentDir, "auth-profiles.json");
|
|
689
|
-
// eslint-disable-next-line no-await-in-loop
|
|
690
|
-
const authPerms = await inspectPathPermissions(authPath, {
|
|
691
|
-
env: params.env,
|
|
692
|
-
platform: params.platform,
|
|
693
|
-
exec: params.execIcacls,
|
|
694
|
-
});
|
|
695
|
-
if (authPerms.ok) {
|
|
696
|
-
if (authPerms.worldWritable || authPerms.groupWritable) {
|
|
697
|
-
findings.push({
|
|
698
|
-
checkId: "fs.auth_profiles.perms_writable",
|
|
699
|
-
severity: "critical",
|
|
700
|
-
title: "auth-profiles.json is writable by others",
|
|
701
|
-
detail: `${formatPermissionDetail(authPath, authPerms)}; another user could inject credentials.`,
|
|
702
|
-
remediation: formatPermissionRemediation({
|
|
703
|
-
targetPath: authPath,
|
|
704
|
-
perms: authPerms,
|
|
705
|
-
isDir: false,
|
|
706
|
-
posixMode: 0o600,
|
|
707
|
-
env: params.env,
|
|
708
|
-
}),
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
else if (authPerms.worldReadable || authPerms.groupReadable) {
|
|
712
|
-
findings.push({
|
|
713
|
-
checkId: "fs.auth_profiles.perms_readable",
|
|
714
|
-
severity: "warn",
|
|
715
|
-
title: "auth-profiles.json is readable by others",
|
|
716
|
-
detail: `${formatPermissionDetail(authPath, authPerms)}; auth-profiles.json contains API keys and OAuth tokens.`,
|
|
717
|
-
remediation: formatPermissionRemediation({
|
|
718
|
-
targetPath: authPath,
|
|
719
|
-
perms: authPerms,
|
|
720
|
-
isDir: false,
|
|
721
|
-
posixMode: 0o600,
|
|
722
|
-
env: params.env,
|
|
723
|
-
}),
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
const storePath = path.join(params.stateDir, "agents", agentId, "sessions", "sessions.json");
|
|
728
|
-
// eslint-disable-next-line no-await-in-loop
|
|
729
|
-
const storePerms = await inspectPathPermissions(storePath, {
|
|
730
|
-
env: params.env,
|
|
731
|
-
platform: params.platform,
|
|
732
|
-
exec: params.execIcacls,
|
|
733
|
-
});
|
|
734
|
-
if (storePerms.ok) {
|
|
735
|
-
if (storePerms.worldReadable || storePerms.groupReadable) {
|
|
736
|
-
findings.push({
|
|
737
|
-
checkId: "fs.sessions_store.perms_readable",
|
|
738
|
-
severity: "warn",
|
|
739
|
-
title: "sessions.json is readable by others",
|
|
740
|
-
detail: `${formatPermissionDetail(storePath, storePerms)}; routing and transcript metadata can be sensitive.`,
|
|
741
|
-
remediation: formatPermissionRemediation({
|
|
742
|
-
targetPath: storePath,
|
|
743
|
-
perms: storePerms,
|
|
744
|
-
isDir: false,
|
|
745
|
-
posixMode: 0o600,
|
|
746
|
-
env: params.env,
|
|
747
|
-
}),
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
const logFile = typeof params.cfg.logging?.file === "string" ? params.cfg.logging.file.trim() : "";
|
|
753
|
-
if (logFile) {
|
|
754
|
-
const expanded = logFile.startsWith("~") ? expandTilde(logFile, params.env) : logFile;
|
|
755
|
-
if (expanded) {
|
|
756
|
-
const logPath = path.resolve(expanded);
|
|
757
|
-
const logPerms = await inspectPathPermissions(logPath, {
|
|
758
|
-
env: params.env,
|
|
759
|
-
platform: params.platform,
|
|
760
|
-
exec: params.execIcacls,
|
|
761
|
-
});
|
|
762
|
-
if (logPerms.ok) {
|
|
763
|
-
if (logPerms.worldReadable || logPerms.groupReadable) {
|
|
764
|
-
findings.push({
|
|
765
|
-
checkId: "fs.log_file.perms_readable",
|
|
766
|
-
severity: "warn",
|
|
767
|
-
title: "Log file is readable by others",
|
|
768
|
-
detail: `${formatPermissionDetail(logPath, logPerms)}; logs can contain private messages and tool output.`,
|
|
769
|
-
remediation: formatPermissionRemediation({
|
|
770
|
-
targetPath: logPath,
|
|
771
|
-
perms: logPerms,
|
|
772
|
-
isDir: false,
|
|
773
|
-
posixMode: 0o600,
|
|
774
|
-
env: params.env,
|
|
775
|
-
}),
|
|
776
|
-
});
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
return findings;
|
|
782
|
-
}
|
|
783
|
-
function listGroupPolicyOpen(cfg) {
|
|
784
|
-
const out = [];
|
|
785
|
-
const channels = cfg.channels;
|
|
786
|
-
if (!channels || typeof channels !== "object")
|
|
787
|
-
return out;
|
|
788
|
-
for (const [channelId, value] of Object.entries(channels)) {
|
|
789
|
-
if (!value || typeof value !== "object")
|
|
790
|
-
continue;
|
|
791
|
-
const section = value;
|
|
792
|
-
if (section.groupPolicy === "open")
|
|
793
|
-
out.push(`channels.${channelId}.groupPolicy`);
|
|
794
|
-
const accounts = section.accounts;
|
|
795
|
-
if (accounts && typeof accounts === "object") {
|
|
796
|
-
for (const [accountId, accountVal] of Object.entries(accounts)) {
|
|
797
|
-
if (!accountVal || typeof accountVal !== "object")
|
|
798
|
-
continue;
|
|
799
|
-
const acc = accountVal;
|
|
800
|
-
if (acc.groupPolicy === "open")
|
|
801
|
-
out.push(`channels.${channelId}.accounts.${accountId}.groupPolicy`);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
return out;
|
|
806
|
-
}
|
|
807
|
-
export function collectExposureMatrixFindings(cfg) {
|
|
808
|
-
const findings = [];
|
|
809
|
-
const openGroups = listGroupPolicyOpen(cfg);
|
|
810
|
-
if (openGroups.length === 0)
|
|
811
|
-
return findings;
|
|
812
|
-
const elevatedEnabled = cfg.tools?.elevated?.enabled !== false;
|
|
813
|
-
if (elevatedEnabled) {
|
|
814
|
-
findings.push({
|
|
815
|
-
checkId: "security.exposure.open_groups_with_elevated",
|
|
816
|
-
severity: "critical",
|
|
817
|
-
title: "Open groupPolicy with elevated tools enabled",
|
|
818
|
-
detail: `Found groupPolicy="open" at:\n${openGroups.map((p) => `- ${p}`).join("\n")}\n` +
|
|
819
|
-
"With tools.elevated enabled, a prompt injection in those rooms can become a high-impact incident.",
|
|
820
|
-
remediation: `Set groupPolicy="allowlist" and keep elevated allowlists extremely tight.`,
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
return findings;
|
|
824
|
-
}
|
|
825
|
-
export async function readConfigSnapshotForAudit(params) {
|
|
826
|
-
return await createConfigIO({
|
|
827
|
-
env: params.env,
|
|
828
|
-
configPath: params.configPath,
|
|
829
|
-
}).readConfigFileSnapshot();
|
|
830
|
-
}
|
|
1
|
+
export { collectAttackSurfaceSummaryFindings, collectExposureMatrixFindings, collectHooksHardeningFindings, collectModelHygieneFindings, collectSecretsInConfigFindings, collectSmallModelRiskFindings, collectSyncedFolderFindings, } from "./audit-extra.sync.js";
|
|
2
|
+
export { collectIncludeFilePermFindings, collectInstalledSkillsCodeSafetyFindings, collectPluginsCodeSafetyFindings, collectPluginsTrustFindings, collectStateDeepFilesystemFindings, readConfigSnapshotForAudit, } from "./audit-extra.async.js";
|