@poolzin/pool-bot 2026.2.21 → 2026.2.23
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 +25 -0
- package/dist/agents/api-key-rotation.js +47 -0
- package/dist/agents/apply-patch-update.js +19 -9
- package/dist/agents/apply-patch.js +72 -47
- package/dist/agents/bash-tools.exec.js +141 -559
- package/dist/agents/cli-backends.js +49 -6
- package/dist/agents/cli-runner/helpers.js +69 -152
- package/dist/agents/cli-runner.js +70 -19
- package/dist/agents/identity.js +20 -1
- package/dist/agents/image-sanitization.js +9 -0
- package/dist/agents/live-auth-keys.js +123 -26
- package/dist/agents/live-model-filter.js +13 -4
- package/dist/agents/model-catalog.js +40 -9
- package/dist/agents/model-forward-compat.js +60 -23
- package/dist/agents/model-selection.js +134 -41
- package/dist/agents/pi-auth-json.js +2 -2
- package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
- package/dist/agents/pi-embedded-helpers/errors.js +140 -15
- package/dist/agents/pi-embedded-helpers/images.js +22 -12
- package/dist/agents/pi-embedded-helpers.js +2 -2
- package/dist/agents/pi-embedded-runner/abort.js +10 -3
- package/dist/agents/pi-embedded-runner/compact.js +230 -32
- package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
- package/dist/agents/pi-embedded-runner/google.js +109 -19
- package/dist/agents/pi-embedded-runner/history.js +35 -17
- package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
- package/dist/agents/pi-embedded-runner/run/images.js +81 -55
- package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
- package/dist/agents/pi-embedded-runner/run.js +193 -25
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
- package/dist/agents/pi-embedded-runner/runs.js +17 -8
- package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
- package/dist/agents/pi-embedded-runner.js +1 -1
- package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
- package/dist/agents/pi-embedded-subscribe.js +37 -0
- package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
- package/dist/agents/pi-model-discovery.js +9 -2
- package/dist/agents/pi-tool-definition-adapter.js +60 -8
- package/dist/agents/pi-tools.before-tool-call.js +1 -1
- package/dist/agents/pi-tools.js +113 -94
- package/dist/agents/pi-tools.read.js +337 -38
- package/dist/agents/poolbot-tools.js +14 -5
- package/dist/agents/sandbox/docker.js +10 -5
- package/dist/agents/sandbox/registry.js +96 -46
- package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
- package/dist/agents/sandbox-paths.js +43 -10
- package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
- package/dist/agents/session-tool-result-guard.js +39 -39
- package/dist/agents/session-transcript-repair.js +36 -33
- package/dist/agents/session-write-lock.js +62 -44
- package/dist/agents/skills/frontmatter.js +49 -88
- package/dist/agents/skills/workspace.js +335 -28
- package/dist/agents/subagent-announce.js +508 -174
- package/dist/agents/subagent-registry.js +45 -4
- package/dist/agents/subagent-spawn.js +16 -33
- package/dist/agents/system-prompt-report.js +27 -10
- package/dist/agents/system-prompt.js +26 -32
- package/dist/agents/tool-call-id.js +69 -17
- package/dist/agents/tool-display-common.js +1 -1
- package/dist/agents/tool-images.js +64 -31
- package/dist/agents/tools/canvas-tool.js +17 -11
- package/dist/agents/tools/common.js +37 -19
- package/dist/agents/tools/cron-tool.js +40 -38
- package/dist/agents/tools/gateway.js +70 -2
- package/dist/agents/tools/message-tool.js +181 -40
- package/dist/agents/tools/nodes-tool.js +128 -36
- package/dist/agents/tools/nodes-utils.js +12 -38
- package/dist/agents/tools/session-status-tool.js +24 -71
- package/dist/agents/tools/sessions-helpers.js +38 -210
- package/dist/agents/tools/sessions-spawn-tool.js +28 -198
- package/dist/agents/tools/telegram-actions.js +58 -7
- package/dist/agents/tools/web-fetch-utils.js +112 -7
- package/dist/agents/tools/web-fetch.js +279 -175
- package/dist/agents/tools/web-shared.js +71 -8
- package/dist/agents/usage.js +25 -16
- package/dist/auto-reply/commands-registry.data.js +85 -11
- package/dist/auto-reply/dispatch.js +40 -21
- package/dist/auto-reply/reply/abort.js +102 -33
- package/dist/auto-reply/reply/commands-core.js +82 -33
- package/dist/auto-reply/reply/commands-export-session.js +1 -1
- package/dist/auto-reply/reply/commands-info.js +41 -12
- package/dist/auto-reply/reply/commands-subagents.js +352 -100
- package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
- package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
- package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
- package/dist/auto-reply/reply/inbound-meta.js +12 -1
- package/dist/auto-reply/reply/mentions.js +18 -11
- package/dist/auto-reply/reply/normalize-reply.js +17 -8
- package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
- package/dist/auto-reply/reply/session.js +102 -21
- package/dist/auto-reply/reply/streaming-directives.js +16 -5
- package/dist/auto-reply/status.js +73 -50
- package/dist/browser/extension-relay.js +3 -3
- package/dist/browser/http-auth.js +1 -1
- package/dist/browser/paths.js +2 -2
- package/dist/build-info.json +3 -3
- package/dist/channels/allowlist-match.js +20 -0
- package/dist/channels/allowlists/resolve-utils.js +65 -2
- package/dist/channels/chat-type.js +8 -4
- package/dist/channels/dock.js +127 -35
- package/dist/channels/draft-stream-loop.js +6 -2
- package/dist/channels/plugins/actions/telegram.js +42 -18
- package/dist/channels/plugins/allowlist-match.js +1 -1
- package/dist/channels/plugins/group-mentions.js +51 -41
- package/dist/channels/plugins/message-action-names.js +2 -0
- package/dist/channels/plugins/message-actions.js +24 -5
- package/dist/channels/plugins/normalize/discord.js +26 -4
- package/dist/channels/plugins/normalize/signal.js +35 -22
- package/dist/channels/plugins/onboarding/helpers.js +8 -26
- package/dist/channels/plugins/outbound/imessage.js +15 -14
- package/dist/channels/registry.js +20 -7
- package/dist/cli/acp-cli.js +7 -5
- package/dist/cli/browser-cli-extension.js +25 -12
- package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
- package/dist/cli/browser-cli-state.js +101 -145
- package/dist/cli/command-options.js +28 -0
- package/dist/cli/completion-cli.js +6 -6
- package/dist/cli/cron-cli/register.cron-add.js +25 -1
- package/dist/cli/cron-cli/register.cron-edit.js +44 -0
- package/dist/cli/cron-cli/shared.js +7 -1
- package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
- package/dist/cli/daemon-cli/lifecycle.js +23 -247
- package/dist/cli/daemon-cli/register-service-commands.js +25 -4
- package/dist/cli/daemon-cli.js +1 -0
- package/dist/cli/devices-cli.js +33 -20
- package/dist/cli/gateway-cli/register.js +37 -105
- package/dist/cli/gateway-cli/run.js +49 -11
- package/dist/cli/nodes-camera.js +59 -4
- package/dist/cli/nodes-cli/register.camera.js +27 -24
- package/dist/cli/nodes-cli/rpc.js +21 -38
- package/dist/cli/qr-cli.js +2 -2
- package/dist/cli/skills-cli.format.js +2 -2
- package/dist/cli/update-cli/progress.js +2 -2
- package/dist/cli/update-cli/restart-helper.js +28 -7
- package/dist/cli/update-cli/shared.js +7 -7
- package/dist/cli/update-cli/status.js +1 -1
- package/dist/cli/update-cli/update-command.js +14 -8
- package/dist/cli/update-cli/wizard.js +2 -2
- package/dist/cli/update-cli.js +21 -1027
- package/dist/commands/auth-choice.apply.anthropic.js +10 -2
- package/dist/commands/channels/add-mutators.js +3 -35
- package/dist/commands/channels/add.js +39 -51
- package/dist/commands/config-validation.js +1 -1
- package/dist/commands/configure.gateway-auth.js +52 -15
- package/dist/commands/configure.gateway.js +84 -40
- package/dist/commands/doctor-completion.js +3 -3
- package/dist/commands/doctor-config-flow.js +536 -16
- package/dist/commands/doctor-gateway-services.js +103 -79
- package/dist/commands/doctor-memory-search.js +9 -9
- package/dist/commands/doctor-platform-notes.js +57 -30
- package/dist/commands/doctor-prompter.js +26 -15
- package/dist/commands/doctor-session-locks.js +1 -1
- package/dist/commands/doctor.js +21 -9
- package/dist/commands/model-picker.js +120 -95
- package/dist/commands/models/set.js +2 -21
- package/dist/commands/models/shared.js +65 -37
- package/dist/commands/onboard-helpers.js +81 -39
- package/dist/commands/openai-codex-oauth.js +1 -1
- package/dist/commands/sessions.js +52 -53
- package/dist/commands/status.summary.js +52 -34
- package/dist/commands/test-wizard-helpers.js +2 -2
- package/dist/config/defaults.js +79 -42
- package/dist/config/group-policy.js +50 -18
- package/dist/config/includes.js +37 -10
- package/dist/config/schema.help.js +5 -4
- package/dist/config/schema.hints.js +2 -2
- package/dist/config/schema.labels.js +1 -0
- package/dist/config/sessions/group.js +12 -11
- package/dist/config/sessions/paths.js +137 -11
- package/dist/config/sessions/store.js +185 -65
- package/dist/config/sessions/types.js +15 -1
- package/dist/config/sessions.js +1 -0
- package/dist/config/telegram-custom-commands.js +3 -2
- package/dist/config/types.js +2 -0
- package/dist/config/zod-schema.agent-defaults.js +6 -27
- package/dist/config/zod-schema.agent-runtime.js +171 -79
- package/dist/config/zod-schema.providers-core.js +138 -65
- package/dist/config/zod-schema.session.js +49 -22
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
- package/dist/cron/isolated-agent/run.js +224 -57
- package/dist/cron/normalize.js +48 -45
- package/dist/cron/run-log.js +14 -0
- package/dist/cron/service/jobs.js +190 -28
- package/dist/cron/service/normalize.js +29 -11
- package/dist/cron/service/store.js +30 -44
- package/dist/cron/service/timer.js +182 -96
- package/dist/cron/service.js +3 -0
- package/dist/cron/stagger.js +37 -0
- package/dist/daemon/inspect.js +132 -92
- package/dist/daemon/runtime-paths.js +25 -4
- package/dist/daemon/service-audit.js +47 -16
- package/dist/discord/accounts.js +23 -20
- package/dist/discord/monitor/agent-components.js +1115 -219
- package/dist/discord/monitor/allow-list.js +114 -34
- package/dist/discord/monitor/listeners.js +204 -97
- package/dist/discord/monitor/message-handler.js +21 -10
- package/dist/discord/monitor/message-handler.preflight.js +195 -101
- package/dist/discord/monitor/message-handler.process.js +384 -123
- package/dist/discord/monitor/message-utils.js +86 -23
- package/dist/discord/monitor/native-command.js +77 -57
- package/dist/discord/monitor/provider.js +122 -117
- package/dist/discord/monitor/reply-context.js +20 -16
- package/dist/discord/monitor/reply-delivery.js +40 -8
- package/dist/discord/monitor/rest-fetch.js +22 -0
- package/dist/discord/monitor/threading.js +117 -24
- package/dist/discord/send.js +2 -1
- package/dist/discord/send.outbound.js +124 -11
- package/dist/discord/send.shared.js +112 -72
- package/dist/discord/voice-message.js +3 -3
- package/dist/gateway/auth.js +119 -44
- package/dist/gateway/call.js +76 -34
- package/dist/gateway/channel-health-monitor.js +57 -50
- package/dist/gateway/client.js +63 -29
- package/dist/gateway/control-ui-contract.js +1 -1
- package/dist/gateway/gateway-config-prompts.shared.js +2 -2
- package/dist/gateway/net.js +109 -1
- package/dist/gateway/protocol/index.js +5 -8
- package/dist/gateway/protocol/schema/agent.js +19 -1
- package/dist/gateway/protocol/schema/channels.js +21 -0
- package/dist/gateway/protocol/schema/cron.js +43 -30
- package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
- package/dist/gateway/protocol/schema/sessions.js +5 -1
- package/dist/gateway/protocol/schema.js +0 -1
- package/dist/gateway/server/presence-events.js +12 -0
- package/dist/gateway/server/ws-connection/message-handler.js +203 -212
- package/dist/gateway/server/ws-connection.js +58 -21
- package/dist/gateway/server-broadcast.js +18 -13
- package/dist/gateway/server-cron.js +177 -10
- package/dist/gateway/server-methods/agent-job.js +131 -38
- package/dist/gateway/server-methods/send.js +60 -14
- package/dist/gateway/server-methods/sessions.js +160 -96
- package/dist/gateway/server-methods/system.js +5 -7
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +24 -8
- package/dist/gateway/server-node-events.js +278 -68
- package/dist/gateway/session-utils.fs.js +316 -75
- package/dist/gateway/session-utils.js +224 -70
- package/dist/gateway/sessions-patch.js +63 -20
- package/dist/gateway/test-temp-config.js +1 -1
- package/dist/gateway/tools-invoke-http.js +118 -70
- package/dist/gateway/ws-log.js +135 -107
- package/dist/hooks/frontmatter.js +36 -82
- package/dist/hooks/install.js +149 -139
- package/dist/hooks/internal-hooks.js +29 -4
- package/dist/hooks/plugin-hooks.js +2 -1
- package/dist/imessage/monitor/deliver.js +10 -4
- package/dist/imessage/monitor/monitor-provider.js +138 -375
- package/dist/imessage/monitor/runtime.js +4 -8
- package/dist/imessage/send.js +65 -19
- package/dist/infra/exec-approvals-allowlist.js +7 -0
- package/dist/infra/exec-approvals.js +35 -920
- package/dist/infra/exec-safe-bin-trust.js +64 -0
- package/dist/infra/heartbeat-runner.js +207 -134
- package/dist/infra/heartbeat-wake.js +183 -22
- package/dist/infra/install-source-utils.js +47 -0
- package/dist/infra/net/ssrf.js +170 -36
- package/dist/infra/outbound/deliver.js +224 -58
- package/dist/infra/outbound/message-action-spec.js +12 -5
- package/dist/infra/outbound/outbound-session.js +27 -25
- package/dist/infra/poolbot-root.js +32 -22
- package/dist/infra/ports.js +14 -11
- package/dist/infra/skills-remote.js +48 -37
- package/dist/infra/system-events.js +25 -11
- package/dist/infra/system-presence.js +26 -33
- package/dist/infra/tmp-poolbot-dir.js +81 -2
- package/dist/infra/wsl.js +37 -1
- package/dist/line/bot-message-context.js +163 -191
- package/dist/logging/subsystem.js +59 -22
- package/dist/markdown/ir.js +124 -50
- package/dist/media/store.js +1 -1
- package/dist/media-understanding/runner.entries.js +42 -25
- package/dist/media-understanding/runner.js +53 -488
- package/dist/memory/embeddings-gemini.js +53 -38
- package/dist/memory/manager-embedding-ops.js +48 -69
- package/dist/pairing/pairing-store.js +178 -119
- package/dist/plugin-sdk/index.js +34 -6
- package/dist/plugins/hooks.js +135 -14
- package/dist/plugins/install.js +190 -152
- package/dist/polls.js +11 -0
- package/dist/routing/resolve-route.js +190 -56
- package/dist/routing/session-key.js +38 -22
- package/dist/runtime.js +35 -9
- package/dist/security/audit-channel.js +1 -1
- package/dist/sessions/session-key-utils.js +29 -11
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-list-types.js +1 -0
- package/dist/shared/string-normalization.js +15 -0
- package/dist/signal/monitor/event-handler.js +68 -36
- package/dist/signal/send.js +29 -37
- package/dist/slack/monitor/allow-list.js +10 -11
- package/dist/slack/monitor/commands.js +14 -3
- package/dist/slack/monitor/events/interactions.js +4 -4
- package/dist/slack/monitor/media.js +224 -16
- package/dist/slack/monitor/message-handler/dispatch.js +247 -13
- package/dist/slack/monitor/message-handler/prepare.js +128 -45
- package/dist/slack/monitor/slash.js +357 -144
- package/dist/slack/streaming.js +77 -0
- package/dist/telegram/accounts.js +40 -13
- package/dist/telegram/allowed-updates.js +3 -0
- package/dist/telegram/bot/delivery.js +129 -66
- package/dist/telegram/bot/helpers.js +136 -122
- package/dist/telegram/bot-handlers.js +600 -339
- package/dist/telegram/bot-message-context.js +115 -73
- package/dist/telegram/bot-message-dispatch.js +235 -104
- package/dist/telegram/bot-native-command-menu.js +3 -1
- package/dist/telegram/bot-native-commands.js +213 -193
- package/dist/telegram/bot.js +24 -132
- package/dist/telegram/draft-stream.js +84 -75
- package/dist/telegram/format.js +150 -6
- package/dist/telegram/send.js +415 -255
- package/dist/telegram/targets.js +21 -2
- package/dist/telegram/update-offset-store.js +19 -3
- package/dist/terminal/restore.js +5 -2
- package/dist/test-utils/fetch-mock.js +5 -0
- package/dist/version.js +18 -5
- package/dist/web/auto-reply/monitor/broadcast.js +7 -3
- package/dist/web/auto-reply/monitor/on-message.js +6 -3
- package/dist/web/inbound/media.js +34 -8
- package/dist/web/inbound/monitor.js +34 -17
- package/dist/web/inbound/send-api.js +18 -17
- package/dist/web/outbound.js +12 -5
- package/dist/wizard/clack-prompter.js +40 -7
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/device-pair/index.ts +2 -2
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- 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/irc/src/accounts.ts +1 -1
- package/extensions/irc/src/onboarding.ts +4 -4
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +10 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- 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 +10 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +10 -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 +10 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +10 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +10 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +10 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
- package/skills/apple-reminders/SKILL.md +100 -49
- package/skills/coding-agent/SKILL.md +34 -28
- package/skills/github/SKILL.md +131 -16
- package/skills/imsg/SKILL.md +112 -15
- package/skills/openhue/SKILL.md +101 -19
- package/skills/tmux/SKILL.md +111 -79
- package/skills/weather/SKILL.md +88 -25
- package/dist/agents/openclaw-tools.js +0 -151
- package/dist/agents/tool-security.js +0 -96
- package/dist/gateway/url-validation.js +0 -94
- package/dist/infra/openclaw-root.js +0 -109
- package/dist/infra/tmp-openclaw-dir.js +0 -81
- package/dist/media/path-sanitization.js +0 -78
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
-
import net from "node:net";
|
|
4
3
|
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import { DEFAULT_AGENT_ID } from "../routing/session-key.js";
|
|
6
|
+
import { requestJsonlSocket } from "./jsonl-socket.js";
|
|
7
|
+
export * from "./exec-approvals-analysis.js";
|
|
8
|
+
export * from "./exec-approvals-allowlist.js";
|
|
9
|
+
// Keep CLI + gateway defaults in sync.
|
|
10
|
+
export const DEFAULT_EXEC_APPROVAL_TIMEOUT_MS = 120_000;
|
|
7
11
|
const DEFAULT_SECURITY = "deny";
|
|
8
12
|
const DEFAULT_ASK = "on-miss";
|
|
9
13
|
const DEFAULT_ASK_FALLBACK = "deny";
|
|
10
14
|
const DEFAULT_AUTO_ALLOW_SKILLS = false;
|
|
11
15
|
const DEFAULT_SOCKET = "~/.poolbot/exec-approvals.sock";
|
|
12
16
|
const DEFAULT_FILE = "~/.poolbot/exec-approvals.json";
|
|
13
|
-
export const DEFAULT_SAFE_BINS = ["jq", "grep", "cut", "sort", "uniq", "head", "tail", "tr", "wc"];
|
|
14
17
|
function hashExecApprovalsRaw(raw) {
|
|
15
18
|
return crypto
|
|
16
19
|
.createHash("sha256")
|
|
@@ -35,19 +38,6 @@ export function resolveExecApprovalsPath() {
|
|
|
35
38
|
export function resolveExecApprovalsSocketPath() {
|
|
36
39
|
return expandHome(DEFAULT_SOCKET);
|
|
37
40
|
}
|
|
38
|
-
export function mergeExecApprovalsSocketDefaults(params) {
|
|
39
|
-
const currentSocketPath = params.current?.socket?.path?.trim();
|
|
40
|
-
const currentToken = params.current?.socket?.token?.trim();
|
|
41
|
-
const socketPath = params.normalized.socket?.path?.trim() ?? currentSocketPath ?? resolveExecApprovalsSocketPath();
|
|
42
|
-
const token = params.normalized.socket?.token?.trim() ?? currentToken ?? "";
|
|
43
|
-
return {
|
|
44
|
-
...params.normalized,
|
|
45
|
-
socket: {
|
|
46
|
-
path: socketPath,
|
|
47
|
-
token,
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
41
|
function normalizeAllowlistPattern(value) {
|
|
52
42
|
const trimmed = value?.trim() ?? "";
|
|
53
43
|
return trimmed ? trimmed.toLowerCase() : null;
|
|
@@ -162,6 +152,19 @@ export function normalizeExecApprovals(file) {
|
|
|
162
152
|
};
|
|
163
153
|
return normalized;
|
|
164
154
|
}
|
|
155
|
+
export function mergeExecApprovalsSocketDefaults(params) {
|
|
156
|
+
const currentSocketPath = params.current?.socket?.path?.trim();
|
|
157
|
+
const currentToken = params.current?.socket?.token?.trim();
|
|
158
|
+
const socketPath = params.normalized.socket?.path?.trim() ?? currentSocketPath ?? resolveExecApprovalsSocketPath();
|
|
159
|
+
const token = params.normalized.socket?.token?.trim() ?? currentToken ?? "";
|
|
160
|
+
return {
|
|
161
|
+
...params.normalized,
|
|
162
|
+
socket: {
|
|
163
|
+
path: socketPath,
|
|
164
|
+
token,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
165
168
|
function generateToken() {
|
|
166
169
|
return crypto.randomBytes(24).toString("base64url");
|
|
167
170
|
}
|
|
@@ -298,860 +301,6 @@ export function resolveExecApprovalsFromFile(params) {
|
|
|
298
301
|
file,
|
|
299
302
|
};
|
|
300
303
|
}
|
|
301
|
-
function isExecutableFile(filePath) {
|
|
302
|
-
try {
|
|
303
|
-
const stat = fs.statSync(filePath);
|
|
304
|
-
if (!stat.isFile()) {
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
if (process.platform !== "win32") {
|
|
308
|
-
fs.accessSync(filePath, fs.constants.X_OK);
|
|
309
|
-
}
|
|
310
|
-
return true;
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
function parseFirstToken(command) {
|
|
317
|
-
const trimmed = command.trim();
|
|
318
|
-
if (!trimmed) {
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
const first = trimmed[0];
|
|
322
|
-
if (first === '"' || first === "'") {
|
|
323
|
-
const end = trimmed.indexOf(first, 1);
|
|
324
|
-
if (end > 1) {
|
|
325
|
-
return trimmed.slice(1, end);
|
|
326
|
-
}
|
|
327
|
-
return trimmed.slice(1);
|
|
328
|
-
}
|
|
329
|
-
const match = /^[^\s]+/.exec(trimmed);
|
|
330
|
-
return match ? match[0] : null;
|
|
331
|
-
}
|
|
332
|
-
function resolveExecutablePath(rawExecutable, cwd, env) {
|
|
333
|
-
const expanded = rawExecutable.startsWith("~") ? expandHome(rawExecutable) : rawExecutable;
|
|
334
|
-
if (expanded.includes("/") || expanded.includes("\\")) {
|
|
335
|
-
if (path.isAbsolute(expanded)) {
|
|
336
|
-
return isExecutableFile(expanded) ? expanded : undefined;
|
|
337
|
-
}
|
|
338
|
-
const base = cwd && cwd.trim() ? cwd.trim() : process.cwd();
|
|
339
|
-
const candidate = path.resolve(base, expanded);
|
|
340
|
-
return isExecutableFile(candidate) ? candidate : undefined;
|
|
341
|
-
}
|
|
342
|
-
const envPath = env?.PATH ?? env?.Path ?? process.env.PATH ?? process.env.Path ?? "";
|
|
343
|
-
const entries = envPath.split(path.delimiter).filter(Boolean);
|
|
344
|
-
const hasExtension = process.platform === "win32" && path.extname(expanded).length > 0;
|
|
345
|
-
const extensions = process.platform === "win32"
|
|
346
|
-
? hasExtension
|
|
347
|
-
? [""]
|
|
348
|
-
: (env?.PATHEXT ??
|
|
349
|
-
env?.Pathext ??
|
|
350
|
-
process.env.PATHEXT ??
|
|
351
|
-
process.env.Pathext ??
|
|
352
|
-
".EXE;.CMD;.BAT;.COM")
|
|
353
|
-
.split(";")
|
|
354
|
-
.map((ext) => ext.toLowerCase())
|
|
355
|
-
: [""];
|
|
356
|
-
for (const entry of entries) {
|
|
357
|
-
for (const ext of extensions) {
|
|
358
|
-
const candidate = path.join(entry, expanded + ext);
|
|
359
|
-
if (isExecutableFile(candidate)) {
|
|
360
|
-
return candidate;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
return undefined;
|
|
365
|
-
}
|
|
366
|
-
export function resolveCommandResolution(command, cwd, env) {
|
|
367
|
-
const rawExecutable = parseFirstToken(command);
|
|
368
|
-
if (!rawExecutable) {
|
|
369
|
-
return null;
|
|
370
|
-
}
|
|
371
|
-
const resolvedPath = resolveExecutablePath(rawExecutable, cwd, env);
|
|
372
|
-
const executableName = resolvedPath ? path.basename(resolvedPath) : rawExecutable;
|
|
373
|
-
return { rawExecutable, resolvedPath, executableName };
|
|
374
|
-
}
|
|
375
|
-
export function resolveCommandResolutionFromArgv(argv, cwd, env) {
|
|
376
|
-
const rawExecutable = argv[0]?.trim();
|
|
377
|
-
if (!rawExecutable) {
|
|
378
|
-
return null;
|
|
379
|
-
}
|
|
380
|
-
const resolvedPath = resolveExecutablePath(rawExecutable, cwd, env);
|
|
381
|
-
const executableName = resolvedPath ? path.basename(resolvedPath) : rawExecutable;
|
|
382
|
-
return { rawExecutable, resolvedPath, executableName };
|
|
383
|
-
}
|
|
384
|
-
function normalizeMatchTarget(value) {
|
|
385
|
-
if (process.platform === "win32") {
|
|
386
|
-
const stripped = value.replace(/^\\\\[?.]\\/, "");
|
|
387
|
-
return stripped.replace(/\\/g, "/").toLowerCase();
|
|
388
|
-
}
|
|
389
|
-
return value.replace(/\\\\/g, "/").toLowerCase();
|
|
390
|
-
}
|
|
391
|
-
function tryRealpath(value) {
|
|
392
|
-
try {
|
|
393
|
-
return fs.realpathSync(value);
|
|
394
|
-
}
|
|
395
|
-
catch {
|
|
396
|
-
return null;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
function globToRegExp(pattern) {
|
|
400
|
-
let regex = "^";
|
|
401
|
-
let i = 0;
|
|
402
|
-
while (i < pattern.length) {
|
|
403
|
-
const ch = pattern[i];
|
|
404
|
-
if (ch === "*") {
|
|
405
|
-
const next = pattern[i + 1];
|
|
406
|
-
if (next === "*") {
|
|
407
|
-
regex += ".*";
|
|
408
|
-
i += 2;
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
regex += "[^/]*";
|
|
412
|
-
i += 1;
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
if (ch === "?") {
|
|
416
|
-
regex += ".";
|
|
417
|
-
i += 1;
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
regex += ch.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&");
|
|
421
|
-
i += 1;
|
|
422
|
-
}
|
|
423
|
-
regex += "$";
|
|
424
|
-
return new RegExp(regex, "i");
|
|
425
|
-
}
|
|
426
|
-
function matchesPattern(pattern, target) {
|
|
427
|
-
const trimmed = pattern.trim();
|
|
428
|
-
if (!trimmed) {
|
|
429
|
-
return false;
|
|
430
|
-
}
|
|
431
|
-
const expanded = trimmed.startsWith("~") ? expandHome(trimmed) : trimmed;
|
|
432
|
-
const hasWildcard = /[*?]/.test(expanded);
|
|
433
|
-
let normalizedPattern = expanded;
|
|
434
|
-
let normalizedTarget = target;
|
|
435
|
-
if (process.platform === "win32" && !hasWildcard) {
|
|
436
|
-
normalizedPattern = tryRealpath(expanded) ?? expanded;
|
|
437
|
-
normalizedTarget = tryRealpath(target) ?? target;
|
|
438
|
-
}
|
|
439
|
-
normalizedPattern = normalizeMatchTarget(normalizedPattern);
|
|
440
|
-
normalizedTarget = normalizeMatchTarget(normalizedTarget);
|
|
441
|
-
const regex = globToRegExp(normalizedPattern);
|
|
442
|
-
return regex.test(normalizedTarget);
|
|
443
|
-
}
|
|
444
|
-
function resolveAllowlistCandidatePath(resolution, cwd) {
|
|
445
|
-
if (!resolution) {
|
|
446
|
-
return undefined;
|
|
447
|
-
}
|
|
448
|
-
if (resolution.resolvedPath) {
|
|
449
|
-
return resolution.resolvedPath;
|
|
450
|
-
}
|
|
451
|
-
const raw = resolution.rawExecutable?.trim();
|
|
452
|
-
if (!raw) {
|
|
453
|
-
return undefined;
|
|
454
|
-
}
|
|
455
|
-
const expanded = raw.startsWith("~") ? expandHome(raw) : raw;
|
|
456
|
-
if (!expanded.includes("/") && !expanded.includes("\\")) {
|
|
457
|
-
return undefined;
|
|
458
|
-
}
|
|
459
|
-
if (path.isAbsolute(expanded)) {
|
|
460
|
-
return expanded;
|
|
461
|
-
}
|
|
462
|
-
const base = cwd && cwd.trim() ? cwd.trim() : process.cwd();
|
|
463
|
-
return path.resolve(base, expanded);
|
|
464
|
-
}
|
|
465
|
-
export function matchAllowlist(entries, resolution) {
|
|
466
|
-
if (!entries.length || !resolution?.resolvedPath) {
|
|
467
|
-
return null;
|
|
468
|
-
}
|
|
469
|
-
const resolvedPath = resolution.resolvedPath;
|
|
470
|
-
for (const entry of entries) {
|
|
471
|
-
const pattern = entry.pattern?.trim();
|
|
472
|
-
if (!pattern) {
|
|
473
|
-
continue;
|
|
474
|
-
}
|
|
475
|
-
const hasPath = pattern.includes("/") || pattern.includes("\\") || pattern.includes("~");
|
|
476
|
-
if (!hasPath) {
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
if (matchesPattern(pattern, resolvedPath)) {
|
|
480
|
-
return entry;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
return null;
|
|
484
|
-
}
|
|
485
|
-
const DISALLOWED_PIPELINE_TOKENS = new Set([">", "<", "`", "\n", "\r", "(", ")"]);
|
|
486
|
-
const DOUBLE_QUOTE_ESCAPES = new Set(["\\", '"', "$", "`", "\n", "\r"]);
|
|
487
|
-
const WINDOWS_UNSUPPORTED_TOKENS = new Set([
|
|
488
|
-
"&",
|
|
489
|
-
"|",
|
|
490
|
-
"<",
|
|
491
|
-
">",
|
|
492
|
-
"^",
|
|
493
|
-
"(",
|
|
494
|
-
")",
|
|
495
|
-
"%",
|
|
496
|
-
"!",
|
|
497
|
-
"\n",
|
|
498
|
-
"\r",
|
|
499
|
-
]);
|
|
500
|
-
function isDoubleQuoteEscape(next) {
|
|
501
|
-
return Boolean(next && DOUBLE_QUOTE_ESCAPES.has(next));
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* Iterates through a command string while respecting shell quoting rules.
|
|
505
|
-
* The callback receives each character and the next character, and returns an action:
|
|
506
|
-
* - "split": push current buffer as a segment and start a new one
|
|
507
|
-
* - "skip": skip this character (and optionally the next via skip count)
|
|
508
|
-
* - "include": add this character to the buffer
|
|
509
|
-
* - { reject: reason }: abort with an error
|
|
510
|
-
*/
|
|
511
|
-
function iterateQuoteAware(command, onChar) {
|
|
512
|
-
const parts = [];
|
|
513
|
-
let buf = "";
|
|
514
|
-
let inSingle = false;
|
|
515
|
-
let inDouble = false;
|
|
516
|
-
let escaped = false;
|
|
517
|
-
let hasSplit = false;
|
|
518
|
-
const pushPart = () => {
|
|
519
|
-
const trimmed = buf.trim();
|
|
520
|
-
if (trimmed) {
|
|
521
|
-
parts.push(trimmed);
|
|
522
|
-
}
|
|
523
|
-
buf = "";
|
|
524
|
-
};
|
|
525
|
-
for (let i = 0; i < command.length; i += 1) {
|
|
526
|
-
const ch = command[i];
|
|
527
|
-
const next = command[i + 1];
|
|
528
|
-
if (escaped) {
|
|
529
|
-
buf += ch;
|
|
530
|
-
escaped = false;
|
|
531
|
-
continue;
|
|
532
|
-
}
|
|
533
|
-
if (!inSingle && !inDouble && ch === "\\") {
|
|
534
|
-
escaped = true;
|
|
535
|
-
buf += ch;
|
|
536
|
-
continue;
|
|
537
|
-
}
|
|
538
|
-
if (inSingle) {
|
|
539
|
-
if (ch === "'") {
|
|
540
|
-
inSingle = false;
|
|
541
|
-
}
|
|
542
|
-
buf += ch;
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
if (inDouble) {
|
|
546
|
-
if (ch === "\\" && isDoubleQuoteEscape(next)) {
|
|
547
|
-
buf += ch;
|
|
548
|
-
buf += next;
|
|
549
|
-
i += 1;
|
|
550
|
-
continue;
|
|
551
|
-
}
|
|
552
|
-
if (ch === "$" && next === "(") {
|
|
553
|
-
return { ok: false, reason: "unsupported shell token: $()" };
|
|
554
|
-
}
|
|
555
|
-
if (ch === "`") {
|
|
556
|
-
return { ok: false, reason: "unsupported shell token: `" };
|
|
557
|
-
}
|
|
558
|
-
if (ch === "\n" || ch === "\r") {
|
|
559
|
-
return { ok: false, reason: "unsupported shell token: newline" };
|
|
560
|
-
}
|
|
561
|
-
if (ch === '"') {
|
|
562
|
-
inDouble = false;
|
|
563
|
-
}
|
|
564
|
-
buf += ch;
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
if (ch === "'") {
|
|
568
|
-
inSingle = true;
|
|
569
|
-
buf += ch;
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
if (ch === '"') {
|
|
573
|
-
inDouble = true;
|
|
574
|
-
buf += ch;
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
577
|
-
const action = onChar(ch, next, i);
|
|
578
|
-
if (typeof action === "object" && "reject" in action) {
|
|
579
|
-
return { ok: false, reason: action.reject };
|
|
580
|
-
}
|
|
581
|
-
if (action === "split") {
|
|
582
|
-
pushPart();
|
|
583
|
-
hasSplit = true;
|
|
584
|
-
continue;
|
|
585
|
-
}
|
|
586
|
-
if (action === "skip") {
|
|
587
|
-
continue;
|
|
588
|
-
}
|
|
589
|
-
buf += ch;
|
|
590
|
-
}
|
|
591
|
-
if (escaped || inSingle || inDouble) {
|
|
592
|
-
return { ok: false, reason: "unterminated shell quote/escape" };
|
|
593
|
-
}
|
|
594
|
-
pushPart();
|
|
595
|
-
return { ok: true, parts, hasSplit };
|
|
596
|
-
}
|
|
597
|
-
function splitShellPipeline(command) {
|
|
598
|
-
let emptySegment = false;
|
|
599
|
-
const result = iterateQuoteAware(command, (ch, next) => {
|
|
600
|
-
if (ch === "|" && next === "|") {
|
|
601
|
-
return { reject: "unsupported shell token: ||" };
|
|
602
|
-
}
|
|
603
|
-
if (ch === "|" && next === "&") {
|
|
604
|
-
return { reject: "unsupported shell token: |&" };
|
|
605
|
-
}
|
|
606
|
-
if (ch === "|") {
|
|
607
|
-
emptySegment = true;
|
|
608
|
-
return "split";
|
|
609
|
-
}
|
|
610
|
-
if (ch === "&" || ch === ";") {
|
|
611
|
-
return { reject: `unsupported shell token: ${ch}` };
|
|
612
|
-
}
|
|
613
|
-
if (DISALLOWED_PIPELINE_TOKENS.has(ch)) {
|
|
614
|
-
return { reject: `unsupported shell token: ${ch}` };
|
|
615
|
-
}
|
|
616
|
-
if (ch === "$" && next === "(") {
|
|
617
|
-
return { reject: "unsupported shell token: $()" };
|
|
618
|
-
}
|
|
619
|
-
emptySegment = false;
|
|
620
|
-
return "include";
|
|
621
|
-
});
|
|
622
|
-
if (!result.ok) {
|
|
623
|
-
return { ok: false, reason: result.reason, segments: [] };
|
|
624
|
-
}
|
|
625
|
-
if (emptySegment || result.parts.length === 0) {
|
|
626
|
-
return {
|
|
627
|
-
ok: false,
|
|
628
|
-
reason: result.parts.length === 0 ? "empty command" : "empty pipeline segment",
|
|
629
|
-
segments: [],
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
return { ok: true, segments: result.parts };
|
|
633
|
-
}
|
|
634
|
-
function findWindowsUnsupportedToken(command) {
|
|
635
|
-
for (const ch of command) {
|
|
636
|
-
if (WINDOWS_UNSUPPORTED_TOKENS.has(ch)) {
|
|
637
|
-
if (ch === "\n" || ch === "\r") {
|
|
638
|
-
return "newline";
|
|
639
|
-
}
|
|
640
|
-
return ch;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
return null;
|
|
644
|
-
}
|
|
645
|
-
function tokenizeWindowsSegment(segment) {
|
|
646
|
-
const tokens = [];
|
|
647
|
-
let buf = "";
|
|
648
|
-
let inDouble = false;
|
|
649
|
-
const pushToken = () => {
|
|
650
|
-
if (buf.length > 0) {
|
|
651
|
-
tokens.push(buf);
|
|
652
|
-
buf = "";
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
for (let i = 0; i < segment.length; i += 1) {
|
|
656
|
-
const ch = segment[i];
|
|
657
|
-
if (ch === '"') {
|
|
658
|
-
inDouble = !inDouble;
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
661
|
-
if (!inDouble && /\s/.test(ch)) {
|
|
662
|
-
pushToken();
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
buf += ch;
|
|
666
|
-
}
|
|
667
|
-
if (inDouble) {
|
|
668
|
-
return null;
|
|
669
|
-
}
|
|
670
|
-
pushToken();
|
|
671
|
-
return tokens.length > 0 ? tokens : null;
|
|
672
|
-
}
|
|
673
|
-
function analyzeWindowsShellCommand(params) {
|
|
674
|
-
const unsupported = findWindowsUnsupportedToken(params.command);
|
|
675
|
-
if (unsupported) {
|
|
676
|
-
return {
|
|
677
|
-
ok: false,
|
|
678
|
-
reason: `unsupported windows shell token: ${unsupported}`,
|
|
679
|
-
segments: [],
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
const argv = tokenizeWindowsSegment(params.command);
|
|
683
|
-
if (!argv || argv.length === 0) {
|
|
684
|
-
return { ok: false, reason: "unable to parse windows command", segments: [] };
|
|
685
|
-
}
|
|
686
|
-
return {
|
|
687
|
-
ok: true,
|
|
688
|
-
segments: [
|
|
689
|
-
{
|
|
690
|
-
raw: params.command,
|
|
691
|
-
argv,
|
|
692
|
-
resolution: resolveCommandResolutionFromArgv(argv, params.cwd, params.env),
|
|
693
|
-
},
|
|
694
|
-
],
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
function isWindowsPlatform(platform) {
|
|
698
|
-
const normalized = String(platform ?? "")
|
|
699
|
-
.trim()
|
|
700
|
-
.toLowerCase();
|
|
701
|
-
return normalized.startsWith("win");
|
|
702
|
-
}
|
|
703
|
-
function tokenizeShellSegment(segment) {
|
|
704
|
-
const tokens = [];
|
|
705
|
-
let buf = "";
|
|
706
|
-
let inSingle = false;
|
|
707
|
-
let inDouble = false;
|
|
708
|
-
let escaped = false;
|
|
709
|
-
const pushToken = () => {
|
|
710
|
-
if (buf.length > 0) {
|
|
711
|
-
tokens.push(buf);
|
|
712
|
-
buf = "";
|
|
713
|
-
}
|
|
714
|
-
};
|
|
715
|
-
for (let i = 0; i < segment.length; i += 1) {
|
|
716
|
-
const ch = segment[i];
|
|
717
|
-
if (escaped) {
|
|
718
|
-
buf += ch;
|
|
719
|
-
escaped = false;
|
|
720
|
-
continue;
|
|
721
|
-
}
|
|
722
|
-
if (!inSingle && !inDouble && ch === "\\") {
|
|
723
|
-
escaped = true;
|
|
724
|
-
continue;
|
|
725
|
-
}
|
|
726
|
-
if (inSingle) {
|
|
727
|
-
if (ch === "'") {
|
|
728
|
-
inSingle = false;
|
|
729
|
-
}
|
|
730
|
-
else {
|
|
731
|
-
buf += ch;
|
|
732
|
-
}
|
|
733
|
-
continue;
|
|
734
|
-
}
|
|
735
|
-
if (inDouble) {
|
|
736
|
-
const next = segment[i + 1];
|
|
737
|
-
if (ch === "\\" && isDoubleQuoteEscape(next)) {
|
|
738
|
-
buf += next;
|
|
739
|
-
i += 1;
|
|
740
|
-
continue;
|
|
741
|
-
}
|
|
742
|
-
if (ch === '"') {
|
|
743
|
-
inDouble = false;
|
|
744
|
-
}
|
|
745
|
-
else {
|
|
746
|
-
buf += ch;
|
|
747
|
-
}
|
|
748
|
-
continue;
|
|
749
|
-
}
|
|
750
|
-
if (ch === "'") {
|
|
751
|
-
inSingle = true;
|
|
752
|
-
continue;
|
|
753
|
-
}
|
|
754
|
-
if (ch === '"') {
|
|
755
|
-
inDouble = true;
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
if (/\s/.test(ch)) {
|
|
759
|
-
pushToken();
|
|
760
|
-
continue;
|
|
761
|
-
}
|
|
762
|
-
buf += ch;
|
|
763
|
-
}
|
|
764
|
-
if (escaped || inSingle || inDouble) {
|
|
765
|
-
return null;
|
|
766
|
-
}
|
|
767
|
-
pushToken();
|
|
768
|
-
return tokens;
|
|
769
|
-
}
|
|
770
|
-
function parseSegmentsFromParts(parts, cwd, env) {
|
|
771
|
-
const segments = [];
|
|
772
|
-
for (const raw of parts) {
|
|
773
|
-
const argv = tokenizeShellSegment(raw);
|
|
774
|
-
if (!argv || argv.length === 0) {
|
|
775
|
-
return null;
|
|
776
|
-
}
|
|
777
|
-
segments.push({
|
|
778
|
-
raw,
|
|
779
|
-
argv,
|
|
780
|
-
resolution: resolveCommandResolutionFromArgv(argv, cwd, env),
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
return segments;
|
|
784
|
-
}
|
|
785
|
-
export function analyzeShellCommand(params) {
|
|
786
|
-
if (isWindowsPlatform(params.platform)) {
|
|
787
|
-
return analyzeWindowsShellCommand(params);
|
|
788
|
-
}
|
|
789
|
-
// First try splitting by chain operators (&&, ||, ;)
|
|
790
|
-
const chainParts = splitCommandChain(params.command);
|
|
791
|
-
if (chainParts) {
|
|
792
|
-
const chains = [];
|
|
793
|
-
const allSegments = [];
|
|
794
|
-
for (const part of chainParts) {
|
|
795
|
-
const pipelineSplit = splitShellPipeline(part);
|
|
796
|
-
if (!pipelineSplit.ok) {
|
|
797
|
-
return { ok: false, reason: pipelineSplit.reason, segments: [] };
|
|
798
|
-
}
|
|
799
|
-
const segments = parseSegmentsFromParts(pipelineSplit.segments, params.cwd, params.env);
|
|
800
|
-
if (!segments) {
|
|
801
|
-
return { ok: false, reason: "unable to parse shell segment", segments: [] };
|
|
802
|
-
}
|
|
803
|
-
chains.push(segments);
|
|
804
|
-
allSegments.push(...segments);
|
|
805
|
-
}
|
|
806
|
-
return { ok: true, segments: allSegments, chains };
|
|
807
|
-
}
|
|
808
|
-
// No chain operators, parse as simple pipeline
|
|
809
|
-
const split = splitShellPipeline(params.command);
|
|
810
|
-
if (!split.ok) {
|
|
811
|
-
return { ok: false, reason: split.reason, segments: [] };
|
|
812
|
-
}
|
|
813
|
-
const segments = parseSegmentsFromParts(split.segments, params.cwd, params.env);
|
|
814
|
-
if (!segments) {
|
|
815
|
-
return { ok: false, reason: "unable to parse shell segment", segments: [] };
|
|
816
|
-
}
|
|
817
|
-
return { ok: true, segments };
|
|
818
|
-
}
|
|
819
|
-
export function analyzeArgvCommand(params) {
|
|
820
|
-
const argv = params.argv.filter((entry) => entry.trim().length > 0);
|
|
821
|
-
if (argv.length === 0) {
|
|
822
|
-
return { ok: false, reason: "empty argv", segments: [] };
|
|
823
|
-
}
|
|
824
|
-
return {
|
|
825
|
-
ok: true,
|
|
826
|
-
segments: [
|
|
827
|
-
{
|
|
828
|
-
raw: argv.join(" "),
|
|
829
|
-
argv,
|
|
830
|
-
resolution: resolveCommandResolutionFromArgv(argv, params.cwd, params.env),
|
|
831
|
-
},
|
|
832
|
-
],
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
function isPathLikeToken(value) {
|
|
836
|
-
const trimmed = value.trim();
|
|
837
|
-
if (!trimmed) {
|
|
838
|
-
return false;
|
|
839
|
-
}
|
|
840
|
-
if (trimmed === "-") {
|
|
841
|
-
return false;
|
|
842
|
-
}
|
|
843
|
-
if (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~")) {
|
|
844
|
-
return true;
|
|
845
|
-
}
|
|
846
|
-
if (trimmed.startsWith("/")) {
|
|
847
|
-
return true;
|
|
848
|
-
}
|
|
849
|
-
return /^[A-Za-z]:[\\/]/.test(trimmed);
|
|
850
|
-
}
|
|
851
|
-
function defaultFileExists(filePath) {
|
|
852
|
-
try {
|
|
853
|
-
return fs.existsSync(filePath);
|
|
854
|
-
}
|
|
855
|
-
catch {
|
|
856
|
-
return false;
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
export function normalizeSafeBins(entries) {
|
|
860
|
-
if (!Array.isArray(entries)) {
|
|
861
|
-
return new Set();
|
|
862
|
-
}
|
|
863
|
-
const normalized = entries
|
|
864
|
-
.map((entry) => entry.trim().toLowerCase())
|
|
865
|
-
.filter((entry) => entry.length > 0);
|
|
866
|
-
return new Set(normalized);
|
|
867
|
-
}
|
|
868
|
-
export function resolveSafeBins(entries) {
|
|
869
|
-
if (entries === undefined) {
|
|
870
|
-
return normalizeSafeBins(DEFAULT_SAFE_BINS);
|
|
871
|
-
}
|
|
872
|
-
return normalizeSafeBins(entries ?? []);
|
|
873
|
-
}
|
|
874
|
-
export function isSafeBinUsage(params) {
|
|
875
|
-
if (params.safeBins.size === 0) {
|
|
876
|
-
return false;
|
|
877
|
-
}
|
|
878
|
-
const resolution = params.resolution;
|
|
879
|
-
const execName = resolution?.executableName?.toLowerCase();
|
|
880
|
-
if (!execName) {
|
|
881
|
-
return false;
|
|
882
|
-
}
|
|
883
|
-
const matchesSafeBin = params.safeBins.has(execName) ||
|
|
884
|
-
(process.platform === "win32" && params.safeBins.has(path.parse(execName).name));
|
|
885
|
-
if (!matchesSafeBin) {
|
|
886
|
-
return false;
|
|
887
|
-
}
|
|
888
|
-
if (!resolution?.resolvedPath) {
|
|
889
|
-
return false;
|
|
890
|
-
}
|
|
891
|
-
const cwd = params.cwd ?? process.cwd();
|
|
892
|
-
const exists = params.fileExists ?? defaultFileExists;
|
|
893
|
-
const argv = params.argv.slice(1);
|
|
894
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
895
|
-
const token = argv[i];
|
|
896
|
-
if (!token) {
|
|
897
|
-
continue;
|
|
898
|
-
}
|
|
899
|
-
if (token === "-") {
|
|
900
|
-
continue;
|
|
901
|
-
}
|
|
902
|
-
if (token.startsWith("-")) {
|
|
903
|
-
const eqIndex = token.indexOf("=");
|
|
904
|
-
if (eqIndex > 0) {
|
|
905
|
-
const value = token.slice(eqIndex + 1);
|
|
906
|
-
if (value && (isPathLikeToken(value) || exists(path.resolve(cwd, value)))) {
|
|
907
|
-
return false;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
continue;
|
|
911
|
-
}
|
|
912
|
-
if (isPathLikeToken(token)) {
|
|
913
|
-
return false;
|
|
914
|
-
}
|
|
915
|
-
if (exists(path.resolve(cwd, token))) {
|
|
916
|
-
return false;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
return true;
|
|
920
|
-
}
|
|
921
|
-
function evaluateSegments(segments, params) {
|
|
922
|
-
const matches = [];
|
|
923
|
-
const allowSkills = params.autoAllowSkills === true && (params.skillBins?.size ?? 0) > 0;
|
|
924
|
-
const satisfied = segments.every((segment) => {
|
|
925
|
-
const candidatePath = resolveAllowlistCandidatePath(segment.resolution, params.cwd);
|
|
926
|
-
const candidateResolution = candidatePath && segment.resolution
|
|
927
|
-
? { ...segment.resolution, resolvedPath: candidatePath }
|
|
928
|
-
: segment.resolution;
|
|
929
|
-
const match = matchAllowlist(params.allowlist, candidateResolution);
|
|
930
|
-
if (match) {
|
|
931
|
-
matches.push(match);
|
|
932
|
-
}
|
|
933
|
-
const safe = isSafeBinUsage({
|
|
934
|
-
argv: segment.argv,
|
|
935
|
-
resolution: segment.resolution,
|
|
936
|
-
safeBins: params.safeBins,
|
|
937
|
-
cwd: params.cwd,
|
|
938
|
-
});
|
|
939
|
-
const skillAllow = allowSkills && segment.resolution?.executableName
|
|
940
|
-
? params.skillBins?.has(segment.resolution.executableName)
|
|
941
|
-
: false;
|
|
942
|
-
return Boolean(match || safe || skillAllow);
|
|
943
|
-
});
|
|
944
|
-
return { satisfied, matches };
|
|
945
|
-
}
|
|
946
|
-
export function evaluateExecAllowlist(params) {
|
|
947
|
-
const allowlistMatches = [];
|
|
948
|
-
if (!params.analysis.ok || params.analysis.segments.length === 0) {
|
|
949
|
-
return { allowlistSatisfied: false, allowlistMatches };
|
|
950
|
-
}
|
|
951
|
-
// If the analysis contains chains, evaluate each chain part separately
|
|
952
|
-
if (params.analysis.chains) {
|
|
953
|
-
for (const chainSegments of params.analysis.chains) {
|
|
954
|
-
const result = evaluateSegments(chainSegments, {
|
|
955
|
-
allowlist: params.allowlist,
|
|
956
|
-
safeBins: params.safeBins,
|
|
957
|
-
cwd: params.cwd,
|
|
958
|
-
skillBins: params.skillBins,
|
|
959
|
-
autoAllowSkills: params.autoAllowSkills,
|
|
960
|
-
});
|
|
961
|
-
if (!result.satisfied) {
|
|
962
|
-
return { allowlistSatisfied: false, allowlistMatches: [] };
|
|
963
|
-
}
|
|
964
|
-
allowlistMatches.push(...result.matches);
|
|
965
|
-
}
|
|
966
|
-
return { allowlistSatisfied: true, allowlistMatches };
|
|
967
|
-
}
|
|
968
|
-
// No chains, evaluate all segments together
|
|
969
|
-
const result = evaluateSegments(params.analysis.segments, {
|
|
970
|
-
allowlist: params.allowlist,
|
|
971
|
-
safeBins: params.safeBins,
|
|
972
|
-
cwd: params.cwd,
|
|
973
|
-
skillBins: params.skillBins,
|
|
974
|
-
autoAllowSkills: params.autoAllowSkills,
|
|
975
|
-
});
|
|
976
|
-
return { allowlistSatisfied: result.satisfied, allowlistMatches: result.matches };
|
|
977
|
-
}
|
|
978
|
-
/**
|
|
979
|
-
* Splits a command string by chain operators (&&, ||, ;) while respecting quotes.
|
|
980
|
-
* Returns null when no chain is present or when the chain is malformed.
|
|
981
|
-
*/
|
|
982
|
-
function splitCommandChain(command) {
|
|
983
|
-
const parts = [];
|
|
984
|
-
let buf = "";
|
|
985
|
-
let inSingle = false;
|
|
986
|
-
let inDouble = false;
|
|
987
|
-
let escaped = false;
|
|
988
|
-
let foundChain = false;
|
|
989
|
-
let invalidChain = false;
|
|
990
|
-
const pushPart = () => {
|
|
991
|
-
const trimmed = buf.trim();
|
|
992
|
-
if (trimmed) {
|
|
993
|
-
parts.push(trimmed);
|
|
994
|
-
buf = "";
|
|
995
|
-
return true;
|
|
996
|
-
}
|
|
997
|
-
buf = "";
|
|
998
|
-
return false;
|
|
999
|
-
};
|
|
1000
|
-
for (let i = 0; i < command.length; i += 1) {
|
|
1001
|
-
const ch = command[i];
|
|
1002
|
-
const next = command[i + 1];
|
|
1003
|
-
if (escaped) {
|
|
1004
|
-
buf += ch;
|
|
1005
|
-
escaped = false;
|
|
1006
|
-
continue;
|
|
1007
|
-
}
|
|
1008
|
-
if (!inSingle && !inDouble && ch === "\\") {
|
|
1009
|
-
escaped = true;
|
|
1010
|
-
buf += ch;
|
|
1011
|
-
continue;
|
|
1012
|
-
}
|
|
1013
|
-
if (inSingle) {
|
|
1014
|
-
if (ch === "'") {
|
|
1015
|
-
inSingle = false;
|
|
1016
|
-
}
|
|
1017
|
-
buf += ch;
|
|
1018
|
-
continue;
|
|
1019
|
-
}
|
|
1020
|
-
if (inDouble) {
|
|
1021
|
-
if (ch === "\\" && isDoubleQuoteEscape(next)) {
|
|
1022
|
-
buf += ch;
|
|
1023
|
-
buf += next;
|
|
1024
|
-
i += 1;
|
|
1025
|
-
continue;
|
|
1026
|
-
}
|
|
1027
|
-
if (ch === '"') {
|
|
1028
|
-
inDouble = false;
|
|
1029
|
-
}
|
|
1030
|
-
buf += ch;
|
|
1031
|
-
continue;
|
|
1032
|
-
}
|
|
1033
|
-
if (ch === "'") {
|
|
1034
|
-
inSingle = true;
|
|
1035
|
-
buf += ch;
|
|
1036
|
-
continue;
|
|
1037
|
-
}
|
|
1038
|
-
if (ch === '"') {
|
|
1039
|
-
inDouble = true;
|
|
1040
|
-
buf += ch;
|
|
1041
|
-
continue;
|
|
1042
|
-
}
|
|
1043
|
-
if (ch === "&" && command[i + 1] === "&") {
|
|
1044
|
-
if (!pushPart()) {
|
|
1045
|
-
invalidChain = true;
|
|
1046
|
-
}
|
|
1047
|
-
i += 1;
|
|
1048
|
-
foundChain = true;
|
|
1049
|
-
continue;
|
|
1050
|
-
}
|
|
1051
|
-
if (ch === "|" && command[i + 1] === "|") {
|
|
1052
|
-
if (!pushPart()) {
|
|
1053
|
-
invalidChain = true;
|
|
1054
|
-
}
|
|
1055
|
-
i += 1;
|
|
1056
|
-
foundChain = true;
|
|
1057
|
-
continue;
|
|
1058
|
-
}
|
|
1059
|
-
if (ch === ";") {
|
|
1060
|
-
if (!pushPart()) {
|
|
1061
|
-
invalidChain = true;
|
|
1062
|
-
}
|
|
1063
|
-
foundChain = true;
|
|
1064
|
-
continue;
|
|
1065
|
-
}
|
|
1066
|
-
buf += ch;
|
|
1067
|
-
}
|
|
1068
|
-
const pushedFinal = pushPart();
|
|
1069
|
-
if (!foundChain) {
|
|
1070
|
-
return null;
|
|
1071
|
-
}
|
|
1072
|
-
if (invalidChain || !pushedFinal) {
|
|
1073
|
-
return null;
|
|
1074
|
-
}
|
|
1075
|
-
return parts.length > 0 ? parts : null;
|
|
1076
|
-
}
|
|
1077
|
-
/**
|
|
1078
|
-
* Evaluates allowlist for shell commands (including &&, ||, ;) and returns analysis metadata.
|
|
1079
|
-
*/
|
|
1080
|
-
export function evaluateShellAllowlist(params) {
|
|
1081
|
-
const chainParts = isWindowsPlatform(params.platform) ? null : splitCommandChain(params.command);
|
|
1082
|
-
if (!chainParts) {
|
|
1083
|
-
const analysis = analyzeShellCommand({
|
|
1084
|
-
command: params.command,
|
|
1085
|
-
cwd: params.cwd,
|
|
1086
|
-
env: params.env,
|
|
1087
|
-
platform: params.platform,
|
|
1088
|
-
});
|
|
1089
|
-
if (!analysis.ok) {
|
|
1090
|
-
return {
|
|
1091
|
-
analysisOk: false,
|
|
1092
|
-
allowlistSatisfied: false,
|
|
1093
|
-
allowlistMatches: [],
|
|
1094
|
-
segments: [],
|
|
1095
|
-
};
|
|
1096
|
-
}
|
|
1097
|
-
const evaluation = evaluateExecAllowlist({
|
|
1098
|
-
analysis,
|
|
1099
|
-
allowlist: params.allowlist,
|
|
1100
|
-
safeBins: params.safeBins,
|
|
1101
|
-
cwd: params.cwd,
|
|
1102
|
-
skillBins: params.skillBins,
|
|
1103
|
-
autoAllowSkills: params.autoAllowSkills,
|
|
1104
|
-
});
|
|
1105
|
-
return {
|
|
1106
|
-
analysisOk: true,
|
|
1107
|
-
allowlistSatisfied: evaluation.allowlistSatisfied,
|
|
1108
|
-
allowlistMatches: evaluation.allowlistMatches,
|
|
1109
|
-
segments: analysis.segments,
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
const allowlistMatches = [];
|
|
1113
|
-
const segments = [];
|
|
1114
|
-
for (const part of chainParts) {
|
|
1115
|
-
const analysis = analyzeShellCommand({
|
|
1116
|
-
command: part,
|
|
1117
|
-
cwd: params.cwd,
|
|
1118
|
-
env: params.env,
|
|
1119
|
-
platform: params.platform,
|
|
1120
|
-
});
|
|
1121
|
-
if (!analysis.ok) {
|
|
1122
|
-
return {
|
|
1123
|
-
analysisOk: false,
|
|
1124
|
-
allowlistSatisfied: false,
|
|
1125
|
-
allowlistMatches: [],
|
|
1126
|
-
segments: [],
|
|
1127
|
-
};
|
|
1128
|
-
}
|
|
1129
|
-
segments.push(...analysis.segments);
|
|
1130
|
-
const evaluation = evaluateExecAllowlist({
|
|
1131
|
-
analysis,
|
|
1132
|
-
allowlist: params.allowlist,
|
|
1133
|
-
safeBins: params.safeBins,
|
|
1134
|
-
cwd: params.cwd,
|
|
1135
|
-
skillBins: params.skillBins,
|
|
1136
|
-
autoAllowSkills: params.autoAllowSkills,
|
|
1137
|
-
});
|
|
1138
|
-
allowlistMatches.push(...evaluation.allowlistMatches);
|
|
1139
|
-
if (!evaluation.allowlistSatisfied) {
|
|
1140
|
-
return {
|
|
1141
|
-
analysisOk: true,
|
|
1142
|
-
allowlistSatisfied: false,
|
|
1143
|
-
allowlistMatches,
|
|
1144
|
-
segments,
|
|
1145
|
-
};
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
return {
|
|
1149
|
-
analysisOk: true,
|
|
1150
|
-
allowlistSatisfied: true,
|
|
1151
|
-
allowlistMatches,
|
|
1152
|
-
segments,
|
|
1153
|
-
};
|
|
1154
|
-
}
|
|
1155
304
|
export function requiresExecApproval(params) {
|
|
1156
305
|
return (params.ask === "always" ||
|
|
1157
306
|
(params.ask === "on-miss" &&
|
|
@@ -1207,56 +356,22 @@ export async function requestExecApprovalViaSocket(params) {
|
|
|
1207
356
|
return null;
|
|
1208
357
|
}
|
|
1209
358
|
const timeoutMs = params.timeoutMs ?? 15_000;
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
}
|
|
1227
|
-
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
1228
|
-
const payload = JSON.stringify({
|
|
1229
|
-
type: "request",
|
|
1230
|
-
token,
|
|
1231
|
-
id: crypto.randomUUID(),
|
|
1232
|
-
request,
|
|
1233
|
-
});
|
|
1234
|
-
client.on("error", () => finish(null));
|
|
1235
|
-
client.connect(socketPath, () => {
|
|
1236
|
-
client.write(`${payload}\n`);
|
|
1237
|
-
});
|
|
1238
|
-
client.on("data", (data) => {
|
|
1239
|
-
buffer += data.toString("utf8");
|
|
1240
|
-
let idx = buffer.indexOf("\n");
|
|
1241
|
-
while (idx !== -1) {
|
|
1242
|
-
const line = buffer.slice(0, idx).trim();
|
|
1243
|
-
buffer = buffer.slice(idx + 1);
|
|
1244
|
-
idx = buffer.indexOf("\n");
|
|
1245
|
-
if (!line) {
|
|
1246
|
-
continue;
|
|
1247
|
-
}
|
|
1248
|
-
try {
|
|
1249
|
-
const msg = JSON.parse(line);
|
|
1250
|
-
if (msg?.type === "decision" && msg.decision) {
|
|
1251
|
-
clearTimeout(timer);
|
|
1252
|
-
finish(msg.decision);
|
|
1253
|
-
return;
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
catch {
|
|
1257
|
-
// ignore
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
});
|
|
359
|
+
const payload = JSON.stringify({
|
|
360
|
+
type: "request",
|
|
361
|
+
token,
|
|
362
|
+
id: crypto.randomUUID(),
|
|
363
|
+
request,
|
|
364
|
+
});
|
|
365
|
+
return await requestJsonlSocket({
|
|
366
|
+
socketPath,
|
|
367
|
+
payload,
|
|
368
|
+
timeoutMs,
|
|
369
|
+
accept: (value) => {
|
|
370
|
+
const msg = value;
|
|
371
|
+
if (msg?.type === "decision" && msg.decision) {
|
|
372
|
+
return msg.decision;
|
|
373
|
+
}
|
|
374
|
+
return undefined;
|
|
375
|
+
},
|
|
1261
376
|
});
|
|
1262
377
|
}
|