@poolzin/pool-bot 2026.2.25 → 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/dist/acp/event-mapper.js +87 -22
- package/dist/acp/meta.js +12 -6
- package/dist/agents/agent-paths.js +8 -9
- package/dist/agents/agent-scope.js +7 -5
- package/dist/agents/auth-profiles/oauth.js +148 -64
- package/dist/agents/auth-profiles/session-override.js +13 -7
- package/dist/agents/bash-tools.exec-host-gateway.js +14 -4
- package/dist/agents/bash-tools.exec-runtime.js +2 -25
- 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/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-selection.js +7 -1
- package/dist/agents/models-config.providers.js +93 -11
- 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-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.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/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.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/fast-coding-tools.js +1 -18
- package/dist/agents/test-helpers/fast-core-tools.js +1 -17
- 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.js +51 -26
- package/dist/agents/tools/browser-tool.js +2 -2
- package/dist/agents/tools/canvas-tool.js +27 -1
- package/dist/agents/tools/common.js +45 -0
- package/dist/agents/tools/discord-actions-guild.js +4 -1
- package/dist/agents/tools/gateway-tool.js +3 -1
- package/dist/agents/tools/nodes-utils.js +1 -10
- 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/auto-reply/commands-registry.data.js +51 -0
- package/dist/auto-reply/commands-registry.js +4 -3
- package/dist/auto-reply/group-activation.js +10 -5
- package/dist/auto-reply/inbound-debounce.js +10 -5
- package/dist/auto-reply/reply/abort.js +1 -1
- package/dist/auto-reply/reply/agent-runner-execution.js +4 -1
- 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.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 +4 -0
- package/dist/browser/extension-relay-auth.js +55 -0
- package/dist/browser/extension-relay.js +74 -29
- package/dist/browser/navigation-guard.js +9 -1
- 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 +2 -2
- package/dist/browser/server-context.js +7 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/allow-from.js +2 -1
- 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/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/cli/banner.js +1 -1
- 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/gateway-cli/run-loop.js +23 -5
- package/dist/cli/node-cli/register.js +14 -5
- package/dist/cli/nodes-media-utils.js +7 -2
- 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 +34 -41
- package/dist/cli/ports.js +11 -10
- package/dist/cli/program/command-registry.js +2 -11
- package/dist/cli/program/command-tree.js +16 -0
- 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 +11 -7
- package/dist/cli/system-cli.js +36 -46
- 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 +22 -2
- 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/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/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/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 +30 -16
- package/dist/config/config-paths.js +9 -11
- package/dist/config/defaults.js +22 -2
- package/dist/config/discord-preview-streaming.js +104 -0
- package/dist/config/env-vars.js +37 -8
- package/dist/config/includes.js +4 -0
- package/dist/config/io.js +97 -12
- package/dist/config/legacy.migrations.part-1.js +189 -78
- package/dist/config/legacy.shared.js +3 -1
- package/dist/config/merge-patch.js +4 -0
- package/dist/config/prototype-keys.js +4 -0
- package/dist/config/schema.help.js +44 -7
- 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/zod-schema.agent-runtime.js +11 -0
- package/dist/config/zod-schema.js +148 -13
- 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/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/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 +34 -10
- package/dist/gateway/call.js +4 -16
- package/dist/gateway/client.js +28 -4
- package/dist/gateway/config-reload.js +3 -4
- package/dist/gateway/control-ui.js +219 -96
- package/dist/gateway/hooks-mapping.js +88 -38
- package/dist/gateway/http-auth-helpers.js +3 -2
- package/dist/gateway/http-endpoint-helpers.js +1 -0
- package/dist/gateway/net.js +54 -12
- 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 +1 -0
- package/dist/gateway/origin-check.js +1 -18
- package/dist/gateway/protocol/index.js +4 -3
- 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 +2 -1
- package/dist/gateway/protocol/schema/sessions.js +6 -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 +16 -7
- package/dist/gateway/server-maintenance.js +20 -5
- package/dist/gateway/server-methods/chat.js +10 -6
- package/dist/gateway/server-methods/config.js +12 -14
- package/dist/gateway/server-methods/devices.js +17 -3
- package/dist/gateway/server-methods/models.js +11 -1
- 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-runtime-config.js +34 -13
- package/dist/gateway/server-startup-memory.js +17 -11
- package/dist/gateway/session-utils.fs.js +32 -34
- package/dist/gateway/sessions-resolve.js +17 -5
- package/dist/gateway/test-helpers.openai-mock.js +14 -7
- 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 +6 -5
- package/dist/hooks/frontmatter.js +6 -6
- 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.js +174 -73
- package/dist/infra/control-ui-assets.js +14 -6
- package/dist/infra/device-pairing.js +108 -29
- package/dist/infra/env.js +10 -5
- package/dist/infra/exec-approvals-allowlist.js +122 -0
- package/dist/infra/exec-approvals-analysis.js +34 -3
- package/dist/infra/exec-approvals.js +5 -17
- package/dist/infra/exec-safe-bin-policy.js +53 -45
- package/dist/infra/fs-safe.js +71 -39
- package/dist/infra/gateway-lock.js +6 -2
- 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/net/ssrf.js +131 -38
- package/dist/infra/outbound/bound-delivery-router.js +88 -0
- 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/payloads.js +14 -7
- package/dist/infra/outbound/session-binding-service.js +123 -0
- package/dist/infra/path-guards.js +25 -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/retry-policy.js +4 -2
- package/dist/infra/retry.js +9 -5
- 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/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/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/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/webhook-targets.js +32 -0
- package/dist/plugins/bundled-dir.js +9 -6
- package/dist/plugins/hooks.js +50 -0
- package/dist/plugins/install.js +28 -16
- 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-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 +93 -2
- package/dist/security/audit-extra.async.js +159 -5
- package/dist/security/audit-extra.js +1 -1
- package/dist/security/audit-extra.sync.js +85 -6
- package/dist/security/audit.js +40 -4
- package/dist/security/dm-policy-shared.js +44 -0
- package/dist/security/external-content.js +26 -6
- package/dist/shared/entry-status.js +6 -0
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-match.js +11 -4
- package/dist/shared/operator-scope-compat.js +8 -3
- 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/monitor/auth.js +1 -1
- package/dist/slack/monitor/message-handler/dispatch.js +50 -29
- 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 +155 -90
- package/dist/telegram/bot-native-commands.js +16 -0
- package/dist/telegram/draft-stream.js +14 -1
- 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/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/theme.js +12 -12
- package/dist/tts/tts.js +80 -567
- package/dist/tui/components/chat-log.js +41 -8
- package/dist/tui/theme/theme.js +10 -12
- package/dist/tui/tui-local-shell.js +16 -6
- package/dist/tui/tui.js +58 -6
- 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/queue-helpers.js +67 -12
- 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 +123 -16
- package/extensions/bluebubbles/src/monitor-processing.ts +580 -139
- package/extensions/bluebubbles/src/monitor.ts +208 -1950
- package/package.json +1 -1
|
@@ -8,6 +8,40 @@ import { resolveDmAllowState } from "./dm-policy-shared.js";
|
|
|
8
8
|
function normalizeAllowFromList(list) {
|
|
9
9
|
return normalizeStringEntries(Array.isArray(list) ? list : undefined);
|
|
10
10
|
}
|
|
11
|
+
const DISCORD_ALLOWLIST_ID_PREFIXES = ["discord:", "user:", "pk:"];
|
|
12
|
+
function isDiscordNameBasedAllowEntry(raw) {
|
|
13
|
+
const text = String(raw).trim();
|
|
14
|
+
if (!text || text === "*") {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const maybeId = text.replace(/^<@!?/, "").replace(/>$/, "");
|
|
18
|
+
if (/^\d+$/.test(maybeId)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const prefixed = DISCORD_ALLOWLIST_ID_PREFIXES.find((prefix) => text.startsWith(prefix));
|
|
22
|
+
if (prefixed) {
|
|
23
|
+
const candidate = text.slice(prefixed.length);
|
|
24
|
+
if (candidate) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
function addDiscordNameBasedEntries(params) {
|
|
31
|
+
if (!Array.isArray(params.values)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
for (const value of params.values) {
|
|
35
|
+
if (!isDiscordNameBasedAllowEntry(value)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const text = String(value).trim();
|
|
39
|
+
if (!text) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
params.target.add(`${params.source}:${text}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
11
45
|
function classifyChannelWarningSeverity(message) {
|
|
12
46
|
const s = message.toLowerCase();
|
|
13
47
|
if (s.includes("dms: open") ||
|
|
@@ -108,6 +142,64 @@ export async function collectChannelSecurityFindings(params) {
|
|
|
108
142
|
if (plugin.id === "discord") {
|
|
109
143
|
const discordCfg = account?.config ??
|
|
110
144
|
{};
|
|
145
|
+
const storeAllowFrom = await readChannelAllowFromStore("discord").catch(() => []);
|
|
146
|
+
const discordNameBasedAllowEntries = new Set();
|
|
147
|
+
addDiscordNameBasedEntries({
|
|
148
|
+
target: discordNameBasedAllowEntries,
|
|
149
|
+
values: discordCfg.allowFrom,
|
|
150
|
+
source: "channels.discord.allowFrom",
|
|
151
|
+
});
|
|
152
|
+
addDiscordNameBasedEntries({
|
|
153
|
+
target: discordNameBasedAllowEntries,
|
|
154
|
+
values: discordCfg.dm?.allowFrom,
|
|
155
|
+
source: "channels.discord.dm.allowFrom",
|
|
156
|
+
});
|
|
157
|
+
addDiscordNameBasedEntries({
|
|
158
|
+
target: discordNameBasedAllowEntries,
|
|
159
|
+
values: storeAllowFrom,
|
|
160
|
+
source: "~/.poolbot/credentials/discord-allowFrom.json",
|
|
161
|
+
});
|
|
162
|
+
const discordGuildEntries = discordCfg.guilds ?? {};
|
|
163
|
+
for (const [guildKey, guildValue] of Object.entries(discordGuildEntries)) {
|
|
164
|
+
if (!guildValue || typeof guildValue !== "object") {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const guild = guildValue;
|
|
168
|
+
addDiscordNameBasedEntries({
|
|
169
|
+
target: discordNameBasedAllowEntries,
|
|
170
|
+
values: guild.users,
|
|
171
|
+
source: `channels.discord.guilds.${guildKey}.users`,
|
|
172
|
+
});
|
|
173
|
+
const channels = guild.channels;
|
|
174
|
+
if (!channels || typeof channels !== "object") {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
for (const [channelKey, channelValue] of Object.entries(channels)) {
|
|
178
|
+
if (!channelValue || typeof channelValue !== "object") {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const channel = channelValue;
|
|
182
|
+
addDiscordNameBasedEntries({
|
|
183
|
+
target: discordNameBasedAllowEntries,
|
|
184
|
+
values: channel.users,
|
|
185
|
+
source: `channels.discord.guilds.${guildKey}.channels.${channelKey}.users`,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (discordNameBasedAllowEntries.size > 0) {
|
|
190
|
+
const examples = Array.from(discordNameBasedAllowEntries).slice(0, 5);
|
|
191
|
+
const more = discordNameBasedAllowEntries.size > examples.length
|
|
192
|
+
? ` (+${discordNameBasedAllowEntries.size - examples.length} more)`
|
|
193
|
+
: "";
|
|
194
|
+
findings.push({
|
|
195
|
+
checkId: "channels.discord.allowFrom.name_based_entries",
|
|
196
|
+
severity: "warn",
|
|
197
|
+
title: "Discord allowlist contains name or tag entries",
|
|
198
|
+
detail: "Discord name/tag allowlist matching uses normalized slugs and can collide across users. " +
|
|
199
|
+
`Found: ${examples.join(", ")}${more}.`,
|
|
200
|
+
remediation: "Prefer stable Discord IDs (or <@id>/user:<id>/pk:<id>) in channels.discord.allowFrom and channels.discord.guilds.*.users.",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
111
203
|
const nativeEnabled = resolveNativeCommandsEnabled({
|
|
112
204
|
providerId: "discord",
|
|
113
205
|
providerSetting: coerceNativeSetting(discordCfg.commands?.native),
|
|
@@ -122,7 +214,7 @@ export async function collectChannelSecurityFindings(params) {
|
|
|
122
214
|
if (slashEnabled) {
|
|
123
215
|
const defaultGroupPolicy = params.cfg.channels?.defaults?.groupPolicy;
|
|
124
216
|
const groupPolicy = discordCfg.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
|
125
|
-
const guildEntries =
|
|
217
|
+
const guildEntries = discordGuildEntries;
|
|
126
218
|
const guildsConfigured = Object.keys(guildEntries).length > 0;
|
|
127
219
|
const hasAnyUserAllowlist = Object.values(guildEntries).some((guild) => {
|
|
128
220
|
if (!guild || typeof guild !== "object") {
|
|
@@ -146,7 +238,6 @@ export async function collectChannelSecurityFindings(params) {
|
|
|
146
238
|
});
|
|
147
239
|
const dmAllowFromRaw = discordCfg.dm?.allowFrom;
|
|
148
240
|
const dmAllowFrom = Array.isArray(dmAllowFromRaw) ? dmAllowFromRaw : [];
|
|
149
|
-
const storeAllowFrom = await readChannelAllowFromStore("discord").catch(() => []);
|
|
150
241
|
const ownerAllowFromConfigured = normalizeAllowFromList([...dmAllowFrom, ...storeAllowFrom]).length > 0;
|
|
151
242
|
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
|
|
152
243
|
if (!useAccessGroups &&
|
|
@@ -8,9 +8,12 @@ import path from "node:path";
|
|
|
8
8
|
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
9
9
|
import { isToolAllowedByPolicies } from "../agents/pi-tools.policy.js";
|
|
10
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";
|
|
11
13
|
import { loadWorkspaceSkillEntries } from "../agents/skills.js";
|
|
12
14
|
import { resolveToolProfilePolicy } from "../agents/tool-policy.js";
|
|
13
15
|
import { listAgentWorkspaceDirs } from "../agents/workspace-dirs.js";
|
|
16
|
+
import { formatCliCommand } from "../cli/command-format.js";
|
|
14
17
|
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
|
15
18
|
import { resolveNativeSkillsEnabled } from "../config/commands.js";
|
|
16
19
|
import { createConfigIO } from "../config/config.js";
|
|
@@ -180,6 +183,157 @@ async function readInstalledPackageVersion(dir) {
|
|
|
180
183
|
// --------------------------------------------------------------------------
|
|
181
184
|
// Exported collectors
|
|
182
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) {
|
|
273
|
+
return findings;
|
|
274
|
+
}
|
|
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
|
+
}
|
|
183
337
|
export async function collectPluginsTrustFindings(params) {
|
|
184
338
|
const findings = [];
|
|
185
339
|
const { extensionsDir, pluginDirs } = await listInstalledPluginDirs({
|
|
@@ -265,7 +419,7 @@ export async function collectPluginsTrustFindings(params) {
|
|
|
265
419
|
sandboxMode,
|
|
266
420
|
agentId: context.agentId,
|
|
267
421
|
});
|
|
268
|
-
const broadPolicy = isToolAllowedByPolicies("
|
|
422
|
+
const broadPolicy = isToolAllowedByPolicies("__poolbot_plugin_probe__", policies);
|
|
269
423
|
const explicitPluginAllow = !restrictiveProfile &&
|
|
270
424
|
(hasExplicitPluginAllow({
|
|
271
425
|
allowEntries: collectAllowEntries(params.cfg.tools),
|
|
@@ -346,7 +500,7 @@ export async function collectPluginsTrustFindings(params) {
|
|
|
346
500
|
severity: "warn",
|
|
347
501
|
title: "Plugin install records drift from installed package versions",
|
|
348
502
|
detail: `Detected plugin install metadata drift:\n${pluginVersionDrift.map((entry) => `- ${entry}`).join("\n")}`,
|
|
349
|
-
remediation: "Run `
|
|
503
|
+
remediation: "Run `poolbot plugins update --all` (or reinstall affected plugins) to refresh install metadata.",
|
|
350
504
|
});
|
|
351
505
|
}
|
|
352
506
|
}
|
|
@@ -397,7 +551,7 @@ export async function collectPluginsTrustFindings(params) {
|
|
|
397
551
|
severity: "warn",
|
|
398
552
|
title: "Hook install records drift from installed package versions",
|
|
399
553
|
detail: `Detected hook install metadata drift:\n${hookVersionDrift.map((entry) => `- ${entry}`).join("\n")}`,
|
|
400
|
-
remediation: "Run `
|
|
554
|
+
remediation: "Run `poolbot hooks update --all` (or reinstall affected hooks) to refresh install metadata.",
|
|
401
555
|
});
|
|
402
556
|
}
|
|
403
557
|
}
|
|
@@ -666,7 +820,7 @@ export async function collectPluginsCodeSafetyFindings(params) {
|
|
|
666
820
|
severity: "critical",
|
|
667
821
|
title: `Plugin "${pluginName}" has extension entry path traversal`,
|
|
668
822
|
detail: `Found extension entries that escape the plugin directory:\n${escapedEntries.map((entry) => ` - ${entry}`).join("\n")}`,
|
|
669
|
-
remediation: "Update the plugin manifest so all
|
|
823
|
+
remediation: "Update the plugin manifest so all poolbot.extensions entries stay inside the plugin directory.",
|
|
670
824
|
});
|
|
671
825
|
}
|
|
672
826
|
const summary = await skillScanner
|
|
@@ -694,7 +848,7 @@ export async function collectPluginsCodeSafetyFindings(params) {
|
|
|
694
848
|
severity: "critical",
|
|
695
849
|
title: `Plugin "${pluginName}" contains dangerous code patterns`,
|
|
696
850
|
detail: `Found ${summary.critical} critical issue(s) in ${summary.scannedFiles} scanned file(s):\n${details}`,
|
|
697
|
-
remediation: "Review the plugin source code carefully before use. If untrusted, remove the plugin from your
|
|
851
|
+
remediation: "Review the plugin source code carefully before use. If untrusted, remove the plugin from your Pool Bot extensions state directory.",
|
|
698
852
|
});
|
|
699
853
|
}
|
|
700
854
|
else if (summary.warn > 0) {
|
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
// Sync collectors
|
|
10
10
|
export { collectAttackSurfaceSummaryFindings, collectExposureMatrixFindings, collectGatewayHttpNoAuthFindings, collectGatewayHttpSessionKeyOverrideFindings, collectHooksHardeningFindings, collectMinimalProfileOverrideFindings, collectModelHygieneFindings, collectNodeDenyCommandPatternFindings, collectSandboxDangerousConfigFindings, collectSandboxDockerNoopFindings, collectSecretsInConfigFindings, collectSmallModelRiskFindings, collectSyncedFolderFindings, } from "./audit-extra.sync.js";
|
|
11
11
|
// Async collectors
|
|
12
|
-
export { collectIncludeFilePermFindings, collectInstalledSkillsCodeSafetyFindings, collectPluginsCodeSafetyFindings, collectPluginsTrustFindings, collectStateDeepFilesystemFindings, readConfigSnapshotForAudit, } from "./audit-extra.async.js";
|
|
12
|
+
export { collectSandboxBrowserHashLabelFindings, collectIncludeFilePermFindings, collectInstalledSkillsCodeSafetyFindings, collectPluginsCodeSafetyFindings, collectPluginsTrustFindings, collectStateDeepFilesystemFindings, readConfigSnapshotForAudit, } from "./audit-extra.async.js";
|
|
@@ -290,7 +290,7 @@ export function collectSyncedFolderFindings(params) {
|
|
|
290
290
|
severity: "warn",
|
|
291
291
|
title: "State/config path looks like a synced folder",
|
|
292
292
|
detail: `stateDir=${params.stateDir}, configPath=${params.configPath}. Synced folders (iCloud/Dropbox/OneDrive/Google Drive) can leak tokens and transcripts onto other devices.`,
|
|
293
|
-
remediation: `Keep
|
|
293
|
+
remediation: `Keep POOLBOT_STATE_DIR on a local-only volume and re-run "${formatCliCommand("poolbot security audit --fix")}".`,
|
|
294
294
|
});
|
|
295
295
|
}
|
|
296
296
|
return findings;
|
|
@@ -337,20 +337,20 @@ export function collectHooksHardeningFindings(cfg, env = process.env) {
|
|
|
337
337
|
tailscaleMode: cfg.gateway?.tailscale?.mode ?? "off",
|
|
338
338
|
env,
|
|
339
339
|
});
|
|
340
|
-
const
|
|
341
|
-
? env.
|
|
340
|
+
const poolbotGatewayToken = typeof env.POOLBOT_GATEWAY_TOKEN === "string" && env.POOLBOT_GATEWAY_TOKEN.trim()
|
|
341
|
+
? env.POOLBOT_GATEWAY_TOKEN.trim()
|
|
342
342
|
: null;
|
|
343
343
|
const gatewayToken = gatewayAuth.mode === "token" &&
|
|
344
344
|
typeof gatewayAuth.token === "string" &&
|
|
345
345
|
gatewayAuth.token.trim()
|
|
346
346
|
? gatewayAuth.token.trim()
|
|
347
|
-
:
|
|
348
|
-
?
|
|
347
|
+
: poolbotGatewayToken
|
|
348
|
+
? poolbotGatewayToken
|
|
349
349
|
: null;
|
|
350
350
|
if (token && gatewayToken && token === gatewayToken) {
|
|
351
351
|
findings.push({
|
|
352
352
|
checkId: "hooks.token_reuse_gateway_token",
|
|
353
|
-
severity: "
|
|
353
|
+
severity: "critical",
|
|
354
354
|
title: "Hooks token reuses the Gateway token",
|
|
355
355
|
detail: "hooks.token matches gateway.auth token; compromise of hooks expands blast radius to the Gateway API.",
|
|
356
356
|
remediation: "Use a separate hooks.token dedicated to hook ingress.",
|
|
@@ -573,6 +573,40 @@ export function collectSandboxDangerousConfigFindings(cfg) {
|
|
|
573
573
|
});
|
|
574
574
|
}
|
|
575
575
|
}
|
|
576
|
+
const browserExposurePaths = [];
|
|
577
|
+
const defaultBrowser = resolveSandboxConfigForAgent(cfg).browser;
|
|
578
|
+
if (defaultBrowser.enabled &&
|
|
579
|
+
defaultBrowser.network.trim().toLowerCase() === "bridge" &&
|
|
580
|
+
!defaultBrowser.cdpSourceRange?.trim()) {
|
|
581
|
+
browserExposurePaths.push("agents.defaults.sandbox.browser");
|
|
582
|
+
}
|
|
583
|
+
for (const entry of agents) {
|
|
584
|
+
if (!entry || typeof entry !== "object" || typeof entry.id !== "string") {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const browser = resolveSandboxConfigForAgent(cfg, entry.id).browser;
|
|
588
|
+
if (!browser.enabled) {
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
if (browser.network.trim().toLowerCase() !== "bridge") {
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (browser.cdpSourceRange?.trim()) {
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
browserExposurePaths.push(`agents.list.${entry.id}.sandbox.browser`);
|
|
598
|
+
}
|
|
599
|
+
if (browserExposurePaths.length > 0) {
|
|
600
|
+
findings.push({
|
|
601
|
+
checkId: "sandbox.browser_cdp_bridge_unrestricted",
|
|
602
|
+
severity: "warn",
|
|
603
|
+
title: "Sandbox browser CDP may be reachable by peer containers",
|
|
604
|
+
detail: "These sandbox browser configs use Docker bridge networking with no CDP source restriction:\n" +
|
|
605
|
+
browserExposurePaths.map((entry) => `- ${entry}`).join("\n"),
|
|
606
|
+
remediation: "Set sandbox.browser.network to a dedicated bridge network (recommended default: poolbot-sandbox-browser), " +
|
|
607
|
+
"or set sandbox.browser.cdpSourceRange (for example 172.21.0.1/32) to restrict container-edge CDP ingress.",
|
|
608
|
+
});
|
|
609
|
+
}
|
|
576
610
|
return findings;
|
|
577
611
|
}
|
|
578
612
|
export function collectNodeDenyCommandPatternFindings(cfg) {
|
|
@@ -808,5 +842,50 @@ export function collectExposureMatrixFindings(cfg) {
|
|
|
808
842
|
remediation: `Set groupPolicy="allowlist" and keep elevated allowlists extremely tight.`,
|
|
809
843
|
});
|
|
810
844
|
}
|
|
845
|
+
const contexts = [{ label: "agents.defaults" }];
|
|
846
|
+
for (const agent of cfg.agents?.list ?? []) {
|
|
847
|
+
if (!agent || typeof agent !== "object" || typeof agent.id !== "string") {
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
contexts.push({
|
|
851
|
+
label: `agents.list.${agent.id}`,
|
|
852
|
+
agentId: agent.id,
|
|
853
|
+
tools: agent.tools,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
const riskyContexts = [];
|
|
857
|
+
let hasRuntimeRisk = false;
|
|
858
|
+
for (const context of contexts) {
|
|
859
|
+
const sandboxMode = resolveSandboxConfigForAgent(cfg, context.agentId).mode;
|
|
860
|
+
const policies = resolveToolPolicies({
|
|
861
|
+
cfg,
|
|
862
|
+
agentTools: context.tools,
|
|
863
|
+
sandboxMode,
|
|
864
|
+
agentId: context.agentId ?? null,
|
|
865
|
+
});
|
|
866
|
+
const runtimeTools = ["exec", "process"].filter((tool) => isToolAllowedByPolicies(tool, policies));
|
|
867
|
+
const fsTools = ["read", "write", "edit", "apply_patch"].filter((tool) => isToolAllowedByPolicies(tool, policies));
|
|
868
|
+
const fsWorkspaceOnly = context.tools?.fs?.workspaceOnly ?? cfg.tools?.fs?.workspaceOnly;
|
|
869
|
+
const runtimeUnguarded = runtimeTools.length > 0 && sandboxMode !== "all";
|
|
870
|
+
const fsUnguarded = fsTools.length > 0 && sandboxMode !== "all" && fsWorkspaceOnly !== true;
|
|
871
|
+
if (!runtimeUnguarded && !fsUnguarded) {
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (runtimeUnguarded) {
|
|
875
|
+
hasRuntimeRisk = true;
|
|
876
|
+
}
|
|
877
|
+
riskyContexts.push(`${context.label} (sandbox=${sandboxMode}; runtime=[${runtimeTools.join(", ") || "off"}]; fs=[${fsTools.join(", ") || "off"}]; fs.workspaceOnly=${fsWorkspaceOnly === true ? "true" : "false"})`);
|
|
878
|
+
}
|
|
879
|
+
if (riskyContexts.length > 0) {
|
|
880
|
+
findings.push({
|
|
881
|
+
checkId: "security.exposure.open_groups_with_runtime_or_fs",
|
|
882
|
+
severity: hasRuntimeRisk ? "critical" : "warn",
|
|
883
|
+
title: "Open groupPolicy with runtime/filesystem tools exposed",
|
|
884
|
+
detail: `Found groupPolicy="open" at:\n${openGroups.map((p) => `- ${p}`).join("\n")}\n` +
|
|
885
|
+
`Risky tool exposure contexts:\n${riskyContexts.map((line) => `- ${line}`).join("\n")}\n` +
|
|
886
|
+
"Prompt injection in open groups can trigger command/file actions in these contexts.",
|
|
887
|
+
remediation: 'For open groups, prefer tools.profile="messaging" (or deny group:runtime/group:fs), set tools.fs.workspaceOnly=true, and use agents.defaults.sandbox.mode="all" for exposed agents.',
|
|
888
|
+
});
|
|
889
|
+
}
|
|
811
890
|
return findings;
|
|
812
891
|
}
|
package/dist/security/audit.js
CHANGED
|
@@ -9,7 +9,7 @@ import { buildGatewayConnectionDetails } from "../gateway/call.js";
|
|
|
9
9
|
import { resolveGatewayProbeAuth } from "../gateway/probe-auth.js";
|
|
10
10
|
import { probeGateway } from "../gateway/probe.js";
|
|
11
11
|
import { collectChannelSecurityFindings } from "./audit-channel.js";
|
|
12
|
-
import { collectAttackSurfaceSummaryFindings, collectExposureMatrixFindings, collectGatewayHttpNoAuthFindings, collectGatewayHttpSessionKeyOverrideFindings, collectHooksHardeningFindings, collectIncludeFilePermFindings, collectInstalledSkillsCodeSafetyFindings, collectMinimalProfileOverrideFindings, collectModelHygieneFindings, collectNodeDenyCommandPatternFindings, collectSmallModelRiskFindings, collectSandboxDangerousConfigFindings, collectSandboxDockerNoopFindings, collectPluginsTrustFindings, collectSecretsInConfigFindings, collectPluginsCodeSafetyFindings, collectStateDeepFilesystemFindings, collectSyncedFolderFindings, readConfigSnapshotForAudit, } from "./audit-extra.js";
|
|
12
|
+
import { collectAttackSurfaceSummaryFindings, collectExposureMatrixFindings, collectGatewayHttpNoAuthFindings, collectGatewayHttpSessionKeyOverrideFindings, collectHooksHardeningFindings, collectIncludeFilePermFindings, collectInstalledSkillsCodeSafetyFindings, collectSandboxBrowserHashLabelFindings, collectMinimalProfileOverrideFindings, collectModelHygieneFindings, collectNodeDenyCommandPatternFindings, collectSmallModelRiskFindings, collectSandboxDangerousConfigFindings, collectSandboxDockerNoopFindings, collectPluginsTrustFindings, collectSecretsInConfigFindings, collectPluginsCodeSafetyFindings, collectStateDeepFilesystemFindings, collectSyncedFolderFindings, readConfigSnapshotForAudit, } from "./audit-extra.js";
|
|
13
13
|
import { formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, } from "./audit-fs.js";
|
|
14
14
|
import { DEFAULT_GATEWAY_HTTP_TOOL_DENY } from "./dangerous-tools.js";
|
|
15
15
|
function countBySeverity(findings) {
|
|
@@ -35,6 +35,29 @@ function normalizeAllowFromList(list) {
|
|
|
35
35
|
}
|
|
36
36
|
return list.map((v) => String(v).trim()).filter(Boolean);
|
|
37
37
|
}
|
|
38
|
+
function collectEnabledInsecureOrDangerousFlags(cfg) {
|
|
39
|
+
const enabledFlags = [];
|
|
40
|
+
if (cfg.gateway?.controlUi?.allowInsecureAuth === true) {
|
|
41
|
+
enabledFlags.push("gateway.controlUi.allowInsecureAuth=true");
|
|
42
|
+
}
|
|
43
|
+
if (cfg.gateway?.controlUi?.dangerouslyDisableDeviceAuth === true) {
|
|
44
|
+
enabledFlags.push("gateway.controlUi.dangerouslyDisableDeviceAuth=true");
|
|
45
|
+
}
|
|
46
|
+
if (cfg.hooks?.gmail?.allowUnsafeExternalContent === true) {
|
|
47
|
+
enabledFlags.push("hooks.gmail.allowUnsafeExternalContent=true");
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(cfg.hooks?.mappings)) {
|
|
50
|
+
for (const [index, mapping] of cfg.hooks.mappings.entries()) {
|
|
51
|
+
if (mapping?.allowUnsafeExternalContent === true) {
|
|
52
|
+
enabledFlags.push(`hooks.mappings[${index}].allowUnsafeExternalContent=true`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (cfg.tools?.exec?.applyPatch?.workspaceOnly === false) {
|
|
57
|
+
enabledFlags.push("tools.exec.applyPatch.workspaceOnly=false");
|
|
58
|
+
}
|
|
59
|
+
return enabledFlags;
|
|
60
|
+
}
|
|
38
61
|
async function collectFilesystemFindings(params) {
|
|
39
62
|
const findings = [];
|
|
40
63
|
const stateDirPerms = await inspectPathPermissions(params.stateDir, {
|
|
@@ -245,9 +268,9 @@ function collectGatewayConfigFindings(cfg, env) {
|
|
|
245
268
|
if (cfg.gateway?.controlUi?.allowInsecureAuth === true) {
|
|
246
269
|
findings.push({
|
|
247
270
|
checkId: "gateway.control_ui.insecure_auth",
|
|
248
|
-
severity: "
|
|
249
|
-
title: "Control UI
|
|
250
|
-
detail: "gateway.controlUi.allowInsecureAuth=true
|
|
271
|
+
severity: "warn",
|
|
272
|
+
title: "Control UI insecure auth toggle enabled",
|
|
273
|
+
detail: "gateway.controlUi.allowInsecureAuth=true does not bypass secure context or device identity checks; only dangerouslyDisableDeviceAuth disables Control UI device identity checks.",
|
|
251
274
|
remediation: "Disable it or switch to HTTPS (Tailscale Serve) or localhost.",
|
|
252
275
|
});
|
|
253
276
|
}
|
|
@@ -260,6 +283,16 @@ function collectGatewayConfigFindings(cfg, env) {
|
|
|
260
283
|
remediation: "Disable it unless you are in a short-lived break-glass scenario.",
|
|
261
284
|
});
|
|
262
285
|
}
|
|
286
|
+
const enabledDangerousFlags = collectEnabledInsecureOrDangerousFlags(cfg);
|
|
287
|
+
if (enabledDangerousFlags.length > 0) {
|
|
288
|
+
findings.push({
|
|
289
|
+
checkId: "config.insecure_or_dangerous_flags",
|
|
290
|
+
severity: "warn",
|
|
291
|
+
title: "Insecure or dangerous config flags enabled",
|
|
292
|
+
detail: `Detected ${enabledDangerousFlags.length} enabled flag(s): ${enabledDangerousFlags.join(", ")}.`,
|
|
293
|
+
remediation: "Disable these flags when not actively debugging, or keep deployment scoped to trusted/local-only networks.",
|
|
294
|
+
});
|
|
295
|
+
}
|
|
263
296
|
const token = typeof auth.token === "string" && auth.token.trim().length > 0 ? auth.token.trim() : null;
|
|
264
297
|
if (auth.mode === "token" && token && token.length < 24) {
|
|
265
298
|
findings.push({
|
|
@@ -536,6 +569,9 @@ export async function runSecurityAudit(opts) {
|
|
|
536
569
|
findings.push(...(await collectIncludeFilePermFindings({ configSnapshot, env, platform, execIcacls })));
|
|
537
570
|
}
|
|
538
571
|
findings.push(...(await collectStateDeepFilesystemFindings({ cfg, env, stateDir, platform, execIcacls })));
|
|
572
|
+
findings.push(...(await collectSandboxBrowserHashLabelFindings({
|
|
573
|
+
execDockerRawFn: opts.execDockerRawFn,
|
|
574
|
+
})));
|
|
539
575
|
findings.push(...(await collectPluginsTrustFindings({ cfg, stateDir })));
|
|
540
576
|
if (opts.deep === true) {
|
|
541
577
|
findings.push(...(await collectPluginsCodeSafetyFindings({ stateDir })));
|
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
import { readChannelAllowFromStore } from "../pairing/pairing-store.js";
|
|
2
2
|
import { normalizeStringEntries } from "../shared/string-normalization.js";
|
|
3
|
+
export function resolveEffectiveAllowFromLists(params) {
|
|
4
|
+
const configAllowFrom = normalizeStringEntries(Array.isArray(params.allowFrom) ? params.allowFrom : undefined);
|
|
5
|
+
const configGroupAllowFrom = normalizeStringEntries(Array.isArray(params.groupAllowFrom) ? params.groupAllowFrom : undefined);
|
|
6
|
+
const storeAllowFrom = params.dmPolicy === "allowlist"
|
|
7
|
+
? []
|
|
8
|
+
: normalizeStringEntries(Array.isArray(params.storeAllowFrom) ? params.storeAllowFrom : undefined);
|
|
9
|
+
const effectiveAllowFrom = normalizeStringEntries([...configAllowFrom, ...storeAllowFrom]);
|
|
10
|
+
const groupBase = configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom;
|
|
11
|
+
const effectiveGroupAllowFrom = normalizeStringEntries([...groupBase, ...storeAllowFrom]);
|
|
12
|
+
return { effectiveAllowFrom, effectiveGroupAllowFrom };
|
|
13
|
+
}
|
|
14
|
+
export function resolveDmGroupAccessDecision(params) {
|
|
15
|
+
const dmPolicy = params.dmPolicy ?? "pairing";
|
|
16
|
+
const groupPolicy = params.groupPolicy ?? "allowlist";
|
|
17
|
+
const effectiveAllowFrom = normalizeStringEntries(params.effectiveAllowFrom);
|
|
18
|
+
const effectiveGroupAllowFrom = normalizeStringEntries(params.effectiveGroupAllowFrom);
|
|
19
|
+
if (params.isGroup) {
|
|
20
|
+
if (groupPolicy === "disabled") {
|
|
21
|
+
return { decision: "block", reason: "groupPolicy=disabled" };
|
|
22
|
+
}
|
|
23
|
+
if (groupPolicy === "allowlist") {
|
|
24
|
+
if (effectiveGroupAllowFrom.length === 0) {
|
|
25
|
+
return { decision: "block", reason: "groupPolicy=allowlist (empty allowlist)" };
|
|
26
|
+
}
|
|
27
|
+
if (!params.isSenderAllowed(effectiveGroupAllowFrom)) {
|
|
28
|
+
return { decision: "block", reason: "groupPolicy=allowlist (not allowlisted)" };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { decision: "allow", reason: `groupPolicy=${groupPolicy}` };
|
|
32
|
+
}
|
|
33
|
+
if (dmPolicy === "disabled") {
|
|
34
|
+
return { decision: "block", reason: "dmPolicy=disabled" };
|
|
35
|
+
}
|
|
36
|
+
if (dmPolicy === "open") {
|
|
37
|
+
return { decision: "allow", reason: "dmPolicy=open" };
|
|
38
|
+
}
|
|
39
|
+
if (params.isSenderAllowed(effectiveAllowFrom)) {
|
|
40
|
+
return { decision: "allow", reason: `dmPolicy=${dmPolicy} (allowlisted)` };
|
|
41
|
+
}
|
|
42
|
+
if (dmPolicy === "pairing") {
|
|
43
|
+
return { decision: "pairing", reason: "dmPolicy=pairing (not allowlisted)" };
|
|
44
|
+
}
|
|
45
|
+
return { decision: "block", reason: `dmPolicy=${dmPolicy} (not allowlisted)` };
|
|
46
|
+
}
|
|
3
47
|
export async function resolveDmAllowState(params) {
|
|
4
48
|
const configAllowFrom = normalizeStringEntries(Array.isArray(params.allowFrom) ? params.allowFrom : undefined);
|
|
5
49
|
const hasWildcard = configAllowFrom.includes("*");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
1
2
|
/**
|
|
2
3
|
* Security utilities for handling untrusted external content.
|
|
3
4
|
*
|
|
@@ -40,9 +41,20 @@ export function detectSuspiciousPatterns(content) {
|
|
|
40
41
|
/**
|
|
41
42
|
* Unique boundary markers for external content.
|
|
42
43
|
* Using XML-style tags that are unlikely to appear in legitimate content.
|
|
44
|
+
* Each wrapper gets a unique random ID to prevent spoofing attacks where
|
|
45
|
+
* malicious content injects fake boundary markers.
|
|
43
46
|
*/
|
|
44
|
-
const
|
|
45
|
-
const
|
|
47
|
+
const EXTERNAL_CONTENT_START_NAME = "EXTERNAL_UNTRUSTED_CONTENT";
|
|
48
|
+
const EXTERNAL_CONTENT_END_NAME = "END_EXTERNAL_UNTRUSTED_CONTENT";
|
|
49
|
+
function createExternalContentMarkerId() {
|
|
50
|
+
return randomBytes(8).toString("hex");
|
|
51
|
+
}
|
|
52
|
+
function createExternalContentStartMarker(id) {
|
|
53
|
+
return `<<<${EXTERNAL_CONTENT_START_NAME} id="${id}">>>`;
|
|
54
|
+
}
|
|
55
|
+
function createExternalContentEndMarker(id) {
|
|
56
|
+
return `<<<${EXTERNAL_CONTENT_END_NAME} id="${id}">>>`;
|
|
57
|
+
}
|
|
46
58
|
/**
|
|
47
59
|
* Security warning prepended to external content.
|
|
48
60
|
*/
|
|
@@ -107,9 +119,16 @@ function replaceMarkers(content) {
|
|
|
107
119
|
return content;
|
|
108
120
|
}
|
|
109
121
|
const replacements = [];
|
|
122
|
+
// Match markers with or without id attribute (handles both legacy and spoofed markers)
|
|
110
123
|
const patterns = [
|
|
111
|
-
{
|
|
112
|
-
|
|
124
|
+
{
|
|
125
|
+
regex: /<<<EXTERNAL_UNTRUSTED_CONTENT(?:\s+id="[^"]{1,128}")?\s*>>>/gi,
|
|
126
|
+
value: "[[MARKER_SANITIZED]]",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
regex: /<<<END_EXTERNAL_UNTRUSTED_CONTENT(?:\s+id="[^"]{1,128}")?\s*>>>/gi,
|
|
130
|
+
value: "[[END_MARKER_SANITIZED]]",
|
|
131
|
+
},
|
|
113
132
|
];
|
|
114
133
|
for (const pattern of patterns) {
|
|
115
134
|
pattern.regex.lastIndex = 0;
|
|
@@ -168,13 +187,14 @@ export function wrapExternalContent(content, options) {
|
|
|
168
187
|
}
|
|
169
188
|
const metadata = metadataLines.join("\n");
|
|
170
189
|
const warningBlock = includeWarning ? `${EXTERNAL_CONTENT_WARNING}\n\n` : "";
|
|
190
|
+
const markerId = createExternalContentMarkerId();
|
|
171
191
|
return [
|
|
172
192
|
warningBlock,
|
|
173
|
-
|
|
193
|
+
createExternalContentStartMarker(markerId),
|
|
174
194
|
metadata,
|
|
175
195
|
"---",
|
|
176
196
|
sanitized,
|
|
177
|
-
|
|
197
|
+
createExternalContentEndMarker(markerId),
|
|
178
198
|
].join("\n");
|
|
179
199
|
}
|
|
180
200
|
/**
|