@poolzin/pool-bot 2026.2.24 → 2026.2.26
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 +21 -0
- package/dist/acp/client.js +207 -18
- package/dist/acp/event-mapper.js +87 -22
- package/dist/acp/meta.js +12 -6
- package/dist/acp/secret-file.js +22 -0
- package/dist/agents/agent-paths.js +8 -9
- package/dist/agents/agent-scope.js +17 -5
- package/dist/agents/auth-profiles/oauth.js +148 -64
- package/dist/agents/auth-profiles/session-override.js +13 -7
- 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 +240 -0
- package/dist/agents/bash-tools.exec-host-node.js +235 -0
- package/dist/agents/bash-tools.exec-runtime.js +2 -25
- package/dist/agents/bash-tools.exec-types.js +1 -0
- package/dist/agents/bash-tools.process.js +224 -218
- package/dist/agents/bedrock-discovery.js +3 -1
- package/dist/agents/byteplus-models.js +97 -0
- package/dist/agents/chutes-oauth.js +1 -0
- package/dist/agents/cli-runner/helpers.js +4 -0
- package/dist/agents/compaction.js +41 -14
- package/dist/agents/content-blocks.js +16 -0
- package/dist/agents/doubao-models.js +121 -0
- package/dist/agents/failover-error.js +2 -0
- package/dist/agents/huggingface-models.js +5 -3
- package/dist/agents/live-model-filter.js +5 -0
- package/dist/agents/minimax-vlm.js +10 -8
- package/dist/agents/model-auth.js +6 -0
- package/dist/agents/model-catalog.js +3 -1
- package/dist/agents/model-fallback.js +96 -101
- package/dist/agents/model-selection.js +7 -1
- package/dist/agents/models-config.providers.js +364 -165
- package/dist/agents/ollama-stream.js +117 -4
- package/dist/agents/opencode-zen-models.js +22 -11
- package/dist/agents/pi-embedded-helpers/errors.js +55 -33
- package/dist/agents/pi-embedded-helpers/messaging-dedupe.js +10 -5
- package/dist/agents/pi-embedded-helpers/thinking.js +10 -5
- package/dist/agents/pi-embedded-helpers.js +1 -1
- package/dist/agents/pi-embedded-payloads.js +1 -0
- package/dist/agents/pi-embedded-runner/compact.js +29 -7
- package/dist/agents/pi-embedded-runner/extensions.js +28 -26
- package/dist/agents/pi-embedded-runner/google.js +20 -8
- package/dist/agents/pi-embedded-runner/run/attempt.js +95 -36
- package/dist/agents/pi-embedded-runner/run.js +71 -12
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +11 -2
- package/dist/agents/pi-embedded-runner/session-manager-cache.js +11 -7
- package/dist/agents/pi-embedded-runner/system-prompt.js +2 -0
- package/dist/agents/pi-embedded-runner/thinking.js +42 -0
- package/dist/agents/pi-embedded-runner/tool-name-allowlist.js +19 -0
- package/dist/agents/pi-embedded-runner/utils.js +7 -10
- package/dist/agents/pi-embedded-subscribe.handlers.lifecycle.js +45 -56
- package/dist/agents/pi-embedded-subscribe.handlers.tools.js +2 -2
- package/dist/agents/pi-embedded-subscribe.js +9 -4
- package/dist/agents/pi-embedded-subscribe.tools.js +68 -14
- package/dist/agents/pi-embedded-utils.js +3 -0
- package/dist/agents/pi-extensions/compaction-safeguard-runtime.js +4 -20
- package/dist/agents/pi-extensions/compaction-safeguard.js +75 -33
- package/dist/agents/pi-settings.js +40 -0
- package/dist/agents/pi-tools.policy.js +2 -1
- package/dist/agents/provider/config-loader.js +1 -1
- package/dist/agents/sandbox/browser.js +170 -33
- package/dist/agents/sandbox/config-hash.js +14 -27
- package/dist/agents/sandbox/config.js +21 -2
- package/dist/agents/sandbox/constants.js +2 -0
- package/dist/agents/sandbox/docker.js +16 -2
- package/dist/agents/sandbox/novnc-auth.js +62 -0
- package/dist/agents/sandbox/sanitize-env-vars.js +1 -1
- package/dist/agents/sandbox/shared.js +10 -6
- package/dist/agents/sandbox-paths.js +24 -11
- package/dist/agents/schema/clean-for-gemini.js +132 -85
- package/dist/agents/session-slug.js +10 -5
- package/dist/agents/session-tool-result-guard-wrapper.js +1 -0
- package/dist/agents/session-tool-result-guard.js +3 -1
- package/dist/agents/session-transcript-repair.js +40 -6
- package/dist/agents/skills/bundled-dir.js +19 -5
- package/dist/agents/skills/env-overrides.js +124 -43
- package/dist/agents/skills/frontmatter.js +6 -6
- package/dist/agents/skills/plugin-skills.js +14 -7
- package/dist/agents/skills/workspace.js +1 -0
- package/dist/agents/skills.test-helpers.js +13 -0
- package/dist/agents/stable-stringify.js +12 -0
- package/dist/agents/subagent-announce.js +251 -49
- package/dist/agents/subagent-lifecycle-events.js +19 -0
- package/dist/agents/subagent-registry-cleanup.js +31 -0
- package/dist/agents/subagent-registry-completion.js +68 -0
- package/dist/agents/subagent-registry-queries.js +117 -0
- package/dist/agents/subagent-registry-state.js +46 -0
- package/dist/agents/subagent-registry.js +252 -221
- package/dist/agents/subagent-registry.mocks.shared.js +12 -0
- package/dist/agents/subagent-registry.store.js +1 -0
- package/dist/agents/subagent-registry.types.js +1 -0
- package/dist/agents/subagent-spawn.js +195 -7
- package/dist/agents/system-prompt.js +22 -6
- package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
- package/dist/agents/test-helpers/fast-coding-tools.js +1 -18
- package/dist/agents/test-helpers/fast-core-tools.js +1 -17
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
- package/dist/agents/timeout.js +18 -6
- package/dist/agents/tool-call-id.js +1 -1
- package/dist/agents/tool-display-common.js +162 -29
- package/dist/agents/tool-images.js +82 -9
- package/dist/agents/tool-policy-shared.js +108 -0
- package/dist/agents/tool-policy.js +51 -26
- package/dist/agents/tools/browser-tool.js +160 -54
- package/dist/agents/tools/canvas-tool.js +27 -1
- package/dist/agents/tools/common.js +45 -0
- package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
- package/dist/agents/tools/discord-actions-guild.js +4 -1
- package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
- package/dist/agents/tools/gateway-tool.js +3 -1
- package/dist/agents/tools/image-tool.js +214 -99
- package/dist/agents/tools/nodes-utils.js +1 -10
- package/dist/agents/tools/sessions-history-tool.js +140 -108
- package/dist/agents/tools/sessions-send-helpers.js +12 -6
- package/dist/agents/tools/sessions-spawn-tool.js +8 -2
- package/dist/agents/tools/subagents-tool.js +2 -1
- package/dist/agents/tools/whatsapp-actions.js +10 -2
- package/dist/agents/tools/whatsapp-target-auth.js +18 -0
- package/dist/agents/transcript-policy.js +22 -8
- package/dist/agents/venice-models.js +11 -3
- package/dist/agents/workspace.js +222 -46
- package/dist/auto-reply/commands-registry.data.js +51 -0
- package/dist/auto-reply/commands-registry.js +19 -21
- package/dist/auto-reply/fallback-state.js +114 -0
- package/dist/auto-reply/group-activation.js +10 -5
- package/dist/auto-reply/inbound-debounce.js +10 -5
- package/dist/auto-reply/model-runtime.js +68 -0
- package/dist/auto-reply/reply/abort.js +1 -1
- package/dist/auto-reply/reply/agent-runner-execution.js +40 -5
- package/dist/auto-reply/reply/agent-runner.js +165 -39
- package/dist/auto-reply/reply/bash-command.js +41 -39
- package/dist/auto-reply/reply/command-gates.js +25 -0
- package/dist/auto-reply/reply/commands-allowlist.js +111 -72
- package/dist/auto-reply/reply/commands-bash.js +6 -5
- package/dist/auto-reply/reply/commands-config.js +30 -28
- package/dist/auto-reply/reply/commands-core.js +2 -1
- package/dist/auto-reply/reply/commands-info.js +1 -0
- package/dist/auto-reply/reply/commands-models.js +65 -14
- package/dist/auto-reply/reply/commands-session.js +237 -82
- package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/auto-reply/reply/commands-setunset.js +45 -0
- package/dist/auto-reply/reply/commands-subagents/action-agents.js +44 -0
- package/dist/auto-reply/reply/commands-subagents/action-focus.js +64 -0
- package/dist/auto-reply/reply/commands-subagents/action-help.js +4 -0
- package/dist/auto-reply/reply/commands-subagents/action-info.js +45 -0
- package/dist/auto-reply/reply/commands-subagents/action-kill.js +60 -0
- package/dist/auto-reply/reply/commands-subagents/action-list.js +44 -0
- package/dist/auto-reply/reply/commands-subagents/action-log.js +29 -0
- package/dist/auto-reply/reply/commands-subagents/action-send.js +119 -0
- package/dist/auto-reply/reply/commands-subagents/action-spawn.js +52 -0
- package/dist/auto-reply/reply/commands-subagents/action-unfocus.js +30 -0
- package/dist/auto-reply/reply/commands-subagents/shared.js +303 -0
- package/dist/auto-reply/reply/commands-subagents.js +51 -587
- package/dist/auto-reply/reply/commands-tts.js +10 -5
- package/dist/auto-reply/reply/config-value.js +10 -5
- package/dist/auto-reply/reply/directive-handling.model-picker.js +12 -6
- package/dist/auto-reply/reply/directive-handling.persist.js +9 -21
- package/dist/auto-reply/reply/directive-handling.shared.js +24 -4
- package/dist/auto-reply/reply/followup-runner.js +1 -0
- package/dist/auto-reply/reply/get-reply-directives-utils.js +23 -14
- package/dist/auto-reply/reply/get-reply-directives.js +17 -28
- package/dist/auto-reply/reply/get-reply-inline-actions.js +1 -0
- package/dist/auto-reply/reply/get-reply.js +71 -12
- package/dist/auto-reply/reply/model-selection.js +80 -39
- package/dist/auto-reply/reply/queue/enqueue.js +10 -5
- package/dist/auto-reply/reply/queue/state.js +13 -12
- package/dist/auto-reply/reply/reply-payloads.js +67 -36
- package/dist/auto-reply/reply/reply-reference.js +9 -8
- package/dist/auto-reply/reply/route-reply.js +15 -8
- package/dist/auto-reply/reply/session-reset-prompt.js +1 -1
- package/dist/auto-reply/reply/session.js +22 -6
- package/dist/auto-reply/reply/strip-inbound-meta.js +147 -0
- package/dist/auto-reply/reply/subagents-utils.js +56 -30
- package/dist/auto-reply/reply/typing.js +46 -21
- package/dist/auto-reply/send-policy.js +14 -7
- package/dist/auto-reply/status.js +140 -16
- package/dist/auto-reply/templating.js +10 -5
- package/dist/auto-reply/thinking.js +7 -16
- package/dist/auto-reply/tokens.js +21 -5
- package/dist/browser/bridge-server.js +36 -20
- package/dist/browser/cdp.helpers.js +7 -14
- package/dist/browser/cdp.js +35 -15
- package/dist/browser/chrome.profile-decoration.js +7 -4
- package/dist/browser/config.js +30 -0
- package/dist/browser/extension-relay-auth.js +55 -0
- package/dist/browser/extension-relay.js +74 -29
- package/dist/browser/navigation-guard.js +39 -0
- package/dist/browser/paths.js +77 -0
- package/dist/browser/profiles.js +13 -8
- package/dist/browser/pw-ai-module.js +10 -5
- package/dist/browser/pw-session.js +76 -39
- package/dist/browser/pw-tools-core.interactions.js +14 -7
- package/dist/browser/pw-tools-core.state.js +12 -6
- 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-context.js +7 -0
- package/dist/browser/server-lifecycle.js +37 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/allow-from.js +26 -0
- package/dist/channels/allowlists/resolve-utils.js +43 -19
- package/dist/channels/channel-config.js +14 -7
- package/dist/channels/draft-stream-loop.js +7 -0
- package/dist/channels/model-overrides.js +82 -0
- package/dist/channels/plugins/account-action-gate.js +13 -0
- package/dist/channels/plugins/message-actions.js +10 -0
- package/dist/channels/plugins/normalize/imessage.js +14 -7
- package/dist/channels/plugins/normalize/slack.js +10 -5
- package/dist/channels/plugins/normalize/telegram.js +14 -7
- package/dist/channels/plugins/outbound/discord.js +80 -8
- package/dist/channels/plugins/outbound/signal.js +11 -11
- package/dist/channels/plugins/setup-helpers.js +10 -5
- package/dist/channels/sender-label.js +14 -7
- package/dist/channels/session.js +4 -2
- package/dist/channels/status-reactions.js +297 -0
- package/dist/channels/telegram/api.js +18 -0
- package/dist/cli/argv.js +84 -21
- package/dist/cli/banner.js +3 -2
- package/dist/cli/browser-cli-actions-input/register.files-downloads.js +65 -56
- package/dist/cli/cli-name.js +11 -11
- package/dist/cli/cli-utils.js +13 -3
- package/dist/cli/command-format.js +1 -1
- package/dist/cli/config-cli.js +1 -1
- package/dist/cli/daemon-cli/lifecycle-core.js +31 -19
- package/dist/cli/daemon-cli/lifecycle.js +64 -2
- package/dist/cli/daemon-cli/restart-health.js +126 -0
- package/dist/cli/daemon-cli/status.gather.js +9 -13
- package/dist/cli/daemon-cli/status.print.js +2 -10
- package/dist/cli/deps.js +27 -22
- package/dist/cli/exec-approvals-cli.js +92 -124
- package/dist/cli/gateway-cli/run-loop.js +23 -5
- package/dist/cli/memory-cli.js +158 -61
- package/dist/cli/node-cli/register.js +14 -5
- package/dist/cli/nodes-cli/register.push.js +63 -0
- package/dist/cli/nodes-media-utils.js +26 -0
- package/dist/cli/outbound-send-deps.js +2 -9
- package/dist/cli/outbound-send-mapping.js +11 -0
- package/dist/cli/pairing-cli.js +40 -14
- package/dist/cli/plugins-cli.js +250 -73
- package/dist/cli/ports.js +11 -10
- package/dist/cli/program/build-program.js +3 -1
- package/dist/cli/program/command-registry.js +214 -136
- package/dist/cli/program/command-tree.js +16 -0
- package/dist/cli/program/help.js +43 -12
- package/dist/cli/program/preaction.js +13 -9
- package/dist/cli/program/register.configure.js +3 -18
- package/dist/cli/program/register.maintenance.js +2 -2
- package/dist/cli/program/register.onboard.js +2 -0
- package/dist/cli/program/register.status-health-sessions.js +16 -17
- package/dist/cli/program/register.subclis.js +93 -52
- package/dist/cli/route.js +12 -8
- package/dist/cli/system-cli.js +36 -46
- package/dist/cli/test-runtime-capture.js +24 -0
- package/dist/cli/update-cli/shared.js +22 -9
- package/dist/cli/update-cli/update-command.js +89 -14
- package/dist/cli/update-cli/wizard.js +6 -12
- package/dist/commands/agent/run-context.js +18 -5
- package/dist/commands/agent/session-store.js +17 -4
- package/dist/commands/agent.js +185 -89
- package/dist/commands/agents.bindings.js +14 -7
- package/dist/commands/agents.commands.add.js +13 -9
- package/dist/commands/agents.commands.identity.js +12 -6
- package/dist/commands/agents.commands.list.js +11 -6
- package/dist/commands/agents.config.js +8 -10
- package/dist/commands/agents.providers.js +12 -6
- package/dist/commands/auth-choice-options.js +103 -75
- package/dist/commands/auth-choice.apply.byteplus.js +55 -0
- package/dist/commands/auth-choice.apply.js +4 -0
- package/dist/commands/auth-choice.apply.minimax.js +61 -13
- package/dist/commands/auth-choice.apply.openai.js +3 -1
- package/dist/commands/auth-choice.apply.volcengine.js +55 -0
- package/dist/commands/auth-choice.preferred-provider.js +2 -0
- package/dist/commands/channels/remove.js +13 -6
- package/dist/commands/channels/shared.js +4 -14
- package/dist/commands/channels.mock-harness.js +23 -0
- package/dist/commands/configure.commands.js +14 -0
- package/dist/commands/configure.gateway.js +2 -4
- package/dist/commands/configure.js +1 -1
- package/dist/commands/configure.shared.js +11 -0
- package/dist/commands/daemon-install-helpers.js +2 -2
- package/dist/commands/daemon-install-runtime-warning.js +11 -0
- package/dist/commands/dashboard.js +12 -10
- package/dist/commands/docs.js +14 -8
- package/dist/commands/doctor-config-flow.js +11 -9
- package/dist/commands/doctor-legacy-config.js +281 -0
- package/dist/commands/doctor-state-integrity.js +99 -23
- package/dist/commands/doctor-update.js +12 -9
- package/dist/commands/models/list.list-command.js +7 -5
- package/dist/commands/models/set-image.js +2 -21
- package/dist/commands/node-daemon-install-helpers.js +10 -8
- package/dist/commands/onboard-auth.config-minimax.js +54 -80
- package/dist/commands/onboard-auth.config-opencode.js +2 -18
- package/dist/commands/onboard-auth.credentials.js +90 -13
- package/dist/commands/onboard-auth.js +1 -1
- package/dist/commands/onboard-auth.models.js +6 -5
- package/dist/commands/onboard-hooks.js +1 -1
- package/dist/commands/onboard-non-interactive/api-keys.js +14 -7
- package/dist/commands/onboard-non-interactive/local/auth-choice.js +64 -49
- package/dist/commands/onboard-provider-auth-flags.js +14 -0
- package/dist/commands/onboard-remote.js +14 -7
- package/dist/commands/onboard.js +11 -13
- package/dist/commands/sandbox-display.js +6 -5
- package/dist/commands/sessions.test-helpers.js +61 -0
- package/dist/commands/status-all/diagnosis.js +14 -10
- package/dist/commands/status-all/format.js +1 -0
- package/dist/commands/status.gateway-probe.js +1 -16
- package/dist/commands/systemd-linger.js +12 -6
- package/dist/config/agent-limits.js +2 -0
- package/dist/config/commands.js +32 -15
- package/dist/config/config-paths.js +9 -11
- package/dist/config/config.js +1 -1
- package/dist/config/defaults.js +22 -2
- package/dist/config/discord-preview-streaming.js +104 -0
- package/dist/config/env-substitution.js +62 -34
- package/dist/config/env-vars.js +45 -7
- package/dist/config/includes.js +4 -0
- package/dist/config/io.js +656 -171
- package/dist/config/legacy.migrations.part-1.js +189 -78
- package/dist/config/legacy.shared.js +3 -1
- package/dist/config/merge-patch.js +54 -4
- package/dist/config/prototype-keys.js +4 -0
- package/dist/config/redact-snapshot.js +404 -76
- package/dist/config/schema.help.js +44 -7
- package/dist/config/schema.js +58 -570
- package/dist/config/schema.labels.js +38 -6
- package/dist/config/sessions/delivery-info.js +10 -3
- package/dist/config/sessions/main-session.js +10 -5
- package/dist/config/sessions/session-file.js +33 -0
- package/dist/config/sessions/session-key.js +10 -5
- package/dist/config/sessions/store.js +1 -1
- package/dist/config/sessions.js +1 -0
- package/dist/config/validation.js +140 -85
- package/dist/config/zod-schema.agent-runtime.js +11 -0
- package/dist/config/zod-schema.hooks.js +40 -11
- package/dist/config/zod-schema.installs.js +20 -0
- package/dist/config/zod-schema.js +156 -20
- package/dist/config/zod-schema.providers-core.js +78 -4
- package/dist/config/zod-schema.providers.js +6 -1
- package/dist/config/zod-schema.session.js +41 -2
- package/dist/cron/run-log.js +3 -0
- package/dist/cron/schedule.js +21 -10
- package/dist/cron/service/ops.js +35 -21
- package/dist/cron/service/timer.js +116 -16
- package/dist/cron/stagger.js +3 -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/api.js +12 -6
- package/dist/discord/draft-chunking.js +22 -0
- package/dist/discord/draft-stream.js +124 -0
- package/dist/discord/monitor/agent-components.js +1 -1
- package/dist/discord/monitor/commands.js +5 -0
- package/dist/discord/monitor/exec-approvals.js +357 -162
- package/dist/discord/monitor/gateway-plugin.js +2 -1
- package/dist/discord/monitor/listeners.js +37 -27
- package/dist/discord/monitor/message-handler.js +4 -1
- package/dist/discord/monitor/message-handler.preflight.js +65 -8
- package/dist/discord/monitor/message-handler.process.js +246 -217
- package/dist/discord/monitor/message-utils.js +143 -6
- package/dist/discord/monitor/model-picker-preferences.js +143 -0
- package/dist/discord/monitor/model-picker.js +651 -0
- package/dist/discord/monitor/native-command.js +573 -16
- package/dist/discord/monitor/provider.allowlist.js +223 -0
- package/dist/discord/monitor/provider.js +275 -347
- package/dist/discord/monitor/provider.lifecycle.js +100 -0
- package/dist/discord/monitor/reply-delivery.js +123 -16
- package/dist/discord/monitor/thread-bindings.discord-api.js +215 -0
- package/dist/discord/monitor/thread-bindings.js +4 -0
- package/dist/discord/monitor/thread-bindings.lifecycle.js +177 -0
- package/dist/discord/monitor/thread-bindings.manager.js +423 -0
- package/dist/discord/monitor/thread-bindings.messages.js +55 -0
- package/dist/discord/monitor/thread-bindings.state.js +358 -0
- package/dist/discord/monitor/thread-bindings.types.js +6 -0
- package/dist/discord/resolve-users.js +33 -21
- package/dist/discord/send.channels.js +15 -0
- package/dist/discord/send.js +3 -2
- package/dist/discord/send.outbound.js +82 -26
- package/dist/discord/send.permissions.js +83 -30
- package/dist/discord/send.reactions.js +8 -4
- package/dist/discord/token.js +10 -5
- package/dist/discord/voice/command.js +263 -0
- package/dist/discord/voice/manager.js +531 -0
- package/dist/gateway/auth.js +72 -13
- package/dist/gateway/call.js +152 -83
- package/dist/gateway/canvas-capability.js +75 -0
- package/dist/gateway/client.js +28 -4
- package/dist/gateway/config-reload.js +3 -4
- package/dist/gateway/control-plane-audit.js +28 -0
- package/dist/gateway/control-plane-rate-limit.js +53 -0
- package/dist/gateway/control-ui.js +219 -96
- package/dist/gateway/events.js +1 -0
- package/dist/gateway/hooks-mapping.js +88 -38
- package/dist/gateway/hooks.js +109 -54
- package/dist/gateway/http-auth-helpers.js +3 -2
- package/dist/gateway/http-common.js +22 -0
- package/dist/gateway/http-endpoint-helpers.js +1 -0
- package/dist/gateway/method-scopes.js +169 -0
- package/dist/gateway/net.js +74 -9
- package/dist/gateway/node-invoke-system-run-approval.js +14 -35
- package/dist/gateway/node-registry.js +10 -5
- package/dist/gateway/openai-http.js +1 -0
- package/dist/gateway/openresponses-http.js +121 -110
- package/dist/gateway/origin-check.js +1 -18
- package/dist/gateway/probe-auth.js +2 -0
- package/dist/gateway/protocol/index.js +4 -2
- package/dist/gateway/protocol/schema/cron.js +1 -0
- package/dist/gateway/protocol/schema/devices.js +1 -0
- package/dist/gateway/protocol/schema/protocol-schemas.js +4 -1
- package/dist/gateway/protocol/schema/push.js +18 -0
- package/dist/gateway/protocol/schema/sessions.js +6 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/role-policy.js +17 -0
- package/dist/gateway/server/ws-connection/connect-policy.js +37 -0
- package/dist/gateway/server/ws-connection/message-handler.js +175 -148
- package/dist/gateway/server-chat.js +83 -25
- package/dist/gateway/server-constants.js +10 -9
- package/dist/gateway/server-cron.js +1 -0
- package/dist/gateway/server-http.js +247 -54
- package/dist/gateway/server-maintenance.js +20 -5
- package/dist/gateway/server-methods/agent.js +162 -24
- package/dist/gateway/server-methods/chat.js +465 -130
- package/dist/gateway/server-methods/config.js +193 -152
- package/dist/gateway/server-methods/devices.js +17 -3
- package/dist/gateway/server-methods/models.js +11 -1
- 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-methods/sessions.js +64 -8
- package/dist/gateway/server-methods/usage.js +162 -75
- package/dist/gateway/server-node-events.js +29 -0
- package/dist/gateway/server-reload-handlers.js +2 -3
- package/dist/gateway/server-runtime-config.js +39 -13
- package/dist/gateway/server-runtime-state.js +2 -0
- package/dist/gateway/server-startup-memory.js +17 -11
- 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/session-utils.fs.js +32 -34
- package/dist/gateway/sessions-resolve.js +17 -5
- 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.openai-mock.js +14 -7
- package/dist/gateway/test-helpers.server.js +161 -77
- package/dist/gateway/tools-invoke-http.js +21 -10
- package/dist/hooks/bundled/bootstrap-extra-files/handler.js +3 -1
- package/dist/hooks/bundled/command-logger/handler.js +7 -2
- package/dist/hooks/bundled/session-memory/handler.js +170 -38
- package/dist/hooks/frontmatter.js +6 -6
- package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
- package/dist/hooks/gmail-watcher.js +11 -6
- package/dist/hooks/internal-hooks.js +11 -1
- package/dist/hooks/llm-slug-generator.js +4 -1
- package/dist/hooks/workspace.js +47 -17
- package/dist/imessage/accounts.js +9 -20
- package/dist/imessage/monitor/inbound-processing.js +2 -1
- package/dist/infra/archive-path.js +49 -0
- package/dist/infra/archive.js +174 -73
- package/dist/infra/control-ui-assets.js +14 -6
- package/dist/infra/device-pairing.js +204 -144
- package/dist/infra/env.js +10 -5
- package/dist/infra/exec-approvals-allowlist.js +141 -70
- package/dist/infra/exec-approvals-analysis.js +78 -20
- package/dist/infra/exec-approvals.js +5 -17
- package/dist/infra/exec-safe-bin-policy.js +277 -0
- package/dist/infra/fixed-window-rate-limit.js +33 -0
- package/dist/infra/fs-safe.js +71 -39
- package/dist/infra/gateway-lock.js +6 -2
- 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/heartbeat-wake.js +6 -12
- package/dist/infra/host-env-security-policy.json +19 -0
- package/dist/infra/host-env-security.js +66 -0
- package/dist/infra/install-source-utils.js +91 -7
- package/dist/infra/net/ssrf.js +131 -38
- 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/bound-delivery-router.js +88 -0
- package/dist/infra/outbound/channel-adapters.js +20 -7
- package/dist/infra/outbound/channel-selection.js +12 -6
- package/dist/infra/outbound/envelope.js +1 -1
- package/dist/infra/outbound/format.js +12 -6
- 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/outbound/payloads.js +14 -7
- package/dist/infra/outbound/session-binding-service.js +123 -0
- package/dist/infra/pairing-files.js +10 -0
- package/dist/infra/path-guards.js +25 -0
- package/dist/infra/plain-object.js +9 -0
- package/dist/infra/provider-usage.fetch.codex.js +7 -15
- package/dist/infra/provider-usage.fetch.gemini.js +14 -11
- package/dist/infra/provider-usage.fetch.shared.js +30 -1
- package/dist/infra/provider-usage.fetch.zai.js +10 -9
- 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/retry-policy.js +4 -2
- package/dist/infra/retry.js +9 -5
- package/dist/infra/scp-host.js +54 -0
- package/dist/infra/session-cost-usage.js +107 -59
- package/dist/infra/session-maintenance-warning.js +3 -1
- package/dist/infra/shell-env.js +98 -34
- package/dist/infra/ssh-config.js +12 -6
- package/dist/infra/system-run-command.js +49 -4
- package/dist/infra/update-channels.js +10 -5
- package/dist/infra/update-startup.js +86 -9
- package/dist/line/accounts.js +5 -7
- package/dist/line/bot-access.js +8 -20
- package/dist/line/bot-handlers.js +3 -1
- package/dist/link-understanding/detect.js +15 -7
- package/dist/media/constants.js +15 -6
- package/dist/media/image-ops.js +7 -0
- package/dist/media/inbound-path-policy.js +114 -0
- package/dist/media/input-files.js +16 -0
- package/dist/media/local-roots.js +3 -2
- package/dist/media-understanding/apply.js +4 -1
- package/dist/media-understanding/concurrency.js +8 -20
- package/dist/memory/backend-config.js +45 -6
- package/dist/memory/embeddings.js +10 -4
- package/dist/memory/fs-utils.js +23 -0
- package/dist/memory/manager-search.js +12 -6
- package/dist/memory/manager-sync-ops.js +12 -2
- package/dist/memory/qmd-manager.js +466 -53
- package/dist/memory/query-expansion.js +167 -3
- package/dist/memory/status-format.js +10 -5
- package/dist/memory/sync-memory-files.js +1 -1
- package/dist/memory/test-manager.js +8 -0
- package/dist/node-host/invoke-system-run.js +281 -0
- package/dist/node-host/invoke.js +55 -337
- package/dist/pairing/pairing-store.js +22 -0
- package/dist/plugin-sdk/allow-from.js +1 -1
- package/dist/plugin-sdk/command-auth.js +3 -1
- package/dist/plugin-sdk/index.js +6 -3
- package/dist/plugin-sdk/temp-path.js +47 -0
- package/dist/plugin-sdk/webhook-targets.js +32 -0
- package/dist/plugins/bundled-dir.js +9 -6
- package/dist/plugins/discovery.js +217 -23
- package/dist/plugins/hook-runner-global.js +16 -0
- package/dist/plugins/hooks.js +50 -0
- package/dist/plugins/install.js +28 -16
- 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/plugins/runtime.js +3 -17
- package/dist/plugins/update.js +78 -12
- package/dist/process/spawn-utils.js +14 -7
- package/dist/providers/github-copilot-models.js +4 -1
- package/dist/providers/github-copilot-token.js +11 -6
- package/dist/providers/qwen-portal-oauth.js +14 -6
- package/dist/routing/account-id.js +30 -0
- package/dist/routing/resolve-route.js +3 -7
- package/dist/routing/session-key.js +2 -16
- package/dist/security/audit-channel.js +100 -20
- package/dist/security/audit-extra.async.js +505 -179
- package/dist/security/audit-extra.js +12 -2
- package/dist/security/audit-extra.sync.js +421 -35
- package/dist/security/audit-fs.js +31 -13
- package/dist/security/audit.js +180 -370
- package/dist/security/dm-policy-shared.js +68 -0
- package/dist/security/external-content.js +46 -14
- 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/entry-status.js +6 -0
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-list-parse.js +13 -0
- package/dist/shared/node-match.js +11 -4
- package/dist/shared/operator-scope-compat.js +42 -0
- package/dist/shared/text-chunking.js +29 -0
- package/dist/signal/accounts.js +7 -20
- package/dist/signal/monitor/event-handler.js +3 -1
- package/dist/slack/accounts.js +6 -19
- package/dist/slack/actions.js +11 -3
- package/dist/slack/blocks.test-helpers.js +31 -0
- package/dist/slack/monitor/auth.js +1 -1
- package/dist/slack/monitor/message-handler/dispatch.js +50 -29
- package/dist/slack/monitor/mrkdwn.js +8 -0
- package/dist/slack/monitor/replies.js +15 -7
- package/dist/slack/monitor/slash.js +22 -13
- package/dist/slack/resolve-channels.js +10 -5
- package/dist/slack/send.js +102 -12
- package/dist/slack/stream-mode.js +10 -0
- package/dist/slack/streaming.js +4 -2
- package/dist/telegram/accounts.js +19 -14
- package/dist/telegram/bot/helpers.js +3 -5
- package/dist/telegram/bot-access.js +35 -36
- package/dist/telegram/bot-handlers.js +120 -148
- package/dist/telegram/bot-message-context.js +68 -9
- package/dist/telegram/bot-message-dispatch.js +477 -210
- package/dist/telegram/bot-native-commands.js +16 -0
- package/dist/telegram/draft-stream.js +44 -8
- package/dist/telegram/inline-buttons.js +5 -15
- package/dist/telegram/monitor.js +11 -7
- package/dist/telegram/network-config.js +19 -7
- package/dist/telegram/reasoning-lane-coordinator.js +128 -0
- package/dist/telegram/send.js +3 -2
- package/dist/telegram/sent-message-cache.js +5 -6
- package/dist/telegram/status-reaction-variants.js +208 -0
- package/dist/telegram/sticker-cache.js +11 -9
- package/dist/terminal/prompt-select-styled.js +9 -0
- package/dist/terminal/theme.js +12 -12
- 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/tts/tts.js +80 -567
- package/dist/tui/components/chat-log.js +50 -8
- package/dist/tui/theme/theme.js +10 -12
- package/dist/tui/tui-command-handlers.js +36 -27
- package/dist/tui/tui-event-handlers.js +122 -32
- package/dist/tui/tui-local-shell.js +16 -6
- package/dist/tui/tui.js +236 -48
- package/dist/utils/account-id.js +2 -4
- package/dist/utils/boolean.js +10 -5
- package/dist/utils/directive-tags.js +11 -0
- package/dist/utils/mask-api-key.js +10 -0
- package/dist/utils/queue-helpers.js +67 -12
- package/dist/utils/run-with-concurrency.js +39 -0
- package/dist/web/auto-reply/deliver-reply.js +8 -4
- package/dist/web/auto-reply/mentions.js +10 -5
- package/dist/web/auto-reply/monitor/group-members.js +14 -7
- package/dist/web/auto-reply/monitor/process-message.js +45 -24
- package/dist/web/inbound/access-control.js +5 -2
- package/dist/web/login-qr.js +12 -6
- package/dist/web/media.js +126 -15
- package/docs/tools/slash-commands.md +5 -1
- package/extensions/bluebubbles/src/monitor-processing.ts +580 -139
- package/extensions/bluebubbles/src/monitor.ts +208 -1950
- package/extensions/feishu/src/external-keys.ts +19 -0
- package/extensions/lobster/src/windows-spawn.ts +193 -0
- package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
- package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
- package/package.json +1 -1
|
@@ -3,19 +3,28 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These functions perform I/O (filesystem, config reads) to detect security issues.
|
|
5
5
|
*/
|
|
6
|
-
import JSON5 from "json5";
|
|
7
6
|
import fs from "node:fs/promises";
|
|
8
7
|
import path from "node:path";
|
|
9
|
-
import {
|
|
8
|
+
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
9
|
+
import { isToolAllowedByPolicies } from "../agents/pi-tools.policy.js";
|
|
10
|
+
import { resolveSandboxConfigForAgent, resolveSandboxToolPolicyForAgent, } from "../agents/sandbox.js";
|
|
11
|
+
import { SANDBOX_BROWSER_SECURITY_HASH_EPOCH } from "../agents/sandbox/constants.js";
|
|
12
|
+
import { execDockerRaw } from "../agents/sandbox/docker.js";
|
|
10
13
|
import { loadWorkspaceSkillEntries } from "../agents/skills.js";
|
|
11
|
-
import {
|
|
14
|
+
import { resolveToolProfilePolicy } from "../agents/tool-policy.js";
|
|
15
|
+
import { listAgentWorkspaceDirs } from "../agents/workspace-dirs.js";
|
|
16
|
+
import { formatCliCommand } from "../cli/command-format.js";
|
|
17
|
+
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
|
12
18
|
import { resolveNativeSkillsEnabled } from "../config/commands.js";
|
|
13
19
|
import { createConfigIO } from "../config/config.js";
|
|
14
|
-
import {
|
|
20
|
+
import { collectIncludePathsRecursive } from "../config/includes-scan.js";
|
|
15
21
|
import { resolveOAuthDir } from "../config/paths.js";
|
|
22
|
+
import { normalizePluginsConfig } from "../plugins/config-state.js";
|
|
16
23
|
import { normalizeAgentId } from "../routing/session-key.js";
|
|
17
24
|
import { formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, safeStat, } from "./audit-fs.js";
|
|
18
|
-
import {
|
|
25
|
+
import { pickSandboxToolPolicy } from "./audit-tool-policy.js";
|
|
26
|
+
import { extensionUsesSkippedScannerPath, isPathInside } from "./scan-paths.js";
|
|
27
|
+
import * as skillScanner from "./skill-scanner.js";
|
|
19
28
|
// --------------------------------------------------------------------------
|
|
20
29
|
// Helpers
|
|
21
30
|
// --------------------------------------------------------------------------
|
|
@@ -35,91 +44,6 @@ function expandTilde(p, env) {
|
|
|
35
44
|
}
|
|
36
45
|
return null;
|
|
37
46
|
}
|
|
38
|
-
function resolveIncludePath(baseConfigPath, includePath) {
|
|
39
|
-
return path.normalize(path.isAbsolute(includePath)
|
|
40
|
-
? includePath
|
|
41
|
-
: path.resolve(path.dirname(baseConfigPath), includePath));
|
|
42
|
-
}
|
|
43
|
-
function listDirectIncludes(parsed) {
|
|
44
|
-
const out = [];
|
|
45
|
-
const visit = (value) => {
|
|
46
|
-
if (!value) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (Array.isArray(value)) {
|
|
50
|
-
for (const item of value) {
|
|
51
|
-
visit(item);
|
|
52
|
-
}
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (typeof value !== "object") {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
const rec = value;
|
|
59
|
-
const includeVal = rec[INCLUDE_KEY];
|
|
60
|
-
if (typeof includeVal === "string") {
|
|
61
|
-
out.push(includeVal);
|
|
62
|
-
}
|
|
63
|
-
else if (Array.isArray(includeVal)) {
|
|
64
|
-
for (const item of includeVal) {
|
|
65
|
-
if (typeof item === "string") {
|
|
66
|
-
out.push(item);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
for (const v of Object.values(rec)) {
|
|
71
|
-
visit(v);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
visit(parsed);
|
|
75
|
-
return out;
|
|
76
|
-
}
|
|
77
|
-
async function collectIncludePathsRecursive(params) {
|
|
78
|
-
const visited = new Set();
|
|
79
|
-
const result = [];
|
|
80
|
-
const walk = async (basePath, parsed, depth) => {
|
|
81
|
-
if (depth > MAX_INCLUDE_DEPTH) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
for (const raw of listDirectIncludes(parsed)) {
|
|
85
|
-
const resolved = resolveIncludePath(basePath, raw);
|
|
86
|
-
if (visited.has(resolved)) {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
visited.add(resolved);
|
|
90
|
-
result.push(resolved);
|
|
91
|
-
const rawText = await fs.readFile(resolved, "utf-8").catch(() => null);
|
|
92
|
-
if (!rawText) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
const nestedParsed = (() => {
|
|
96
|
-
try {
|
|
97
|
-
return JSON5.parse(rawText);
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
})();
|
|
103
|
-
if (nestedParsed) {
|
|
104
|
-
// eslint-disable-next-line no-await-in-loop
|
|
105
|
-
await walk(resolved, nestedParsed, depth + 1);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
await walk(params.configPath, params.parsed, 0);
|
|
110
|
-
return result;
|
|
111
|
-
}
|
|
112
|
-
function isPathInside(basePath, candidatePath) {
|
|
113
|
-
const base = path.resolve(basePath);
|
|
114
|
-
const candidate = path.resolve(candidatePath);
|
|
115
|
-
const rel = path.relative(base, candidate);
|
|
116
|
-
return rel === "" || (!rel.startsWith(`..${path.sep}`) && rel !== ".." && !path.isAbsolute(rel));
|
|
117
|
-
}
|
|
118
|
-
function extensionUsesSkippedScannerPath(entry) {
|
|
119
|
-
const segments = entry.split(/[\\/]+/).filter(Boolean);
|
|
120
|
-
return segments.some((segment) => segment === "node_modules" ||
|
|
121
|
-
(segment.startsWith(".") && segment !== "." && segment !== ".."));
|
|
122
|
-
}
|
|
123
47
|
async function readPluginManifestExtensions(pluginPath) {
|
|
124
48
|
const manifestPath = path.join(pluginPath, "package.json");
|
|
125
49
|
const raw = await fs.readFile(manifestPath, "utf-8").catch(() => "");
|
|
@@ -127,25 +51,12 @@ async function readPluginManifestExtensions(pluginPath) {
|
|
|
127
51
|
return [];
|
|
128
52
|
}
|
|
129
53
|
const parsed = JSON.parse(raw);
|
|
130
|
-
const extensions = parsed?.[
|
|
54
|
+
const extensions = parsed?.[MANIFEST_KEY]?.extensions;
|
|
131
55
|
if (!Array.isArray(extensions)) {
|
|
132
56
|
return [];
|
|
133
57
|
}
|
|
134
58
|
return extensions.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
|
135
59
|
}
|
|
136
|
-
function listWorkspaceDirs(cfg) {
|
|
137
|
-
const dirs = new Set();
|
|
138
|
-
const list = cfg.agents?.list;
|
|
139
|
-
if (Array.isArray(list)) {
|
|
140
|
-
for (const entry of list) {
|
|
141
|
-
if (entry && typeof entry === "object" && typeof entry.id === "string") {
|
|
142
|
-
dirs.add(resolveAgentWorkspaceDir(cfg, entry.id));
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
dirs.add(resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)));
|
|
147
|
-
return [...dirs];
|
|
148
|
-
}
|
|
149
60
|
function formatCodeSafetyDetails(findings, rootDir) {
|
|
150
61
|
return findings
|
|
151
62
|
.map((finding) => {
|
|
@@ -158,74 +69,491 @@ function formatCodeSafetyDetails(findings, rootDir) {
|
|
|
158
69
|
})
|
|
159
70
|
.join("\n");
|
|
160
71
|
}
|
|
161
|
-
|
|
162
|
-
// Exported collectors
|
|
163
|
-
// --------------------------------------------------------------------------
|
|
164
|
-
export async function collectPluginsTrustFindings(params) {
|
|
165
|
-
const findings = [];
|
|
72
|
+
async function listInstalledPluginDirs(params) {
|
|
166
73
|
const extensionsDir = path.join(params.stateDir, "extensions");
|
|
167
74
|
const st = await safeStat(extensionsDir);
|
|
168
75
|
if (!st.ok || !st.isDir) {
|
|
169
|
-
return
|
|
76
|
+
return { extensionsDir, pluginDirs: [] };
|
|
170
77
|
}
|
|
171
|
-
const entries = await fs.readdir(extensionsDir, { withFileTypes: true }).catch(() =>
|
|
78
|
+
const entries = await fs.readdir(extensionsDir, { withFileTypes: true }).catch((err) => {
|
|
79
|
+
params.onReadError?.(err);
|
|
80
|
+
return [];
|
|
81
|
+
});
|
|
172
82
|
const pluginDirs = entries
|
|
173
|
-
.filter((
|
|
174
|
-
.map((
|
|
83
|
+
.filter((entry) => entry.isDirectory())
|
|
84
|
+
.map((entry) => entry.name)
|
|
175
85
|
.filter(Boolean);
|
|
176
|
-
|
|
86
|
+
return { extensionsDir, pluginDirs };
|
|
87
|
+
}
|
|
88
|
+
function resolveToolPolicies(params) {
|
|
89
|
+
const profile = params.agentTools?.profile ?? params.cfg.tools?.profile;
|
|
90
|
+
const profilePolicy = resolveToolProfilePolicy(profile);
|
|
91
|
+
const policies = [
|
|
92
|
+
profilePolicy,
|
|
93
|
+
pickSandboxToolPolicy(params.cfg.tools ?? undefined),
|
|
94
|
+
pickSandboxToolPolicy(params.agentTools),
|
|
95
|
+
];
|
|
96
|
+
if (params.sandboxMode === "all") {
|
|
97
|
+
policies.push(resolveSandboxToolPolicyForAgent(params.cfg, params.agentId ?? undefined));
|
|
98
|
+
}
|
|
99
|
+
return policies;
|
|
100
|
+
}
|
|
101
|
+
function normalizePluginIdSet(entries) {
|
|
102
|
+
return new Set(entries.map((entry) => entry.trim().toLowerCase()).filter(Boolean));
|
|
103
|
+
}
|
|
104
|
+
function resolveEnabledExtensionPluginIds(params) {
|
|
105
|
+
const normalized = normalizePluginsConfig(params.cfg.plugins);
|
|
106
|
+
if (!normalized.enabled) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
const allowSet = normalizePluginIdSet(normalized.allow);
|
|
110
|
+
const denySet = normalizePluginIdSet(normalized.deny);
|
|
111
|
+
const entryById = new Map();
|
|
112
|
+
for (const [id, entry] of Object.entries(normalized.entries)) {
|
|
113
|
+
entryById.set(id.trim().toLowerCase(), entry);
|
|
114
|
+
}
|
|
115
|
+
const enabled = [];
|
|
116
|
+
for (const id of params.pluginDirs) {
|
|
117
|
+
const normalizedId = id.trim().toLowerCase();
|
|
118
|
+
if (!normalizedId) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (denySet.has(normalizedId)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (allowSet.size > 0 && !allowSet.has(normalizedId)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (entryById.get(normalizedId)?.enabled === false) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
enabled.push(normalizedId);
|
|
131
|
+
}
|
|
132
|
+
return enabled;
|
|
133
|
+
}
|
|
134
|
+
function collectAllowEntries(config) {
|
|
135
|
+
const out = [];
|
|
136
|
+
if (Array.isArray(config?.allow)) {
|
|
137
|
+
out.push(...config.allow);
|
|
138
|
+
}
|
|
139
|
+
if (Array.isArray(config?.alsoAllow)) {
|
|
140
|
+
out.push(...config.alsoAllow);
|
|
141
|
+
}
|
|
142
|
+
return out.map((entry) => entry.trim().toLowerCase()).filter(Boolean);
|
|
143
|
+
}
|
|
144
|
+
function hasExplicitPluginAllow(params) {
|
|
145
|
+
return params.allowEntries.some((entry) => entry === "group:plugins" || params.enabledPluginIds.has(entry));
|
|
146
|
+
}
|
|
147
|
+
function hasProviderPluginAllow(params) {
|
|
148
|
+
if (!params.byProvider) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
for (const policy of Object.values(params.byProvider)) {
|
|
152
|
+
if (hasExplicitPluginAllow({
|
|
153
|
+
allowEntries: collectAllowEntries(policy),
|
|
154
|
+
enabledPluginIds: params.enabledPluginIds,
|
|
155
|
+
})) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
function isPinnedRegistrySpec(spec) {
|
|
162
|
+
const value = spec.trim();
|
|
163
|
+
if (!value) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
const at = value.lastIndexOf("@");
|
|
167
|
+
if (at <= 0 || at >= value.length - 1) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const version = value.slice(at + 1).trim();
|
|
171
|
+
return /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version);
|
|
172
|
+
}
|
|
173
|
+
async function readInstalledPackageVersion(dir) {
|
|
174
|
+
try {
|
|
175
|
+
const raw = await fs.readFile(path.join(dir, "package.json"), "utf-8");
|
|
176
|
+
const parsed = JSON.parse(raw);
|
|
177
|
+
return typeof parsed.version === "string" ? parsed.version : undefined;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// --------------------------------------------------------------------------
|
|
184
|
+
// Exported collectors
|
|
185
|
+
// --------------------------------------------------------------------------
|
|
186
|
+
function normalizeDockerLabelValue(raw) {
|
|
187
|
+
const trimmed = raw?.trim() ?? "";
|
|
188
|
+
if (!trimmed || trimmed === "<no value>") {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return trimmed;
|
|
192
|
+
}
|
|
193
|
+
async function listSandboxBrowserContainers(execDockerRawFn) {
|
|
194
|
+
try {
|
|
195
|
+
const result = await execDockerRawFn(["ps", "-a", "--filter", "label=poolbot.sandboxBrowser=1", "--format", "{{.Names}}"], { allowFailure: true });
|
|
196
|
+
if (result.code !== 0) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
return result.stdout
|
|
200
|
+
.toString("utf8")
|
|
201
|
+
.split(/\r?\n/)
|
|
202
|
+
.map((entry) => entry.trim())
|
|
203
|
+
.filter(Boolean);
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function readSandboxBrowserHashLabels(params) {
|
|
210
|
+
try {
|
|
211
|
+
const result = await params.execDockerRawFn([
|
|
212
|
+
"inspect",
|
|
213
|
+
"-f",
|
|
214
|
+
'{{ index .Config.Labels "poolbot.configHash" }}\t{{ index .Config.Labels "poolbot.browserConfigEpoch" }}',
|
|
215
|
+
params.containerName,
|
|
216
|
+
], { allowFailure: true });
|
|
217
|
+
if (result.code !== 0) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const [hashRaw, epochRaw] = result.stdout.toString("utf8").split("\t");
|
|
221
|
+
return {
|
|
222
|
+
configHash: normalizeDockerLabelValue(hashRaw),
|
|
223
|
+
epoch: normalizeDockerLabelValue(epochRaw),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function parsePublishedHostFromDockerPortLine(line) {
|
|
231
|
+
const trimmed = line.trim();
|
|
232
|
+
const rhs = trimmed.includes("->") ? (trimmed.split("->").at(-1)?.trim() ?? "") : trimmed;
|
|
233
|
+
if (!rhs) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const bracketHost = rhs.match(/^\[([^\]]+)\]:\d+$/);
|
|
237
|
+
if (bracketHost?.[1]) {
|
|
238
|
+
return bracketHost[1];
|
|
239
|
+
}
|
|
240
|
+
const hostPort = rhs.match(/^([^:]+):\d+$/);
|
|
241
|
+
if (hostPort?.[1]) {
|
|
242
|
+
return hostPort[1];
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
function isLoopbackPublishHost(host) {
|
|
247
|
+
const normalized = host.trim().toLowerCase();
|
|
248
|
+
return normalized === "127.0.0.1" || normalized === "::1" || normalized === "localhost";
|
|
249
|
+
}
|
|
250
|
+
async function readSandboxBrowserPortMappings(params) {
|
|
251
|
+
try {
|
|
252
|
+
const result = await params.execDockerRawFn(["port", params.containerName], {
|
|
253
|
+
allowFailure: true,
|
|
254
|
+
});
|
|
255
|
+
if (result.code !== 0) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
return result.stdout
|
|
259
|
+
.toString("utf8")
|
|
260
|
+
.split(/\r?\n/)
|
|
261
|
+
.map((entry) => entry.trim())
|
|
262
|
+
.filter(Boolean);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
export async function collectSandboxBrowserHashLabelFindings(params) {
|
|
269
|
+
const findings = [];
|
|
270
|
+
const execFn = params?.execDockerRawFn ?? execDockerRaw;
|
|
271
|
+
const containers = await listSandboxBrowserContainers(execFn);
|
|
272
|
+
if (!containers || containers.length === 0) {
|
|
177
273
|
return findings;
|
|
178
274
|
}
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
275
|
+
const missingHash = [];
|
|
276
|
+
const staleEpoch = [];
|
|
277
|
+
const nonLoopbackPublished = [];
|
|
278
|
+
for (const containerName of containers) {
|
|
279
|
+
const labels = await readSandboxBrowserHashLabels({ containerName, execDockerRawFn: execFn });
|
|
280
|
+
if (!labels) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (!labels.configHash) {
|
|
284
|
+
missingHash.push(containerName);
|
|
285
|
+
}
|
|
286
|
+
if (labels.epoch !== SANDBOX_BROWSER_SECURITY_HASH_EPOCH) {
|
|
287
|
+
staleEpoch.push(containerName);
|
|
288
|
+
}
|
|
289
|
+
const portMappings = await readSandboxBrowserPortMappings({
|
|
290
|
+
containerName,
|
|
291
|
+
execDockerRawFn: execFn,
|
|
292
|
+
});
|
|
293
|
+
if (!portMappings?.length) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const exposedMappings = portMappings.filter((line) => {
|
|
297
|
+
const host = parsePublishedHostFromDockerPortLine(line);
|
|
298
|
+
return Boolean(host && !isLoopbackPublishHost(host));
|
|
299
|
+
});
|
|
300
|
+
if (exposedMappings.length > 0) {
|
|
301
|
+
nonLoopbackPublished.push(`${containerName} (${exposedMappings.join("; ")})`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (missingHash.length > 0) {
|
|
305
|
+
findings.push({
|
|
306
|
+
checkId: "sandbox.browser_container.hash_label_missing",
|
|
307
|
+
severity: "warn",
|
|
308
|
+
title: "Sandbox browser container missing config hash label",
|
|
309
|
+
detail: `Containers: ${missingHash.join(", ")}. ` +
|
|
310
|
+
"These browser containers predate hash-based drift checks and may miss security remediations until recreated.",
|
|
311
|
+
remediation: `${formatCliCommand("poolbot sandbox recreate --browser --all")} (add --force to skip prompt).`,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
if (staleEpoch.length > 0) {
|
|
315
|
+
findings.push({
|
|
316
|
+
checkId: "sandbox.browser_container.hash_epoch_stale",
|
|
317
|
+
severity: "warn",
|
|
318
|
+
title: "Sandbox browser container hash epoch is stale",
|
|
319
|
+
detail: `Containers: ${staleEpoch.join(", ")}. ` +
|
|
320
|
+
`Expected poolbot.browserConfigEpoch=${SANDBOX_BROWSER_SECURITY_HASH_EPOCH}.`,
|
|
321
|
+
remediation: `${formatCliCommand("poolbot sandbox recreate --browser --all")} (add --force to skip prompt).`,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
if (nonLoopbackPublished.length > 0) {
|
|
325
|
+
findings.push({
|
|
326
|
+
checkId: "sandbox.browser_container.non_loopback_publish",
|
|
327
|
+
severity: "critical",
|
|
328
|
+
title: "Sandbox browser container publishes ports on non-loopback interfaces",
|
|
329
|
+
detail: `Containers: ${nonLoopbackPublished.join(", ")}. ` +
|
|
330
|
+
"Sandbox browser observer/control ports should stay loopback-only to avoid unintended remote access.",
|
|
331
|
+
remediation: `${formatCliCommand("poolbot sandbox recreate --browser --all")} (add --force to skip prompt), ` +
|
|
332
|
+
"then verify published ports are bound to 127.0.0.1.",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return findings;
|
|
336
|
+
}
|
|
337
|
+
export async function collectPluginsTrustFindings(params) {
|
|
338
|
+
const findings = [];
|
|
339
|
+
const { extensionsDir, pluginDirs } = await listInstalledPluginDirs({
|
|
340
|
+
stateDir: params.stateDir,
|
|
341
|
+
});
|
|
342
|
+
if (pluginDirs.length > 0) {
|
|
343
|
+
const allow = params.cfg.plugins?.allow;
|
|
344
|
+
const allowConfigured = Array.isArray(allow) && allow.length > 0;
|
|
345
|
+
if (!allowConfigured) {
|
|
346
|
+
const hasString = (value) => typeof value === "string" && value.trim().length > 0;
|
|
347
|
+
const hasAccountStringKey = (account, key) => Boolean(account &&
|
|
348
|
+
typeof account === "object" &&
|
|
349
|
+
hasString(account[key]));
|
|
350
|
+
const discordConfigured = hasString(params.cfg.channels?.discord?.token) ||
|
|
351
|
+
Boolean(params.cfg.channels?.discord?.accounts &&
|
|
352
|
+
Object.values(params.cfg.channels.discord.accounts).some((a) => hasAccountStringKey(a, "token"))) ||
|
|
353
|
+
hasString(process.env.DISCORD_BOT_TOKEN);
|
|
354
|
+
const telegramConfigured = hasString(params.cfg.channels?.telegram?.botToken) ||
|
|
355
|
+
hasString(params.cfg.channels?.telegram?.tokenFile) ||
|
|
356
|
+
Boolean(params.cfg.channels?.telegram?.accounts &&
|
|
357
|
+
Object.values(params.cfg.channels.telegram.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "tokenFile"))) ||
|
|
358
|
+
hasString(process.env.TELEGRAM_BOT_TOKEN);
|
|
359
|
+
const slackConfigured = hasString(params.cfg.channels?.slack?.botToken) ||
|
|
360
|
+
hasString(params.cfg.channels?.slack?.appToken) ||
|
|
361
|
+
Boolean(params.cfg.channels?.slack?.accounts &&
|
|
362
|
+
Object.values(params.cfg.channels.slack.accounts).some((a) => hasAccountStringKey(a, "botToken") || hasAccountStringKey(a, "appToken"))) ||
|
|
363
|
+
hasString(process.env.SLACK_BOT_TOKEN) ||
|
|
364
|
+
hasString(process.env.SLACK_APP_TOKEN);
|
|
365
|
+
const skillCommandsLikelyExposed = (discordConfigured &&
|
|
208
366
|
resolveNativeSkillsEnabled({
|
|
209
|
-
providerId: "
|
|
210
|
-
providerSetting: params.cfg.channels?.
|
|
367
|
+
providerId: "discord",
|
|
368
|
+
providerSetting: params.cfg.channels?.discord?.commands?.nativeSkills,
|
|
211
369
|
globalSetting: params.cfg.commands?.nativeSkills,
|
|
212
370
|
})) ||
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
371
|
+
(telegramConfigured &&
|
|
372
|
+
resolveNativeSkillsEnabled({
|
|
373
|
+
providerId: "telegram",
|
|
374
|
+
providerSetting: params.cfg.channels?.telegram?.commands?.nativeSkills,
|
|
375
|
+
globalSetting: params.cfg.commands?.nativeSkills,
|
|
376
|
+
})) ||
|
|
377
|
+
(slackConfigured &&
|
|
378
|
+
resolveNativeSkillsEnabled({
|
|
379
|
+
providerId: "slack",
|
|
380
|
+
providerSetting: params.cfg.channels?.slack?.commands?.nativeSkills,
|
|
381
|
+
globalSetting: params.cfg.commands?.nativeSkills,
|
|
382
|
+
}));
|
|
383
|
+
findings.push({
|
|
384
|
+
checkId: "plugins.extensions_no_allowlist",
|
|
385
|
+
severity: skillCommandsLikelyExposed ? "critical" : "warn",
|
|
386
|
+
title: "Extensions exist but plugins.allow is not set",
|
|
387
|
+
detail: `Found ${pluginDirs.length} extension(s) under ${extensionsDir}. Without plugins.allow, any discovered plugin id may load (depending on config and plugin behavior).` +
|
|
388
|
+
(skillCommandsLikelyExposed
|
|
389
|
+
? "\nNative skill commands are enabled on at least one configured chat surface; treat unpinned/unallowlisted extensions as high risk."
|
|
390
|
+
: ""),
|
|
391
|
+
remediation: "Set plugins.allow to an explicit list of plugin ids you trust.",
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
const enabledExtensionPluginIds = resolveEnabledExtensionPluginIds({
|
|
395
|
+
cfg: params.cfg,
|
|
396
|
+
pluginDirs,
|
|
228
397
|
});
|
|
398
|
+
if (enabledExtensionPluginIds.length > 0) {
|
|
399
|
+
const enabledPluginSet = new Set(enabledExtensionPluginIds);
|
|
400
|
+
const contexts = [{ label: "default" }];
|
|
401
|
+
for (const entry of params.cfg.agents?.list ?? []) {
|
|
402
|
+
if (!entry || typeof entry !== "object" || typeof entry.id !== "string") {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
contexts.push({
|
|
406
|
+
label: `agents.list.${entry.id}`,
|
|
407
|
+
agentId: entry.id,
|
|
408
|
+
tools: entry.tools,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
const permissiveContexts = [];
|
|
412
|
+
for (const context of contexts) {
|
|
413
|
+
const profile = context.tools?.profile ?? params.cfg.tools?.profile;
|
|
414
|
+
const restrictiveProfile = Boolean(resolveToolProfilePolicy(profile));
|
|
415
|
+
const sandboxMode = resolveSandboxConfigForAgent(params.cfg, context.agentId).mode;
|
|
416
|
+
const policies = resolveToolPolicies({
|
|
417
|
+
cfg: params.cfg,
|
|
418
|
+
agentTools: context.tools,
|
|
419
|
+
sandboxMode,
|
|
420
|
+
agentId: context.agentId,
|
|
421
|
+
});
|
|
422
|
+
const broadPolicy = isToolAllowedByPolicies("__poolbot_plugin_probe__", policies);
|
|
423
|
+
const explicitPluginAllow = !restrictiveProfile &&
|
|
424
|
+
(hasExplicitPluginAllow({
|
|
425
|
+
allowEntries: collectAllowEntries(params.cfg.tools),
|
|
426
|
+
enabledPluginIds: enabledPluginSet,
|
|
427
|
+
}) ||
|
|
428
|
+
hasProviderPluginAllow({
|
|
429
|
+
byProvider: params.cfg.tools?.byProvider,
|
|
430
|
+
enabledPluginIds: enabledPluginSet,
|
|
431
|
+
}) ||
|
|
432
|
+
hasExplicitPluginAllow({
|
|
433
|
+
allowEntries: collectAllowEntries(context.tools),
|
|
434
|
+
enabledPluginIds: enabledPluginSet,
|
|
435
|
+
}) ||
|
|
436
|
+
hasProviderPluginAllow({
|
|
437
|
+
byProvider: context.tools?.byProvider,
|
|
438
|
+
enabledPluginIds: enabledPluginSet,
|
|
439
|
+
}));
|
|
440
|
+
if (broadPolicy || explicitPluginAllow) {
|
|
441
|
+
permissiveContexts.push(context.label);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (permissiveContexts.length > 0) {
|
|
445
|
+
findings.push({
|
|
446
|
+
checkId: "plugins.tools_reachable_permissive_policy",
|
|
447
|
+
severity: "warn",
|
|
448
|
+
title: "Extension plugin tools may be reachable under permissive tool policy",
|
|
449
|
+
detail: `Enabled extension plugins: ${enabledExtensionPluginIds.join(", ")}.\n` +
|
|
450
|
+
`Permissive tool policy contexts:\n${permissiveContexts.map((entry) => `- ${entry}`).join("\n")}`,
|
|
451
|
+
remediation: "Use restrictive profiles (`minimal`/`coding`) or explicit tool allowlists that exclude plugin tools for agents handling untrusted input.",
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const pluginInstalls = params.cfg.plugins?.installs ?? {};
|
|
457
|
+
const npmPluginInstalls = Object.entries(pluginInstalls).filter(([, record]) => record?.source === "npm");
|
|
458
|
+
if (npmPluginInstalls.length > 0) {
|
|
459
|
+
const unpinned = npmPluginInstalls
|
|
460
|
+
.filter(([, record]) => typeof record.spec === "string" && !isPinnedRegistrySpec(record.spec))
|
|
461
|
+
.map(([pluginId, record]) => `${pluginId} (${record.spec})`);
|
|
462
|
+
if (unpinned.length > 0) {
|
|
463
|
+
findings.push({
|
|
464
|
+
checkId: "plugins.installs_unpinned_npm_specs",
|
|
465
|
+
severity: "warn",
|
|
466
|
+
title: "Plugin installs include unpinned npm specs",
|
|
467
|
+
detail: `Unpinned plugin install records:\n${unpinned.map((entry) => `- ${entry}`).join("\n")}`,
|
|
468
|
+
remediation: "Pin install specs to exact versions (for example, `@scope/pkg@1.2.3`) for higher supply-chain stability.",
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
const missingIntegrity = npmPluginInstalls
|
|
472
|
+
.filter(([, record]) => typeof record.integrity !== "string" || record.integrity.trim() === "")
|
|
473
|
+
.map(([pluginId]) => pluginId);
|
|
474
|
+
if (missingIntegrity.length > 0) {
|
|
475
|
+
findings.push({
|
|
476
|
+
checkId: "plugins.installs_missing_integrity",
|
|
477
|
+
severity: "warn",
|
|
478
|
+
title: "Plugin installs are missing integrity metadata",
|
|
479
|
+
detail: `Plugin install records missing integrity:\n${missingIntegrity.map((entry) => `- ${entry}`).join("\n")}`,
|
|
480
|
+
remediation: "Reinstall or update plugins to refresh install metadata with resolved integrity hashes.",
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
const pluginVersionDrift = [];
|
|
484
|
+
for (const [pluginId, record] of npmPluginInstalls) {
|
|
485
|
+
const recordedVersion = record.resolvedVersion ?? record.version;
|
|
486
|
+
if (!recordedVersion) {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
const installPath = record.installPath ?? path.join(params.stateDir, "extensions", pluginId);
|
|
490
|
+
// eslint-disable-next-line no-await-in-loop
|
|
491
|
+
const installedVersion = await readInstalledPackageVersion(installPath);
|
|
492
|
+
if (!installedVersion || installedVersion === recordedVersion) {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
pluginVersionDrift.push(`${pluginId} (recorded ${recordedVersion}, installed ${installedVersion})`);
|
|
496
|
+
}
|
|
497
|
+
if (pluginVersionDrift.length > 0) {
|
|
498
|
+
findings.push({
|
|
499
|
+
checkId: "plugins.installs_version_drift",
|
|
500
|
+
severity: "warn",
|
|
501
|
+
title: "Plugin install records drift from installed package versions",
|
|
502
|
+
detail: `Detected plugin install metadata drift:\n${pluginVersionDrift.map((entry) => `- ${entry}`).join("\n")}`,
|
|
503
|
+
remediation: "Run `poolbot plugins update --all` (or reinstall affected plugins) to refresh install metadata.",
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
const hookInstalls = params.cfg.hooks?.internal?.installs ?? {};
|
|
508
|
+
const npmHookInstalls = Object.entries(hookInstalls).filter(([, record]) => record?.source === "npm");
|
|
509
|
+
if (npmHookInstalls.length > 0) {
|
|
510
|
+
const unpinned = npmHookInstalls
|
|
511
|
+
.filter(([, record]) => typeof record.spec === "string" && !isPinnedRegistrySpec(record.spec))
|
|
512
|
+
.map(([hookId, record]) => `${hookId} (${record.spec})`);
|
|
513
|
+
if (unpinned.length > 0) {
|
|
514
|
+
findings.push({
|
|
515
|
+
checkId: "hooks.installs_unpinned_npm_specs",
|
|
516
|
+
severity: "warn",
|
|
517
|
+
title: "Hook installs include unpinned npm specs",
|
|
518
|
+
detail: `Unpinned hook install records:\n${unpinned.map((entry) => `- ${entry}`).join("\n")}`,
|
|
519
|
+
remediation: "Pin hook install specs to exact versions (for example, `@scope/pkg@1.2.3`) for higher supply-chain stability.",
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
const missingIntegrity = npmHookInstalls
|
|
523
|
+
.filter(([, record]) => typeof record.integrity !== "string" || record.integrity.trim() === "")
|
|
524
|
+
.map(([hookId]) => hookId);
|
|
525
|
+
if (missingIntegrity.length > 0) {
|
|
526
|
+
findings.push({
|
|
527
|
+
checkId: "hooks.installs_missing_integrity",
|
|
528
|
+
severity: "warn",
|
|
529
|
+
title: "Hook installs are missing integrity metadata",
|
|
530
|
+
detail: `Hook install records missing integrity:\n${missingIntegrity.map((entry) => `- ${entry}`).join("\n")}`,
|
|
531
|
+
remediation: "Reinstall or update hooks to refresh install metadata with resolved integrity hashes.",
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
const hookVersionDrift = [];
|
|
535
|
+
for (const [hookId, record] of npmHookInstalls) {
|
|
536
|
+
const recordedVersion = record.resolvedVersion ?? record.version;
|
|
537
|
+
if (!recordedVersion) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
const installPath = record.installPath ?? path.join(params.stateDir, "hooks", hookId);
|
|
541
|
+
// eslint-disable-next-line no-await-in-loop
|
|
542
|
+
const installedVersion = await readInstalledPackageVersion(installPath);
|
|
543
|
+
if (!installedVersion || installedVersion === recordedVersion) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
hookVersionDrift.push(`${hookId} (recorded ${recordedVersion}, installed ${installedVersion})`);
|
|
547
|
+
}
|
|
548
|
+
if (hookVersionDrift.length > 0) {
|
|
549
|
+
findings.push({
|
|
550
|
+
checkId: "hooks.installs_version_drift",
|
|
551
|
+
severity: "warn",
|
|
552
|
+
title: "Hook install records drift from installed package versions",
|
|
553
|
+
detail: `Detected hook install metadata drift:\n${hookVersionDrift.map((entry) => `- ${entry}`).join("\n")}`,
|
|
554
|
+
remediation: "Run `poolbot hooks update --all` (or reinstall affected hooks) to refresh install metadata.",
|
|
555
|
+
});
|
|
556
|
+
}
|
|
229
557
|
}
|
|
230
558
|
return findings;
|
|
231
559
|
}
|
|
@@ -452,22 +780,18 @@ export async function readConfigSnapshotForAudit(params) {
|
|
|
452
780
|
}
|
|
453
781
|
export async function collectPluginsCodeSafetyFindings(params) {
|
|
454
782
|
const findings = [];
|
|
455
|
-
const extensionsDir =
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
remediation: "Check file permissions and plugin layout, then rerun `poolbot security audit --deep`.",
|
|
467
|
-
});
|
|
468
|
-
return [];
|
|
783
|
+
const { extensionsDir, pluginDirs } = await listInstalledPluginDirs({
|
|
784
|
+
stateDir: params.stateDir,
|
|
785
|
+
onReadError: (err) => {
|
|
786
|
+
findings.push({
|
|
787
|
+
checkId: "plugins.code_safety.scan_failed",
|
|
788
|
+
severity: "warn",
|
|
789
|
+
title: "Plugin extensions directory scan failed",
|
|
790
|
+
detail: `Static code scan could not list extensions directory: ${String(err)}`,
|
|
791
|
+
remediation: "Check file permissions and plugin layout, then rerun `poolbot security audit --deep`.",
|
|
792
|
+
});
|
|
793
|
+
},
|
|
469
794
|
});
|
|
470
|
-
const pluginDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
471
795
|
for (const pluginName of pluginDirs) {
|
|
472
796
|
const pluginPath = path.join(extensionsDir, pluginName);
|
|
473
797
|
const extensionEntries = await readPluginManifestExtensions(pluginPath).catch(() => []);
|
|
@@ -499,9 +823,11 @@ export async function collectPluginsCodeSafetyFindings(params) {
|
|
|
499
823
|
remediation: "Update the plugin manifest so all poolbot.extensions entries stay inside the plugin directory.",
|
|
500
824
|
});
|
|
501
825
|
}
|
|
502
|
-
const summary = await
|
|
826
|
+
const summary = await skillScanner
|
|
827
|
+
.scanDirectoryWithSummary(pluginPath, {
|
|
503
828
|
includeFiles: forcedScanEntries,
|
|
504
|
-
})
|
|
829
|
+
})
|
|
830
|
+
.catch((err) => {
|
|
505
831
|
findings.push({
|
|
506
832
|
checkId: "plugins.code_safety.scan_failed",
|
|
507
833
|
severity: "warn",
|
|
@@ -543,7 +869,7 @@ export async function collectInstalledSkillsCodeSafetyFindings(params) {
|
|
|
543
869
|
const findings = [];
|
|
544
870
|
const pluginExtensionsDir = path.join(params.stateDir, "extensions");
|
|
545
871
|
const scannedSkillDirs = new Set();
|
|
546
|
-
const workspaceDirs =
|
|
872
|
+
const workspaceDirs = listAgentWorkspaceDirs(params.cfg);
|
|
547
873
|
for (const workspaceDir of workspaceDirs) {
|
|
548
874
|
const entries = loadWorkspaceSkillEntries(workspaceDir, { config: params.cfg });
|
|
549
875
|
for (const entry of entries) {
|
|
@@ -560,7 +886,7 @@ export async function collectInstalledSkillsCodeSafetyFindings(params) {
|
|
|
560
886
|
}
|
|
561
887
|
scannedSkillDirs.add(skillDir);
|
|
562
888
|
const skillName = entry.skill.name;
|
|
563
|
-
const summary = await scanDirectoryWithSummary(skillDir).catch((err) => {
|
|
889
|
+
const summary = await skillScanner.scanDirectoryWithSummary(skillDir).catch((err) => {
|
|
564
890
|
findings.push({
|
|
565
891
|
checkId: "skills.code_safety.scan_failed",
|
|
566
892
|
severity: "warn",
|