@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
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readChannelAllowFromStore } from "../pairing/pairing-store.js";
|
|
2
|
+
import { normalizeStringEntries } from "../shared/string-normalization.js";
|
|
3
|
+
export async function resolveDmAllowState(params) {
|
|
4
|
+
const configAllowFrom = normalizeStringEntries(Array.isArray(params.allowFrom) ? params.allowFrom : undefined);
|
|
5
|
+
const hasWildcard = configAllowFrom.includes("*");
|
|
6
|
+
const storeAllowFrom = await (params.readStore ?? readChannelAllowFromStore)(params.provider).catch(() => []);
|
|
7
|
+
const normalizeEntry = params.normalizeEntry ?? ((value) => value);
|
|
8
|
+
const normalizedCfg = configAllowFrom
|
|
9
|
+
.filter((value) => value !== "*")
|
|
10
|
+
.map((value) => normalizeEntry(value))
|
|
11
|
+
.map((value) => value.trim())
|
|
12
|
+
.filter(Boolean);
|
|
13
|
+
const normalizedStore = storeAllowFrom
|
|
14
|
+
.map((value) => normalizeEntry(value))
|
|
15
|
+
.map((value) => value.trim())
|
|
16
|
+
.filter(Boolean);
|
|
17
|
+
const allowCount = Array.from(new Set([...normalizedCfg, ...normalizedStore])).length;
|
|
18
|
+
return {
|
|
19
|
+
configAllowFrom,
|
|
20
|
+
hasWildcard,
|
|
21
|
+
allowCount,
|
|
22
|
+
isMultiUserDm: hasWildcard || allowCount > 1,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -62,14 +62,28 @@ const EXTERNAL_SOURCE_LABELS = {
|
|
|
62
62
|
email: "Email",
|
|
63
63
|
webhook: "Webhook",
|
|
64
64
|
api: "API",
|
|
65
|
+
browser: "Browser",
|
|
65
66
|
channel_metadata: "Channel metadata",
|
|
66
67
|
web_search: "Web Search",
|
|
67
68
|
web_fetch: "Web Fetch",
|
|
68
69
|
unknown: "External",
|
|
69
70
|
};
|
|
70
71
|
const FULLWIDTH_ASCII_OFFSET = 0xfee0;
|
|
71
|
-
|
|
72
|
-
const
|
|
72
|
+
// Map of Unicode angle bracket homoglyphs to their ASCII equivalents.
|
|
73
|
+
const ANGLE_BRACKET_MAP = {
|
|
74
|
+
0xff1c: "<", // fullwidth <
|
|
75
|
+
0xff1e: ">", // fullwidth >
|
|
76
|
+
0x2329: "<", // left-pointing angle bracket
|
|
77
|
+
0x232a: ">", // right-pointing angle bracket
|
|
78
|
+
0x3008: "<", // CJK left angle bracket
|
|
79
|
+
0x3009: ">", // CJK right angle bracket
|
|
80
|
+
0x2039: "<", // single left-pointing angle quotation mark
|
|
81
|
+
0x203a: ">", // single right-pointing angle quotation mark
|
|
82
|
+
0x27e8: "<", // mathematical left angle bracket
|
|
83
|
+
0x27e9: ">", // mathematical right angle bracket
|
|
84
|
+
0xfe64: "<", // small less-than sign
|
|
85
|
+
0xfe65: ">", // small greater-than sign
|
|
86
|
+
};
|
|
73
87
|
function foldMarkerChar(char) {
|
|
74
88
|
const code = char.charCodeAt(0);
|
|
75
89
|
if (code >= 0xff21 && code <= 0xff3a) {
|
|
@@ -78,16 +92,14 @@ function foldMarkerChar(char) {
|
|
|
78
92
|
if (code >= 0xff41 && code <= 0xff5a) {
|
|
79
93
|
return String.fromCharCode(code - FULLWIDTH_ASCII_OFFSET);
|
|
80
94
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (code === FULLWIDTH_RIGHT_ANGLE) {
|
|
85
|
-
return ">";
|
|
95
|
+
const bracket = ANGLE_BRACKET_MAP[code];
|
|
96
|
+
if (bracket) {
|
|
97
|
+
return bracket;
|
|
86
98
|
}
|
|
87
99
|
return char;
|
|
88
100
|
}
|
|
89
101
|
function foldMarkerText(input) {
|
|
90
|
-
return input.replace(/[\uFF21-\uFF3A\uFF41-\uFF5A\uFF1C\uFF1E]/g, (char) => foldMarkerChar(char));
|
|
102
|
+
return input.replace(/[\uFF21-\uFF3A\uFF41-\uFF5A\uFF1C\uFF1E\u2329\u232A\u3008\u3009\u2039\u203A\u27E8\u27E9\uFE64\uFE65]/g, (char) => foldMarkerChar(char));
|
|
91
103
|
}
|
|
92
104
|
function replaceMarkers(content) {
|
|
93
105
|
const folded = foldMarkerText(content);
|
package/dist/security/fix.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import
|
|
3
|
+
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
4
4
|
import { createConfigIO } from "../config/config.js";
|
|
5
|
+
import { collectIncludePathsRecursive } from "../config/includes-scan.js";
|
|
5
6
|
import { resolveConfigPath, resolveOAuthDir, resolveStateDir } from "../config/paths.js";
|
|
6
|
-
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
7
|
-
import { INCLUDE_KEY, MAX_INCLUDE_DEPTH } from "../config/includes.js";
|
|
8
|
-
import { normalizeAgentId } from "../routing/session-key.js";
|
|
9
7
|
import { readChannelAllowFromStore } from "../pairing/pairing-store.js";
|
|
10
8
|
import { runExec } from "../process/exec.js";
|
|
9
|
+
import { normalizeAgentId } from "../routing/session-key.js";
|
|
11
10
|
import { createIcaclsResetCommand, formatIcaclsResetCommand } from "./windows-acl.js";
|
|
12
11
|
async function safeChmod(params) {
|
|
13
12
|
try {
|
|
@@ -144,11 +143,13 @@ async function safeAclReset(params) {
|
|
|
144
143
|
}
|
|
145
144
|
}
|
|
146
145
|
function setGroupPolicyAllowlist(params) {
|
|
147
|
-
if (!params.cfg.channels)
|
|
146
|
+
if (!params.cfg.channels) {
|
|
148
147
|
return;
|
|
148
|
+
}
|
|
149
149
|
const section = params.cfg.channels[params.channel];
|
|
150
|
-
if (!section || typeof section !== "object")
|
|
150
|
+
if (!section || typeof section !== "object") {
|
|
151
151
|
return;
|
|
152
|
+
}
|
|
152
153
|
const topPolicy = section.groupPolicy;
|
|
153
154
|
if (topPolicy === "open") {
|
|
154
155
|
section.groupPolicy = "allowlist";
|
|
@@ -156,13 +157,16 @@ function setGroupPolicyAllowlist(params) {
|
|
|
156
157
|
params.policyFlips.add(`channels.${params.channel}.`);
|
|
157
158
|
}
|
|
158
159
|
const accounts = section.accounts;
|
|
159
|
-
if (!accounts || typeof accounts !== "object")
|
|
160
|
+
if (!accounts || typeof accounts !== "object") {
|
|
160
161
|
return;
|
|
162
|
+
}
|
|
161
163
|
for (const [accountId, accountValue] of Object.entries(accounts)) {
|
|
162
|
-
if (!accountId)
|
|
164
|
+
if (!accountId) {
|
|
163
165
|
continue;
|
|
164
|
-
|
|
166
|
+
}
|
|
167
|
+
if (!accountValue || typeof accountValue !== "object") {
|
|
165
168
|
continue;
|
|
169
|
+
}
|
|
166
170
|
const account = accountValue;
|
|
167
171
|
if (account.groupPolicy === "open") {
|
|
168
172
|
account.groupPolicy = "allowlist";
|
|
@@ -173,29 +177,36 @@ function setGroupPolicyAllowlist(params) {
|
|
|
173
177
|
}
|
|
174
178
|
function setWhatsAppGroupAllowFromFromStore(params) {
|
|
175
179
|
const section = params.cfg.channels?.whatsapp;
|
|
176
|
-
if (!section || typeof section !== "object")
|
|
180
|
+
if (!section || typeof section !== "object") {
|
|
177
181
|
return;
|
|
178
|
-
|
|
182
|
+
}
|
|
183
|
+
if (params.storeAllowFrom.length === 0) {
|
|
179
184
|
return;
|
|
185
|
+
}
|
|
180
186
|
const maybeApply = (prefix, obj) => {
|
|
181
|
-
if (!params.policyFlips.has(prefix))
|
|
187
|
+
if (!params.policyFlips.has(prefix)) {
|
|
182
188
|
return;
|
|
189
|
+
}
|
|
183
190
|
const allowFrom = Array.isArray(obj.allowFrom) ? obj.allowFrom : [];
|
|
184
191
|
const groupAllowFrom = Array.isArray(obj.groupAllowFrom) ? obj.groupAllowFrom : [];
|
|
185
|
-
if (allowFrom.length > 0)
|
|
192
|
+
if (allowFrom.length > 0) {
|
|
186
193
|
return;
|
|
187
|
-
|
|
194
|
+
}
|
|
195
|
+
if (groupAllowFrom.length > 0) {
|
|
188
196
|
return;
|
|
197
|
+
}
|
|
189
198
|
obj.groupAllowFrom = params.storeAllowFrom;
|
|
190
199
|
params.changes.push(`${prefix}groupAllowFrom=pairing-store`);
|
|
191
200
|
};
|
|
192
201
|
maybeApply("channels.whatsapp.", section);
|
|
193
202
|
const accounts = section.accounts;
|
|
194
|
-
if (!accounts || typeof accounts !== "object")
|
|
203
|
+
if (!accounts || typeof accounts !== "object") {
|
|
195
204
|
return;
|
|
205
|
+
}
|
|
196
206
|
for (const [accountId, accountValue] of Object.entries(accounts)) {
|
|
197
|
-
if (!accountValue || typeof accountValue !== "object")
|
|
207
|
+
if (!accountValue || typeof accountValue !== "object") {
|
|
198
208
|
continue;
|
|
209
|
+
}
|
|
199
210
|
const account = accountValue;
|
|
200
211
|
maybeApply(`channels.whatsapp.accounts.${accountId}.`, account);
|
|
201
212
|
}
|
|
@@ -221,80 +232,17 @@ function applyConfigFixes(params) {
|
|
|
221
232
|
}
|
|
222
233
|
return { cfg: next, changes, policyFlips };
|
|
223
234
|
}
|
|
224
|
-
function listDirectIncludes(parsed) {
|
|
225
|
-
const out = [];
|
|
226
|
-
const visit = (value) => {
|
|
227
|
-
if (!value)
|
|
228
|
-
return;
|
|
229
|
-
if (Array.isArray(value)) {
|
|
230
|
-
for (const item of value)
|
|
231
|
-
visit(item);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
if (typeof value !== "object")
|
|
235
|
-
return;
|
|
236
|
-
const rec = value;
|
|
237
|
-
const includeVal = rec[INCLUDE_KEY];
|
|
238
|
-
if (typeof includeVal === "string")
|
|
239
|
-
out.push(includeVal);
|
|
240
|
-
else if (Array.isArray(includeVal)) {
|
|
241
|
-
for (const item of includeVal) {
|
|
242
|
-
if (typeof item === "string")
|
|
243
|
-
out.push(item);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
for (const v of Object.values(rec))
|
|
247
|
-
visit(v);
|
|
248
|
-
};
|
|
249
|
-
visit(parsed);
|
|
250
|
-
return out;
|
|
251
|
-
}
|
|
252
|
-
function resolveIncludePath(baseConfigPath, includePath) {
|
|
253
|
-
return path.normalize(path.isAbsolute(includePath)
|
|
254
|
-
? includePath
|
|
255
|
-
: path.resolve(path.dirname(baseConfigPath), includePath));
|
|
256
|
-
}
|
|
257
|
-
async function collectIncludePathsRecursive(params) {
|
|
258
|
-
const visited = new Set();
|
|
259
|
-
const result = [];
|
|
260
|
-
const walk = async (basePath, parsed, depth) => {
|
|
261
|
-
if (depth > MAX_INCLUDE_DEPTH)
|
|
262
|
-
return;
|
|
263
|
-
for (const raw of listDirectIncludes(parsed)) {
|
|
264
|
-
const resolved = resolveIncludePath(basePath, raw);
|
|
265
|
-
if (visited.has(resolved))
|
|
266
|
-
continue;
|
|
267
|
-
visited.add(resolved);
|
|
268
|
-
result.push(resolved);
|
|
269
|
-
const rawText = await fs.readFile(resolved, "utf-8").catch(() => null);
|
|
270
|
-
if (!rawText)
|
|
271
|
-
continue;
|
|
272
|
-
const nestedParsed = (() => {
|
|
273
|
-
try {
|
|
274
|
-
return JSON5.parse(rawText);
|
|
275
|
-
}
|
|
276
|
-
catch {
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
})();
|
|
280
|
-
if (nestedParsed) {
|
|
281
|
-
// eslint-disable-next-line no-await-in-loop
|
|
282
|
-
await walk(resolved, nestedParsed, depth + 1);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
await walk(params.configPath, params.parsed, 0);
|
|
287
|
-
return result;
|
|
288
|
-
}
|
|
289
235
|
async function chmodCredentialsAndAgentState(params) {
|
|
290
236
|
const credsDir = resolveOAuthDir(params.env, params.stateDir);
|
|
291
237
|
params.actions.push(await safeChmod({ path: credsDir, mode: 0o700, require: "dir" }));
|
|
292
238
|
const credsEntries = await fs.readdir(credsDir, { withFileTypes: true }).catch(() => []);
|
|
293
239
|
for (const entry of credsEntries) {
|
|
294
|
-
if (!entry.isFile())
|
|
240
|
+
if (!entry.isFile()) {
|
|
295
241
|
continue;
|
|
296
|
-
|
|
242
|
+
}
|
|
243
|
+
if (!entry.name.endsWith(".json")) {
|
|
297
244
|
continue;
|
|
245
|
+
}
|
|
298
246
|
const p = path.join(credsDir, entry.name);
|
|
299
247
|
// eslint-disable-next-line no-await-in-loop
|
|
300
248
|
params.actions.push(await safeChmod({ path: p, mode: 0o600, require: "file" }));
|
|
@@ -303,11 +251,13 @@ async function chmodCredentialsAndAgentState(params) {
|
|
|
303
251
|
ids.add(resolveDefaultAgentId(params.cfg));
|
|
304
252
|
const list = Array.isArray(params.cfg.agents?.list) ? params.cfg.agents?.list : [];
|
|
305
253
|
for (const agent of list ?? []) {
|
|
306
|
-
if (!agent || typeof agent !== "object")
|
|
254
|
+
if (!agent || typeof agent !== "object") {
|
|
307
255
|
continue;
|
|
256
|
+
}
|
|
308
257
|
const id = typeof agent.id === "string" ? agent.id.trim() : "";
|
|
309
|
-
if (id)
|
|
258
|
+
if (id) {
|
|
310
259
|
ids.add(id);
|
|
260
|
+
}
|
|
311
261
|
}
|
|
312
262
|
for (const agentId of ids) {
|
|
313
263
|
const normalizedAgentId = normalizeAgentId(agentId);
|
|
@@ -326,6 +276,20 @@ async function chmodCredentialsAndAgentState(params) {
|
|
|
326
276
|
const storePath = path.join(sessionsDir, "sessions.json");
|
|
327
277
|
// eslint-disable-next-line no-await-in-loop
|
|
328
278
|
params.actions.push(await params.applyPerms({ path: storePath, mode: 0o600, require: "file" }));
|
|
279
|
+
// Fix permissions on session transcript files (*.jsonl)
|
|
280
|
+
// eslint-disable-next-line no-await-in-loop
|
|
281
|
+
const sessionEntries = await fs.readdir(sessionsDir, { withFileTypes: true }).catch(() => []);
|
|
282
|
+
for (const entry of sessionEntries) {
|
|
283
|
+
if (!entry.isFile()) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (!entry.name.endsWith(".jsonl")) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const p = path.join(sessionsDir, entry.name);
|
|
290
|
+
// eslint-disable-next-line no-await-in-loop
|
|
291
|
+
params.actions.push(await params.applyPerms({ path: p, mode: 0o600, require: "file" }));
|
|
292
|
+
}
|
|
329
293
|
}
|
|
330
294
|
}
|
|
331
295
|
export async function fixSecurityFootguns(opts) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
export function isPathInside(basePath, candidatePath) {
|
|
3
4
|
const base = path.resolve(basePath);
|
|
@@ -5,6 +6,25 @@ export function isPathInside(basePath, candidatePath) {
|
|
|
5
6
|
const rel = path.relative(base, candidate);
|
|
6
7
|
return rel === "" || (!rel.startsWith(`..${path.sep}`) && rel !== ".." && !path.isAbsolute(rel));
|
|
7
8
|
}
|
|
9
|
+
function safeRealpathSync(filePath) {
|
|
10
|
+
try {
|
|
11
|
+
return fs.realpathSync(filePath);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function isPathInsideWithRealpath(basePath, candidatePath, opts) {
|
|
18
|
+
if (!isPathInside(basePath, candidatePath)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const baseReal = safeRealpathSync(basePath);
|
|
22
|
+
const candidateReal = safeRealpathSync(candidatePath);
|
|
23
|
+
if (!baseReal || !candidateReal) {
|
|
24
|
+
return opts?.requireRealpath !== true;
|
|
25
|
+
}
|
|
26
|
+
return isPathInside(baseReal, candidateReal);
|
|
27
|
+
}
|
|
8
28
|
export function extensionUsesSkippedScannerPath(entry) {
|
|
9
29
|
const segments = entry.split(/[\\/]+/).filter(Boolean);
|
|
10
30
|
return segments.some((segment) => segment === "node_modules" ||
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import { timingSafeEqual } from "node:crypto";
|
|
1
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
2
|
export function safeEqualSecret(provided, expected) {
|
|
3
3
|
if (typeof provided !== "string" || typeof expected !== "string") {
|
|
4
4
|
return false;
|
|
5
5
|
}
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
if (providedBuffer.length !== expectedBuffer.length) {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
return timingSafeEqual(providedBuffer, expectedBuffer);
|
|
6
|
+
const hash = (s) => createHash("sha256").update(s).digest();
|
|
7
|
+
return timingSafeEqual(hash(provided), hash(expected));
|
|
12
8
|
}
|
|
@@ -19,8 +19,9 @@ const TRUSTED_SUFFIXES = ["\\administrators", "\\system"];
|
|
|
19
19
|
const normalize = (value) => value.trim().toLowerCase();
|
|
20
20
|
export function resolveWindowsUserPrincipal(env) {
|
|
21
21
|
const username = env?.USERNAME?.trim() || os.userInfo().username?.trim();
|
|
22
|
-
if (!username)
|
|
22
|
+
if (!username) {
|
|
23
23
|
return null;
|
|
24
|
+
}
|
|
24
25
|
const domain = env?.USERDOMAIN?.trim();
|
|
25
26
|
return domain ? `${domain}\\${username}` : username;
|
|
26
27
|
}
|
|
@@ -31,18 +32,21 @@ function buildTrustedPrincipals(env) {
|
|
|
31
32
|
trusted.add(normalize(principal));
|
|
32
33
|
const parts = principal.split("\\");
|
|
33
34
|
const userOnly = parts.at(-1);
|
|
34
|
-
if (userOnly)
|
|
35
|
+
if (userOnly) {
|
|
35
36
|
trusted.add(normalize(userOnly));
|
|
37
|
+
}
|
|
36
38
|
}
|
|
37
39
|
return trusted;
|
|
38
40
|
}
|
|
39
41
|
function classifyPrincipal(principal, env) {
|
|
40
42
|
const normalized = normalize(principal);
|
|
41
43
|
const trusted = buildTrustedPrincipals(env);
|
|
42
|
-
if (trusted.has(normalized) || TRUSTED_SUFFIXES.some((s) => normalized.endsWith(s)))
|
|
44
|
+
if (trusted.has(normalized) || TRUSTED_SUFFIXES.some((s) => normalized.endsWith(s))) {
|
|
43
45
|
return "trusted";
|
|
44
|
-
|
|
46
|
+
}
|
|
47
|
+
if (WORLD_PRINCIPALS.has(normalized) || WORLD_SUFFIXES.some((s) => normalized.endsWith(s))) {
|
|
45
48
|
return "world";
|
|
49
|
+
}
|
|
46
50
|
return "group";
|
|
47
51
|
}
|
|
48
52
|
function rightsFromTokens(tokens) {
|
|
@@ -59,8 +63,9 @@ export function parseIcaclsOutput(output, targetPath) {
|
|
|
59
63
|
const quotedLower = quotedTarget.toLowerCase();
|
|
60
64
|
for (const rawLine of output.split(/\r?\n/)) {
|
|
61
65
|
const line = rawLine.trimEnd();
|
|
62
|
-
if (!line.trim())
|
|
66
|
+
if (!line.trim()) {
|
|
63
67
|
continue;
|
|
68
|
+
}
|
|
64
69
|
const trimmed = line.trim();
|
|
65
70
|
const lower = trimmed.toLowerCase();
|
|
66
71
|
if (lower.startsWith("successfully processed") ||
|
|
@@ -76,22 +81,26 @@ export function parseIcaclsOutput(output, targetPath) {
|
|
|
76
81
|
else if (lower.startsWith(quotedLower)) {
|
|
77
82
|
entry = trimmed.slice(quotedTarget.length).trim();
|
|
78
83
|
}
|
|
79
|
-
if (!entry)
|
|
84
|
+
if (!entry) {
|
|
80
85
|
continue;
|
|
86
|
+
}
|
|
81
87
|
const idx = entry.indexOf(":");
|
|
82
|
-
if (idx === -1)
|
|
88
|
+
if (idx === -1) {
|
|
83
89
|
continue;
|
|
90
|
+
}
|
|
84
91
|
const principal = entry.slice(0, idx).trim();
|
|
85
92
|
const rawRights = entry.slice(idx + 1).trim();
|
|
86
93
|
const tokens = rawRights
|
|
87
94
|
.match(/\(([^)]+)\)/g)
|
|
88
95
|
?.map((token) => token.slice(1, -1).trim())
|
|
89
96
|
.filter(Boolean) ?? [];
|
|
90
|
-
if (tokens.some((token) => token.toUpperCase() === "DENY"))
|
|
97
|
+
if (tokens.some((token) => token.toUpperCase() === "DENY")) {
|
|
91
98
|
continue;
|
|
99
|
+
}
|
|
92
100
|
const rights = tokens.filter((token) => !INHERIT_FLAGS.has(token.toUpperCase()));
|
|
93
|
-
if (rights.length === 0)
|
|
101
|
+
if (rights.length === 0) {
|
|
94
102
|
continue;
|
|
103
|
+
}
|
|
95
104
|
const { canRead, canWrite } = rightsFromTokens(rights);
|
|
96
105
|
entries.push({ principal, rights, rawRights, canRead, canWrite });
|
|
97
106
|
}
|
|
@@ -103,12 +112,15 @@ export function summarizeWindowsAcl(entries, env) {
|
|
|
103
112
|
const untrustedGroup = [];
|
|
104
113
|
for (const entry of entries) {
|
|
105
114
|
const classification = classifyPrincipal(entry.principal, env);
|
|
106
|
-
if (classification === "trusted")
|
|
115
|
+
if (classification === "trusted") {
|
|
107
116
|
trusted.push(entry);
|
|
108
|
-
|
|
117
|
+
}
|
|
118
|
+
else if (classification === "world") {
|
|
109
119
|
untrustedWorld.push(entry);
|
|
110
|
-
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
111
122
|
untrustedGroup.push(entry);
|
|
123
|
+
}
|
|
112
124
|
}
|
|
113
125
|
return { trusted, untrustedWorld, untrustedGroup };
|
|
114
126
|
}
|
|
@@ -133,11 +145,13 @@ export async function inspectWindowsAcl(targetPath, opts) {
|
|
|
133
145
|
}
|
|
134
146
|
}
|
|
135
147
|
export function formatWindowsAclSummary(summary) {
|
|
136
|
-
if (!summary.ok)
|
|
148
|
+
if (!summary.ok) {
|
|
137
149
|
return "unknown";
|
|
150
|
+
}
|
|
138
151
|
const untrusted = [...summary.untrustedWorld, ...summary.untrustedGroup];
|
|
139
|
-
if (untrusted.length === 0)
|
|
152
|
+
if (untrusted.length === 0) {
|
|
140
153
|
return "trusted-only";
|
|
154
|
+
}
|
|
141
155
|
return untrusted.map((entry) => `${entry.principal}:${entry.rawRights}`).join(", ");
|
|
142
156
|
}
|
|
143
157
|
export function formatIcaclsResetCommand(targetPath, opts) {
|
|
@@ -147,8 +161,9 @@ export function formatIcaclsResetCommand(targetPath, opts) {
|
|
|
147
161
|
}
|
|
148
162
|
export function createIcaclsResetCommand(targetPath, opts) {
|
|
149
163
|
const user = resolveWindowsUserPrincipal(opts.env);
|
|
150
|
-
if (!user)
|
|
164
|
+
if (!user) {
|
|
151
165
|
return null;
|
|
166
|
+
}
|
|
152
167
|
const grant = opts.isDir ? "(OI)(CI)F" : "F";
|
|
153
168
|
const args = [
|
|
154
169
|
targetPath,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function asRecord(value) {
|
|
2
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
3
|
+
}
|
|
4
|
+
export function parsePairingList(value) {
|
|
5
|
+
const obj = asRecord(value);
|
|
6
|
+
const pending = Array.isArray(obj.pending) ? obj.pending : [];
|
|
7
|
+
const paired = Array.isArray(obj.paired) ? obj.paired : [];
|
|
8
|
+
return { pending, paired };
|
|
9
|
+
}
|
|
10
|
+
export function parseNodeList(value) {
|
|
11
|
+
const obj = asRecord(value);
|
|
12
|
+
return Array.isArray(obj.nodes) ? obj.nodes : [];
|
|
13
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const OPERATOR_ROLE = "operator";
|
|
2
|
+
const OPERATOR_ADMIN_SCOPE = "operator.admin";
|
|
3
|
+
const OPERATOR_READ_SCOPE = "operator.read";
|
|
4
|
+
const OPERATOR_WRITE_SCOPE = "operator.write";
|
|
5
|
+
function normalizeScopeList(scopes) {
|
|
6
|
+
const out = new Set();
|
|
7
|
+
for (const scope of scopes) {
|
|
8
|
+
const trimmed = scope.trim();
|
|
9
|
+
if (trimmed) {
|
|
10
|
+
out.add(trimmed);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return [...out];
|
|
14
|
+
}
|
|
15
|
+
function operatorScopeSatisfied(requestedScope, granted) {
|
|
16
|
+
if (requestedScope === OPERATOR_READ_SCOPE) {
|
|
17
|
+
return (granted.has(OPERATOR_READ_SCOPE) ||
|
|
18
|
+
granted.has(OPERATOR_WRITE_SCOPE) ||
|
|
19
|
+
granted.has(OPERATOR_ADMIN_SCOPE));
|
|
20
|
+
}
|
|
21
|
+
return granted.has(requestedScope);
|
|
22
|
+
}
|
|
23
|
+
export function roleScopesAllow(params) {
|
|
24
|
+
const requested = normalizeScopeList(params.requestedScopes);
|
|
25
|
+
if (requested.length === 0) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
const allowed = normalizeScopeList(params.allowedScopes);
|
|
29
|
+
if (allowed.length === 0) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const allowedSet = new Set(allowed);
|
|
33
|
+
if (params.role.trim() !== OPERATOR_ROLE) {
|
|
34
|
+
return requested.every((scope) => allowedSet.has(scope));
|
|
35
|
+
}
|
|
36
|
+
return requested.every((scope) => operatorScopeSatisfied(scope, allowedSet));
|
|
37
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function chunkTextByBreakResolver(text, limit, resolveBreakIndex) {
|
|
2
|
+
if (!text) {
|
|
3
|
+
return [];
|
|
4
|
+
}
|
|
5
|
+
if (limit <= 0 || text.length <= limit) {
|
|
6
|
+
return [text];
|
|
7
|
+
}
|
|
8
|
+
const chunks = [];
|
|
9
|
+
let remaining = text;
|
|
10
|
+
while (remaining.length > limit) {
|
|
11
|
+
const window = remaining.slice(0, limit);
|
|
12
|
+
const candidateBreak = resolveBreakIndex(window);
|
|
13
|
+
const breakIdx = Number.isFinite(candidateBreak) && candidateBreak > 0 && candidateBreak <= limit
|
|
14
|
+
? candidateBreak
|
|
15
|
+
: limit;
|
|
16
|
+
const rawChunk = remaining.slice(0, breakIdx);
|
|
17
|
+
const chunk = rawChunk.trimEnd();
|
|
18
|
+
if (chunk.length > 0) {
|
|
19
|
+
chunks.push(chunk);
|
|
20
|
+
}
|
|
21
|
+
const brokeOnSeparator = breakIdx < remaining.length && /\s/.test(remaining[breakIdx]);
|
|
22
|
+
const nextStart = Math.min(remaining.length, breakIdx + (brokeOnSeparator ? 1 : 0));
|
|
23
|
+
remaining = remaining.slice(nextStart).trimStart();
|
|
24
|
+
}
|
|
25
|
+
if (remaining.length) {
|
|
26
|
+
chunks.push(remaining);
|
|
27
|
+
}
|
|
28
|
+
return chunks;
|
|
29
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
export function installSlackBlockTestMocks() {
|
|
3
|
+
vi.mock("../config/config.js", () => ({
|
|
4
|
+
loadConfig: () => ({}),
|
|
5
|
+
}));
|
|
6
|
+
vi.mock("./accounts.js", () => ({
|
|
7
|
+
resolveSlackAccount: () => ({
|
|
8
|
+
accountId: "default",
|
|
9
|
+
botToken: "xoxb-test",
|
|
10
|
+
botTokenSource: "config",
|
|
11
|
+
config: {},
|
|
12
|
+
}),
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
export function createSlackEditTestClient() {
|
|
16
|
+
return {
|
|
17
|
+
chat: {
|
|
18
|
+
update: vi.fn(async () => ({ ok: true })),
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function createSlackSendTestClient() {
|
|
23
|
+
return {
|
|
24
|
+
conversations: {
|
|
25
|
+
open: vi.fn(async () => ({ channel: { id: "D123" } })),
|
|
26
|
+
},
|
|
27
|
+
chat: {
|
|
28
|
+
postMessage: vi.fn(async () => ({ ts: "171234.567" })),
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|