@poolzin/pool-bot 2026.2.21 → 2026.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/agents/api-key-rotation.js +47 -0
- package/dist/agents/apply-patch-update.js +19 -9
- package/dist/agents/apply-patch.js +72 -47
- package/dist/agents/bash-tools.exec.js +141 -559
- package/dist/agents/cli-backends.js +49 -6
- package/dist/agents/cli-runner/helpers.js +69 -152
- package/dist/agents/cli-runner.js +70 -19
- package/dist/agents/identity.js +20 -1
- package/dist/agents/image-sanitization.js +9 -0
- package/dist/agents/live-auth-keys.js +123 -26
- package/dist/agents/live-model-filter.js +13 -4
- package/dist/agents/model-catalog.js +40 -9
- package/dist/agents/model-forward-compat.js +60 -23
- package/dist/agents/model-selection.js +134 -41
- package/dist/agents/pi-auth-json.js +2 -2
- package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
- package/dist/agents/pi-embedded-helpers/errors.js +140 -15
- package/dist/agents/pi-embedded-helpers/images.js +22 -12
- package/dist/agents/pi-embedded-helpers.js +2 -2
- package/dist/agents/pi-embedded-runner/abort.js +10 -3
- package/dist/agents/pi-embedded-runner/compact.js +230 -32
- package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
- package/dist/agents/pi-embedded-runner/google.js +109 -19
- package/dist/agents/pi-embedded-runner/history.js +35 -17
- package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
- package/dist/agents/pi-embedded-runner/run/images.js +81 -55
- package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
- package/dist/agents/pi-embedded-runner/run.js +193 -25
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
- package/dist/agents/pi-embedded-runner/runs.js +17 -8
- package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
- package/dist/agents/pi-embedded-runner.js +1 -1
- package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
- package/dist/agents/pi-embedded-subscribe.js +37 -0
- package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
- package/dist/agents/pi-model-discovery.js +9 -2
- package/dist/agents/pi-tool-definition-adapter.js +60 -8
- package/dist/agents/pi-tools.before-tool-call.js +1 -1
- package/dist/agents/pi-tools.js +113 -94
- package/dist/agents/pi-tools.read.js +337 -38
- package/dist/agents/poolbot-tools.js +14 -5
- package/dist/agents/sandbox/docker.js +10 -5
- package/dist/agents/sandbox/registry.js +96 -46
- package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
- package/dist/agents/sandbox-paths.js +43 -10
- package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
- package/dist/agents/session-tool-result-guard.js +39 -39
- package/dist/agents/session-transcript-repair.js +36 -33
- package/dist/agents/session-write-lock.js +62 -44
- package/dist/agents/skills/frontmatter.js +49 -88
- package/dist/agents/skills/workspace.js +335 -28
- package/dist/agents/subagent-announce.js +508 -174
- package/dist/agents/subagent-registry.js +45 -4
- package/dist/agents/subagent-spawn.js +16 -33
- package/dist/agents/system-prompt-report.js +27 -10
- package/dist/agents/system-prompt.js +26 -32
- package/dist/agents/tool-call-id.js +69 -17
- package/dist/agents/tool-display-common.js +1 -1
- package/dist/agents/tool-images.js +64 -31
- package/dist/agents/tools/canvas-tool.js +17 -11
- package/dist/agents/tools/common.js +37 -19
- package/dist/agents/tools/cron-tool.js +40 -38
- package/dist/agents/tools/gateway.js +70 -2
- package/dist/agents/tools/message-tool.js +181 -40
- package/dist/agents/tools/nodes-tool.js +128 -36
- package/dist/agents/tools/nodes-utils.js +12 -38
- package/dist/agents/tools/session-status-tool.js +24 -71
- package/dist/agents/tools/sessions-helpers.js +38 -210
- package/dist/agents/tools/sessions-spawn-tool.js +28 -198
- package/dist/agents/tools/telegram-actions.js +58 -7
- package/dist/agents/tools/web-fetch-utils.js +112 -7
- package/dist/agents/tools/web-fetch.js +279 -175
- package/dist/agents/tools/web-shared.js +71 -8
- package/dist/agents/usage.js +25 -16
- package/dist/auto-reply/commands-registry.data.js +85 -11
- package/dist/auto-reply/dispatch.js +40 -21
- package/dist/auto-reply/reply/abort.js +102 -33
- package/dist/auto-reply/reply/commands-core.js +82 -33
- package/dist/auto-reply/reply/commands-export-session.js +1 -1
- package/dist/auto-reply/reply/commands-info.js +41 -12
- package/dist/auto-reply/reply/commands-subagents.js +352 -100
- package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
- package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
- package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
- package/dist/auto-reply/reply/inbound-meta.js +12 -1
- package/dist/auto-reply/reply/mentions.js +18 -11
- package/dist/auto-reply/reply/normalize-reply.js +17 -8
- package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
- package/dist/auto-reply/reply/session.js +102 -21
- package/dist/auto-reply/reply/streaming-directives.js +16 -5
- package/dist/auto-reply/status.js +73 -50
- package/dist/browser/extension-relay.js +3 -3
- package/dist/browser/http-auth.js +1 -1
- package/dist/browser/paths.js +2 -2
- package/dist/build-info.json +3 -3
- package/dist/channels/allowlist-match.js +20 -0
- package/dist/channels/allowlists/resolve-utils.js +65 -2
- package/dist/channels/chat-type.js +8 -4
- package/dist/channels/dock.js +127 -35
- package/dist/channels/draft-stream-loop.js +6 -2
- package/dist/channels/plugins/actions/telegram.js +42 -18
- package/dist/channels/plugins/allowlist-match.js +1 -1
- package/dist/channels/plugins/group-mentions.js +51 -41
- package/dist/channels/plugins/message-action-names.js +2 -0
- package/dist/channels/plugins/message-actions.js +24 -5
- package/dist/channels/plugins/normalize/discord.js +26 -4
- package/dist/channels/plugins/normalize/signal.js +35 -22
- package/dist/channels/plugins/onboarding/helpers.js +8 -26
- package/dist/channels/plugins/outbound/imessage.js +15 -14
- package/dist/channels/registry.js +20 -7
- package/dist/cli/acp-cli.js +7 -5
- package/dist/cli/browser-cli-extension.js +25 -12
- package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
- package/dist/cli/browser-cli-state.js +101 -145
- package/dist/cli/command-options.js +28 -0
- package/dist/cli/completion-cli.js +6 -6
- package/dist/cli/cron-cli/register.cron-add.js +25 -1
- package/dist/cli/cron-cli/register.cron-edit.js +44 -0
- package/dist/cli/cron-cli/shared.js +7 -1
- package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
- package/dist/cli/daemon-cli/lifecycle.js +23 -247
- package/dist/cli/daemon-cli/register-service-commands.js +25 -4
- package/dist/cli/daemon-cli.js +1 -0
- package/dist/cli/devices-cli.js +33 -20
- package/dist/cli/gateway-cli/register.js +37 -105
- package/dist/cli/gateway-cli/run.js +49 -11
- package/dist/cli/nodes-camera.js +59 -4
- package/dist/cli/nodes-cli/register.camera.js +27 -24
- package/dist/cli/nodes-cli/rpc.js +21 -38
- package/dist/cli/qr-cli.js +2 -2
- package/dist/cli/skills-cli.format.js +2 -2
- package/dist/cli/update-cli/progress.js +2 -2
- package/dist/cli/update-cli/restart-helper.js +28 -7
- package/dist/cli/update-cli/shared.js +7 -7
- package/dist/cli/update-cli/status.js +1 -1
- package/dist/cli/update-cli/update-command.js +14 -8
- package/dist/cli/update-cli/wizard.js +2 -2
- package/dist/cli/update-cli.js +21 -1027
- package/dist/commands/auth-choice.apply.anthropic.js +10 -2
- package/dist/commands/channels/add-mutators.js +3 -35
- package/dist/commands/channels/add.js +39 -51
- package/dist/commands/config-validation.js +1 -1
- package/dist/commands/configure.gateway-auth.js +52 -15
- package/dist/commands/configure.gateway.js +84 -40
- package/dist/commands/doctor-completion.js +3 -3
- package/dist/commands/doctor-config-flow.js +536 -16
- package/dist/commands/doctor-gateway-services.js +103 -79
- package/dist/commands/doctor-memory-search.js +9 -9
- package/dist/commands/doctor-platform-notes.js +57 -30
- package/dist/commands/doctor-prompter.js +26 -15
- package/dist/commands/doctor-session-locks.js +1 -1
- package/dist/commands/doctor.js +21 -9
- package/dist/commands/model-picker.js +120 -95
- package/dist/commands/models/set.js +2 -21
- package/dist/commands/models/shared.js +65 -37
- package/dist/commands/onboard-helpers.js +81 -39
- package/dist/commands/openai-codex-oauth.js +1 -1
- package/dist/commands/sessions.js +52 -53
- package/dist/commands/status.summary.js +52 -34
- package/dist/commands/test-wizard-helpers.js +2 -2
- package/dist/config/defaults.js +79 -42
- package/dist/config/group-policy.js +50 -18
- package/dist/config/includes.js +37 -10
- package/dist/config/schema.help.js +5 -4
- package/dist/config/schema.hints.js +2 -2
- package/dist/config/schema.labels.js +1 -0
- package/dist/config/sessions/group.js +12 -11
- package/dist/config/sessions/paths.js +137 -11
- package/dist/config/sessions/store.js +185 -65
- package/dist/config/sessions/types.js +15 -1
- package/dist/config/sessions.js +1 -0
- package/dist/config/telegram-custom-commands.js +3 -2
- package/dist/config/types.js +2 -0
- package/dist/config/zod-schema.agent-defaults.js +6 -27
- package/dist/config/zod-schema.agent-runtime.js +171 -79
- package/dist/config/zod-schema.providers-core.js +138 -65
- package/dist/config/zod-schema.session.js +49 -22
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
- package/dist/cron/isolated-agent/run.js +224 -57
- package/dist/cron/normalize.js +48 -45
- package/dist/cron/run-log.js +14 -0
- package/dist/cron/service/jobs.js +190 -28
- package/dist/cron/service/normalize.js +29 -11
- package/dist/cron/service/store.js +30 -44
- package/dist/cron/service/timer.js +182 -96
- package/dist/cron/service.js +3 -0
- package/dist/cron/stagger.js +37 -0
- package/dist/daemon/inspect.js +132 -92
- package/dist/daemon/runtime-paths.js +25 -4
- package/dist/daemon/service-audit.js +47 -16
- package/dist/discord/accounts.js +23 -20
- package/dist/discord/monitor/agent-components.js +1115 -219
- package/dist/discord/monitor/allow-list.js +114 -34
- package/dist/discord/monitor/listeners.js +204 -97
- package/dist/discord/monitor/message-handler.js +21 -10
- package/dist/discord/monitor/message-handler.preflight.js +195 -101
- package/dist/discord/monitor/message-handler.process.js +384 -123
- package/dist/discord/monitor/message-utils.js +86 -23
- package/dist/discord/monitor/native-command.js +77 -57
- package/dist/discord/monitor/provider.js +122 -117
- package/dist/discord/monitor/reply-context.js +20 -16
- package/dist/discord/monitor/reply-delivery.js +40 -8
- package/dist/discord/monitor/rest-fetch.js +22 -0
- package/dist/discord/monitor/threading.js +117 -24
- package/dist/discord/send.js +2 -1
- package/dist/discord/send.outbound.js +124 -11
- package/dist/discord/send.shared.js +112 -72
- package/dist/discord/voice-message.js +3 -3
- package/dist/gateway/auth.js +119 -44
- package/dist/gateway/call.js +76 -34
- package/dist/gateway/channel-health-monitor.js +57 -50
- package/dist/gateway/client.js +63 -29
- package/dist/gateway/control-ui-contract.js +1 -1
- package/dist/gateway/gateway-config-prompts.shared.js +2 -2
- package/dist/gateway/net.js +109 -1
- package/dist/gateway/protocol/index.js +5 -8
- package/dist/gateway/protocol/schema/agent.js +19 -1
- package/dist/gateway/protocol/schema/channels.js +21 -0
- package/dist/gateway/protocol/schema/cron.js +43 -30
- package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
- package/dist/gateway/protocol/schema/sessions.js +5 -1
- package/dist/gateway/protocol/schema.js +0 -1
- package/dist/gateway/server/presence-events.js +12 -0
- package/dist/gateway/server/ws-connection/message-handler.js +203 -212
- package/dist/gateway/server/ws-connection.js +58 -21
- package/dist/gateway/server-broadcast.js +18 -13
- package/dist/gateway/server-cron.js +177 -10
- package/dist/gateway/server-methods/agent-job.js +131 -38
- package/dist/gateway/server-methods/send.js +60 -14
- package/dist/gateway/server-methods/sessions.js +160 -96
- package/dist/gateway/server-methods/system.js +5 -7
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +24 -8
- package/dist/gateway/server-node-events.js +278 -68
- package/dist/gateway/session-utils.fs.js +316 -75
- package/dist/gateway/session-utils.js +224 -70
- package/dist/gateway/sessions-patch.js +63 -20
- package/dist/gateway/test-temp-config.js +1 -1
- package/dist/gateway/tools-invoke-http.js +118 -70
- package/dist/gateway/ws-log.js +135 -107
- package/dist/hooks/frontmatter.js +36 -82
- package/dist/hooks/install.js +149 -139
- package/dist/hooks/internal-hooks.js +29 -4
- package/dist/hooks/plugin-hooks.js +2 -1
- package/dist/imessage/monitor/deliver.js +10 -4
- package/dist/imessage/monitor/monitor-provider.js +138 -375
- package/dist/imessage/monitor/runtime.js +4 -8
- package/dist/imessage/send.js +65 -19
- package/dist/infra/exec-approvals-allowlist.js +7 -0
- package/dist/infra/exec-approvals.js +35 -920
- package/dist/infra/exec-safe-bin-trust.js +64 -0
- package/dist/infra/heartbeat-runner.js +207 -134
- package/dist/infra/heartbeat-wake.js +183 -22
- package/dist/infra/install-source-utils.js +47 -0
- package/dist/infra/net/ssrf.js +170 -36
- package/dist/infra/outbound/deliver.js +224 -58
- package/dist/infra/outbound/message-action-spec.js +12 -5
- package/dist/infra/outbound/outbound-session.js +27 -25
- package/dist/infra/poolbot-root.js +32 -22
- package/dist/infra/ports.js +14 -11
- package/dist/infra/skills-remote.js +48 -37
- package/dist/infra/system-events.js +25 -11
- package/dist/infra/system-presence.js +26 -33
- package/dist/infra/tmp-poolbot-dir.js +81 -2
- package/dist/infra/wsl.js +37 -1
- package/dist/line/bot-message-context.js +163 -191
- package/dist/logging/subsystem.js +59 -22
- package/dist/markdown/ir.js +124 -50
- package/dist/media/store.js +1 -1
- package/dist/media-understanding/runner.entries.js +42 -25
- package/dist/media-understanding/runner.js +53 -488
- package/dist/memory/embeddings-gemini.js +53 -38
- package/dist/memory/manager-embedding-ops.js +48 -69
- package/dist/pairing/pairing-store.js +178 -119
- package/dist/plugin-sdk/index.js +34 -6
- package/dist/plugins/hooks.js +135 -14
- package/dist/plugins/install.js +190 -152
- package/dist/polls.js +11 -0
- package/dist/routing/resolve-route.js +190 -56
- package/dist/routing/session-key.js +38 -22
- package/dist/runtime.js +35 -9
- package/dist/security/audit-channel.js +1 -1
- package/dist/sessions/session-key-utils.js +29 -11
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-list-types.js +1 -0
- package/dist/shared/string-normalization.js +15 -0
- package/dist/signal/monitor/event-handler.js +68 -36
- package/dist/signal/send.js +29 -37
- package/dist/slack/monitor/allow-list.js +10 -11
- package/dist/slack/monitor/commands.js +14 -3
- package/dist/slack/monitor/events/interactions.js +4 -4
- package/dist/slack/monitor/media.js +224 -16
- package/dist/slack/monitor/message-handler/dispatch.js +247 -13
- package/dist/slack/monitor/message-handler/prepare.js +128 -45
- package/dist/slack/monitor/slash.js +357 -144
- package/dist/slack/streaming.js +77 -0
- package/dist/telegram/accounts.js +40 -13
- package/dist/telegram/allowed-updates.js +3 -0
- package/dist/telegram/bot/delivery.js +129 -66
- package/dist/telegram/bot/helpers.js +136 -122
- package/dist/telegram/bot-handlers.js +600 -339
- package/dist/telegram/bot-message-context.js +115 -73
- package/dist/telegram/bot-message-dispatch.js +235 -104
- package/dist/telegram/bot-native-command-menu.js +3 -1
- package/dist/telegram/bot-native-commands.js +213 -193
- package/dist/telegram/bot.js +24 -132
- package/dist/telegram/draft-stream.js +84 -75
- package/dist/telegram/format.js +150 -6
- package/dist/telegram/send.js +415 -255
- package/dist/telegram/targets.js +21 -2
- package/dist/telegram/update-offset-store.js +19 -3
- package/dist/terminal/restore.js +5 -2
- package/dist/test-utils/fetch-mock.js +5 -0
- package/dist/version.js +18 -5
- package/dist/web/auto-reply/monitor/broadcast.js +7 -3
- package/dist/web/auto-reply/monitor/on-message.js +6 -3
- package/dist/web/inbound/media.js +34 -8
- package/dist/web/inbound/monitor.js +34 -17
- package/dist/web/inbound/send-api.js +18 -17
- package/dist/web/outbound.js +12 -5
- package/dist/wizard/clack-prompter.js +40 -7
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/device-pair/index.ts +2 -2
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/irc/src/accounts.ts +1 -1
- package/extensions/irc/src/onboarding.ts +4 -4
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +10 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +10 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +10 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +10 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +10 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +10 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +10 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
- package/skills/apple-reminders/SKILL.md +100 -49
- package/skills/coding-agent/SKILL.md +34 -28
- package/skills/github/SKILL.md +131 -16
- package/skills/imsg/SKILL.md +112 -15
- package/skills/openhue/SKILL.md +101 -19
- package/skills/tmux/SKILL.md +111 -79
- package/skills/weather/SKILL.md +88 -25
- package/dist/agents/openclaw-tools.js +0 -151
- package/dist/agents/tool-security.js +0 -96
- package/dist/gateway/url-validation.js +0 -94
- package/dist/infra/openclaw-root.js +0 -109
- package/dist/infra/tmp-openclaw-dir.js +0 -81
- package/dist/media/path-sanitization.js +0 -78
|
@@ -1,88 +1,33 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
|
+
import { loadConfig } from "../../../config/config.js";
|
|
2
3
|
import { deriveDeviceIdFromPublicKey, normalizeDevicePublicKeyBase64Url, verifyDeviceSignature, } from "../../../infra/device-identity.js";
|
|
3
4
|
import { approveDevicePairing, ensureDeviceToken, getPairedDevice, requestDevicePairing, updatePairedDeviceMetadata, verifyDeviceToken, } from "../../../infra/device-pairing.js";
|
|
4
5
|
import { updatePairedNodeMetadata } from "../../../infra/node-pairing.js";
|
|
5
6
|
import { recordRemoteNodeInfo, refreshRemoteNodeBins } from "../../../infra/skills-remote.js";
|
|
6
|
-
import { loadVoiceWakeConfig } from "../../../infra/voicewake.js";
|
|
7
7
|
import { upsertPresence } from "../../../infra/system-presence.js";
|
|
8
|
+
import { loadVoiceWakeConfig } from "../../../infra/voicewake.js";
|
|
8
9
|
import { rawDataToString } from "../../../infra/ws.js";
|
|
9
10
|
import { isGatewayCliClient, isWebchatClient } from "../../../utils/message-channel.js";
|
|
11
|
+
import { resolveRuntimeServiceVersion } from "../../../version.js";
|
|
12
|
+
import { AUTH_RATE_LIMIT_SCOPE_DEVICE_TOKEN, AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET, } from "../../auth-rate-limit.js";
|
|
10
13
|
import { authorizeGatewayConnect, isLocalDirectRequest } from "../../auth.js";
|
|
11
|
-
import { loadConfig } from "../../../config/config.js";
|
|
12
14
|
import { buildDeviceAuthPayload } from "../../device-auth.js";
|
|
13
15
|
import { isLoopbackAddress, isTrustedProxyAddress, resolveGatewayClientIp } from "../../net.js";
|
|
16
|
+
import { resolveHostName } from "../../net.js";
|
|
14
17
|
import { resolveNodeCommandAllowlist } from "../../node-command-policy.js";
|
|
15
|
-
import {
|
|
18
|
+
import { checkBrowserOrigin } from "../../origin-check.js";
|
|
16
19
|
import { GATEWAY_CLIENT_IDS } from "../../protocol/client-info.js";
|
|
20
|
+
import { ErrorCodes, errorShape, formatValidationErrors, PROTOCOL_VERSION, validateConnectParams, validateRequestFrame, } from "../../protocol/index.js";
|
|
17
21
|
import { MAX_BUFFERED_BYTES, MAX_PAYLOAD_BYTES, TICK_INTERVAL_MS } from "../../server-constants.js";
|
|
18
22
|
import { handleGatewayRequest } from "../../server-methods.js";
|
|
19
23
|
import { formatError } from "../../server-utils.js";
|
|
20
24
|
import { formatForLog, logWs } from "../../ws-log.js";
|
|
21
25
|
import { truncateCloseReason } from "../close-reason.js";
|
|
22
26
|
import { buildGatewaySnapshot, getHealthCache, getHealthVersion, incrementPresenceVersion, refreshGatewayHealthSnapshot, } from "../health-state.js";
|
|
27
|
+
import { formatGatewayAuthFailureMessage } from "./auth-messages.js";
|
|
23
28
|
const DEVICE_SIGNATURE_SKEW_MS = 10 * 60 * 1000;
|
|
24
|
-
function resolveHostName(hostHeader) {
|
|
25
|
-
const host = (hostHeader ?? "").trim().toLowerCase();
|
|
26
|
-
if (!host)
|
|
27
|
-
return "";
|
|
28
|
-
if (host.startsWith("[")) {
|
|
29
|
-
const end = host.indexOf("]");
|
|
30
|
-
if (end !== -1)
|
|
31
|
-
return host.slice(1, end);
|
|
32
|
-
}
|
|
33
|
-
const [name] = host.split(":");
|
|
34
|
-
return name ?? "";
|
|
35
|
-
}
|
|
36
|
-
function formatGatewayAuthFailureMessage(params) {
|
|
37
|
-
const { authMode, authProvided, reason, client } = params;
|
|
38
|
-
const isCli = isGatewayCliClient(client);
|
|
39
|
-
const isControlUi = client?.id === GATEWAY_CLIENT_IDS.CONTROL_UI;
|
|
40
|
-
const isWebchat = isWebchatClient(client);
|
|
41
|
-
const uiHint = "open a tokenized dashboard URL or paste token in Control UI settings";
|
|
42
|
-
const tokenHint = isCli
|
|
43
|
-
? "set gateway.remote.token to match gateway.auth.token"
|
|
44
|
-
: isControlUi || isWebchat
|
|
45
|
-
? uiHint
|
|
46
|
-
: "provide gateway auth token";
|
|
47
|
-
const passwordHint = isCli
|
|
48
|
-
? "set gateway.remote.password to match gateway.auth.password"
|
|
49
|
-
: isControlUi || isWebchat
|
|
50
|
-
? "enter the password in Control UI settings"
|
|
51
|
-
: "provide gateway auth password";
|
|
52
|
-
switch (reason) {
|
|
53
|
-
case "token_missing":
|
|
54
|
-
return `unauthorized: gateway token missing (${tokenHint})`;
|
|
55
|
-
case "token_mismatch":
|
|
56
|
-
return `unauthorized: gateway token mismatch (${tokenHint})`;
|
|
57
|
-
case "token_missing_config":
|
|
58
|
-
return "unauthorized: gateway token not configured on gateway (set gateway.auth.token)";
|
|
59
|
-
case "password_missing":
|
|
60
|
-
return `unauthorized: gateway password missing (${passwordHint})`;
|
|
61
|
-
case "password_mismatch":
|
|
62
|
-
return `unauthorized: gateway password mismatch (${passwordHint})`;
|
|
63
|
-
case "password_missing_config":
|
|
64
|
-
return "unauthorized: gateway password not configured on gateway (set gateway.auth.password)";
|
|
65
|
-
case "tailscale_user_missing":
|
|
66
|
-
return "unauthorized: tailscale identity missing (use Tailscale Serve auth or gateway token/password)";
|
|
67
|
-
case "tailscale_proxy_missing":
|
|
68
|
-
return "unauthorized: tailscale proxy headers missing (use Tailscale Serve or gateway token/password)";
|
|
69
|
-
case "tailscale_whois_failed":
|
|
70
|
-
return "unauthorized: tailscale identity check failed (use Tailscale Serve auth or gateway token/password)";
|
|
71
|
-
case "tailscale_user_mismatch":
|
|
72
|
-
return "unauthorized: tailscale identity mismatch (use Tailscale Serve auth or gateway token/password)";
|
|
73
|
-
default:
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
if (authMode === "token" && authProvided === "none") {
|
|
77
|
-
return `unauthorized: gateway token missing (${tokenHint})`;
|
|
78
|
-
}
|
|
79
|
-
if (authMode === "password" && authProvided === "none") {
|
|
80
|
-
return `unauthorized: gateway password missing (${passwordHint})`;
|
|
81
|
-
}
|
|
82
|
-
return "unauthorized";
|
|
83
|
-
}
|
|
84
29
|
export function attachGatewayWsMessageHandler(params) {
|
|
85
|
-
const { socket, upgradeReq, connId, remoteAddr, forwardedFor, realIp, requestHost, requestOrigin, requestUserAgent, canvasHostUrl, connectNonce, resolvedAuth, gatewayMethods, events, extraHandlers, buildRequestContext, send, close, isClosed, clearHandshakeTimer, getClient, setClient, setHandshakeState, setCloseCause, setLastFrameMeta, logGateway, logHealth, logWsControl, } = params;
|
|
30
|
+
const { socket, upgradeReq, connId, remoteAddr, forwardedFor, realIp, requestHost, requestOrigin, requestUserAgent, canvasHostUrl, connectNonce, resolvedAuth, rateLimiter, gatewayMethods, events, extraHandlers, buildRequestContext, send, close, isClosed, clearHandshakeTimer, getClient, setClient, setHandshakeState, setCloseCause, setLastFrameMeta, logGateway, logHealth, logWsControl, } = params;
|
|
86
31
|
const configSnapshot = loadConfig();
|
|
87
32
|
const trustedProxies = configSnapshot.gateway?.trustedProxies ?? [];
|
|
88
33
|
const clientIp = resolveGatewayClientIp({ remoteAddr, forwardedFor, realIp, trustedProxies });
|
|
@@ -115,8 +60,9 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
115
60
|
}
|
|
116
61
|
const isWebchatConnect = (p) => isWebchatClient(p?.client);
|
|
117
62
|
socket.on("message", async (data) => {
|
|
118
|
-
if (isClosed())
|
|
63
|
+
if (isClosed()) {
|
|
119
64
|
return;
|
|
65
|
+
}
|
|
120
66
|
const text = rawDataToString(data);
|
|
121
67
|
try {
|
|
122
68
|
const parsed = JSON.parse(text);
|
|
@@ -182,27 +128,35 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
182
128
|
const frame = parsed;
|
|
183
129
|
const connectParams = frame.params;
|
|
184
130
|
const clientLabel = connectParams.client.displayName ?? connectParams.client.id;
|
|
131
|
+
const clientMeta = {
|
|
132
|
+
client: connectParams.client.id,
|
|
133
|
+
clientDisplayName: connectParams.client.displayName,
|
|
134
|
+
mode: connectParams.client.mode,
|
|
135
|
+
version: connectParams.client.version,
|
|
136
|
+
};
|
|
137
|
+
const markHandshakeFailure = (cause, meta) => {
|
|
138
|
+
setHandshakeState("failed");
|
|
139
|
+
setCloseCause(cause, { ...meta, ...clientMeta });
|
|
140
|
+
};
|
|
141
|
+
const sendHandshakeErrorResponse = (code, message, options) => {
|
|
142
|
+
send({
|
|
143
|
+
type: "res",
|
|
144
|
+
id: frame.id,
|
|
145
|
+
ok: false,
|
|
146
|
+
error: errorShape(code, message, options),
|
|
147
|
+
});
|
|
148
|
+
};
|
|
185
149
|
// protocol negotiation
|
|
186
150
|
const { minProtocol, maxProtocol } = connectParams;
|
|
187
151
|
if (maxProtocol < PROTOCOL_VERSION || minProtocol > PROTOCOL_VERSION) {
|
|
188
|
-
|
|
189
|
-
logWsControl.warn(`protocol mismatch conn=${connId} remote=${remoteAddr ?? "?"} client=${clientLabel} ${connectParams.client.mode} v${connectParams.client.version}`);
|
|
190
|
-
setCloseCause("protocol-mismatch", {
|
|
152
|
+
markHandshakeFailure("protocol-mismatch", {
|
|
191
153
|
minProtocol,
|
|
192
154
|
maxProtocol,
|
|
193
155
|
expectedProtocol: PROTOCOL_VERSION,
|
|
194
|
-
client: connectParams.client.id,
|
|
195
|
-
clientDisplayName: connectParams.client.displayName,
|
|
196
|
-
mode: connectParams.client.mode,
|
|
197
|
-
version: connectParams.client.version,
|
|
198
156
|
});
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
ok: false,
|
|
203
|
-
error: errorShape(ErrorCodes.INVALID_REQUEST, "protocol mismatch", {
|
|
204
|
-
details: { expectedProtocol: PROTOCOL_VERSION },
|
|
205
|
-
}),
|
|
157
|
+
logWsControl.warn(`protocol mismatch conn=${connId} remote=${remoteAddr ?? "?"} client=${clientLabel} ${connectParams.client.mode} v${connectParams.client.version}`);
|
|
158
|
+
sendHandshakeErrorResponse(ErrorCodes.INVALID_REQUEST, "protocol mismatch", {
|
|
159
|
+
details: { expectedProtocol: PROTOCOL_VERSION },
|
|
206
160
|
});
|
|
207
161
|
close(1002, "protocol mismatch");
|
|
208
162
|
return;
|
|
@@ -210,76 +164,137 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
210
164
|
const roleRaw = connectParams.role ?? "operator";
|
|
211
165
|
const role = roleRaw === "operator" || roleRaw === "node" ? roleRaw : null;
|
|
212
166
|
if (!role) {
|
|
213
|
-
|
|
214
|
-
setCloseCause("invalid-role", {
|
|
167
|
+
markHandshakeFailure("invalid-role", {
|
|
215
168
|
role: roleRaw,
|
|
216
|
-
client: connectParams.client.id,
|
|
217
|
-
clientDisplayName: connectParams.client.displayName,
|
|
218
|
-
mode: connectParams.client.mode,
|
|
219
|
-
version: connectParams.client.version,
|
|
220
|
-
});
|
|
221
|
-
send({
|
|
222
|
-
type: "res",
|
|
223
|
-
id: frame.id,
|
|
224
|
-
ok: false,
|
|
225
|
-
error: errorShape(ErrorCodes.INVALID_REQUEST, "invalid role"),
|
|
226
169
|
});
|
|
170
|
+
sendHandshakeErrorResponse(ErrorCodes.INVALID_REQUEST, "invalid role");
|
|
227
171
|
close(1008, "invalid role");
|
|
228
172
|
return;
|
|
229
173
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
? ["operator.admin"]
|
|
235
|
-
: [];
|
|
174
|
+
// Default-deny: scopes must be explicit. Empty/missing scopes means no permissions.
|
|
175
|
+
// Note: If the client does not present a device identity, we can't bind scopes to a paired
|
|
176
|
+
// device/token, so we will clear scopes after auth to avoid self-declared permissions.
|
|
177
|
+
let scopes = Array.isArray(connectParams.scopes) ? connectParams.scopes : [];
|
|
236
178
|
connectParams.role = role;
|
|
237
179
|
connectParams.scopes = scopes;
|
|
180
|
+
const isControlUi = connectParams.client.id === GATEWAY_CLIENT_IDS.CONTROL_UI;
|
|
181
|
+
const isWebchat = isWebchatConnect(connectParams);
|
|
182
|
+
if (isControlUi || isWebchat) {
|
|
183
|
+
const originCheck = checkBrowserOrigin({
|
|
184
|
+
requestHost,
|
|
185
|
+
origin: requestOrigin,
|
|
186
|
+
allowedOrigins: configSnapshot.gateway?.controlUi?.allowedOrigins,
|
|
187
|
+
});
|
|
188
|
+
if (!originCheck.ok) {
|
|
189
|
+
const errorMessage = "origin not allowed (open the Control UI from the gateway host or allow it in gateway.controlUi.allowedOrigins)";
|
|
190
|
+
markHandshakeFailure("origin-mismatch", {
|
|
191
|
+
origin: requestOrigin ?? "n/a",
|
|
192
|
+
host: requestHost ?? "n/a",
|
|
193
|
+
reason: originCheck.reason,
|
|
194
|
+
});
|
|
195
|
+
sendHandshakeErrorResponse(ErrorCodes.INVALID_REQUEST, errorMessage);
|
|
196
|
+
close(1008, truncateCloseReason(errorMessage));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
238
200
|
const deviceRaw = connectParams.device;
|
|
239
201
|
let devicePublicKey = null;
|
|
240
202
|
const hasTokenAuth = Boolean(connectParams.auth?.token);
|
|
241
203
|
const hasPasswordAuth = Boolean(connectParams.auth?.password);
|
|
242
204
|
const hasSharedAuth = hasTokenAuth || hasPasswordAuth;
|
|
243
|
-
const isControlUi = connectParams.client.id === GATEWAY_CLIENT_IDS.CONTROL_UI;
|
|
244
205
|
const allowInsecureControlUi = isControlUi && configSnapshot.gateway?.controlUi?.allowInsecureAuth === true;
|
|
245
206
|
const disableControlUiDeviceAuth = isControlUi && configSnapshot.gateway?.controlUi?.dangerouslyDisableDeviceAuth === true;
|
|
246
207
|
const allowControlUiBypass = allowInsecureControlUi || disableControlUiDeviceAuth;
|
|
247
208
|
const device = disableControlUiDeviceAuth ? null : deviceRaw;
|
|
209
|
+
const hasDeviceTokenCandidate = Boolean(connectParams.auth?.token && device);
|
|
210
|
+
let authResult = await authorizeGatewayConnect({
|
|
211
|
+
auth: resolvedAuth,
|
|
212
|
+
connectAuth: connectParams.auth,
|
|
213
|
+
req: upgradeReq,
|
|
214
|
+
trustedProxies,
|
|
215
|
+
rateLimiter: hasDeviceTokenCandidate ? undefined : rateLimiter,
|
|
216
|
+
clientIp,
|
|
217
|
+
rateLimitScope: AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET,
|
|
218
|
+
});
|
|
219
|
+
if (hasDeviceTokenCandidate &&
|
|
220
|
+
authResult.ok &&
|
|
221
|
+
rateLimiter &&
|
|
222
|
+
(authResult.method === "token" || authResult.method === "password")) {
|
|
223
|
+
const sharedRateCheck = rateLimiter.check(clientIp, AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET);
|
|
224
|
+
if (!sharedRateCheck.allowed) {
|
|
225
|
+
authResult = {
|
|
226
|
+
ok: false,
|
|
227
|
+
reason: "rate_limited",
|
|
228
|
+
rateLimited: true,
|
|
229
|
+
retryAfterMs: sharedRateCheck.retryAfterMs,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
rateLimiter.reset(clientIp, AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
let authOk = authResult.ok;
|
|
237
|
+
let authMethod = authResult.method ?? (resolvedAuth.mode === "password" ? "password" : "token");
|
|
238
|
+
const sharedAuthResult = hasSharedAuth
|
|
239
|
+
? await authorizeGatewayConnect({
|
|
240
|
+
auth: { ...resolvedAuth, allowTailscale: false },
|
|
241
|
+
connectAuth: connectParams.auth,
|
|
242
|
+
req: upgradeReq,
|
|
243
|
+
trustedProxies,
|
|
244
|
+
// Shared-auth probe only; rate-limit side effects are handled in
|
|
245
|
+
// the primary auth flow (or deferred for device-token candidates).
|
|
246
|
+
rateLimitScope: AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET,
|
|
247
|
+
})
|
|
248
|
+
: null;
|
|
249
|
+
const sharedAuthOk = sharedAuthResult?.ok === true &&
|
|
250
|
+
(sharedAuthResult.method === "token" || sharedAuthResult.method === "password");
|
|
251
|
+
const rejectUnauthorized = (failedAuth) => {
|
|
252
|
+
markHandshakeFailure("unauthorized", {
|
|
253
|
+
authMode: resolvedAuth.mode,
|
|
254
|
+
authProvided: connectParams.auth?.token
|
|
255
|
+
? "token"
|
|
256
|
+
: connectParams.auth?.password
|
|
257
|
+
? "password"
|
|
258
|
+
: "none",
|
|
259
|
+
authReason: failedAuth.reason,
|
|
260
|
+
allowTailscale: resolvedAuth.allowTailscale,
|
|
261
|
+
});
|
|
262
|
+
logWsControl.warn(`unauthorized conn=${connId} remote=${remoteAddr ?? "?"} client=${clientLabel} ${connectParams.client.mode} v${connectParams.client.version} reason=${failedAuth.reason ?? "unknown"}`);
|
|
263
|
+
const authProvided = connectParams.auth?.token
|
|
264
|
+
? "token"
|
|
265
|
+
: connectParams.auth?.password
|
|
266
|
+
? "password"
|
|
267
|
+
: "none";
|
|
268
|
+
const authMessage = formatGatewayAuthFailureMessage({
|
|
269
|
+
authMode: resolvedAuth.mode,
|
|
270
|
+
authProvided,
|
|
271
|
+
reason: failedAuth.reason,
|
|
272
|
+
client: connectParams.client,
|
|
273
|
+
});
|
|
274
|
+
sendHandshakeErrorResponse(ErrorCodes.INVALID_REQUEST, authMessage);
|
|
275
|
+
close(1008, truncateCloseReason(authMessage));
|
|
276
|
+
};
|
|
248
277
|
if (!device) {
|
|
249
|
-
|
|
278
|
+
if (scopes.length > 0 && !allowControlUiBypass) {
|
|
279
|
+
scopes = [];
|
|
280
|
+
connectParams.scopes = scopes;
|
|
281
|
+
}
|
|
282
|
+
const canSkipDevice = sharedAuthOk;
|
|
250
283
|
if (isControlUi && !allowControlUiBypass) {
|
|
251
284
|
const errorMessage = "control ui requires HTTPS or localhost (secure context)";
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
client: connectParams.client.id,
|
|
255
|
-
clientDisplayName: connectParams.client.displayName,
|
|
256
|
-
mode: connectParams.client.mode,
|
|
257
|
-
version: connectParams.client.version,
|
|
258
|
-
});
|
|
259
|
-
send({
|
|
260
|
-
type: "res",
|
|
261
|
-
id: frame.id,
|
|
262
|
-
ok: false,
|
|
263
|
-
error: errorShape(ErrorCodes.INVALID_REQUEST, errorMessage),
|
|
264
|
-
});
|
|
285
|
+
markHandshakeFailure("control-ui-insecure-auth");
|
|
286
|
+
sendHandshakeErrorResponse(ErrorCodes.INVALID_REQUEST, errorMessage);
|
|
265
287
|
close(1008, errorMessage);
|
|
266
288
|
return;
|
|
267
289
|
}
|
|
268
|
-
// Allow
|
|
290
|
+
// Allow shared-secret authenticated connections (e.g., control-ui) to skip device identity
|
|
269
291
|
if (!canSkipDevice) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
});
|
|
277
|
-
send({
|
|
278
|
-
type: "res",
|
|
279
|
-
id: frame.id,
|
|
280
|
-
ok: false,
|
|
281
|
-
error: errorShape(ErrorCodes.NOT_PAIRED, "device identity required"),
|
|
282
|
-
});
|
|
292
|
+
if (!authOk && hasSharedAuth) {
|
|
293
|
+
rejectUnauthorized(authResult);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
markHandshakeFailure("device-required");
|
|
297
|
+
sendHandshakeErrorResponse(ErrorCodes.NOT_PAIRED, "device identity required");
|
|
283
298
|
close(1008, "device identity required");
|
|
284
299
|
return;
|
|
285
300
|
}
|
|
@@ -359,12 +374,27 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
359
374
|
clientId: connectParams.client.id,
|
|
360
375
|
clientMode: connectParams.client.mode,
|
|
361
376
|
role,
|
|
362
|
-
scopes
|
|
377
|
+
scopes,
|
|
363
378
|
signedAtMs: signedAt,
|
|
364
379
|
token: connectParams.auth?.token ?? null,
|
|
365
380
|
nonce: providedNonce || undefined,
|
|
366
381
|
version: providedNonce ? "v2" : "v1",
|
|
367
382
|
});
|
|
383
|
+
const rejectDeviceSignatureInvalid = () => {
|
|
384
|
+
setHandshakeState("failed");
|
|
385
|
+
setCloseCause("device-auth-invalid", {
|
|
386
|
+
reason: "device-signature",
|
|
387
|
+
client: connectParams.client.id,
|
|
388
|
+
deviceId: device.id,
|
|
389
|
+
});
|
|
390
|
+
send({
|
|
391
|
+
type: "res",
|
|
392
|
+
id: frame.id,
|
|
393
|
+
ok: false,
|
|
394
|
+
error: errorShape(ErrorCodes.INVALID_REQUEST, "device signature invalid"),
|
|
395
|
+
});
|
|
396
|
+
close(1008, "device signature invalid");
|
|
397
|
+
};
|
|
368
398
|
const signatureOk = verifyDeviceSignature(device.publicKey, payload, device.signature);
|
|
369
399
|
const allowLegacy = !nonceRequired && !providedNonce;
|
|
370
400
|
if (!signatureOk && allowLegacy) {
|
|
@@ -373,7 +403,7 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
373
403
|
clientId: connectParams.client.id,
|
|
374
404
|
clientMode: connectParams.client.mode,
|
|
375
405
|
role,
|
|
376
|
-
scopes
|
|
406
|
+
scopes,
|
|
377
407
|
signedAtMs: signedAt,
|
|
378
408
|
token: connectParams.auth?.token ?? null,
|
|
379
409
|
version: "v1",
|
|
@@ -382,36 +412,12 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
382
412
|
// accepted legacy loopback signature
|
|
383
413
|
}
|
|
384
414
|
else {
|
|
385
|
-
|
|
386
|
-
setCloseCause("device-auth-invalid", {
|
|
387
|
-
reason: "device-signature",
|
|
388
|
-
client: connectParams.client.id,
|
|
389
|
-
deviceId: device.id,
|
|
390
|
-
});
|
|
391
|
-
send({
|
|
392
|
-
type: "res",
|
|
393
|
-
id: frame.id,
|
|
394
|
-
ok: false,
|
|
395
|
-
error: errorShape(ErrorCodes.INVALID_REQUEST, "device signature invalid"),
|
|
396
|
-
});
|
|
397
|
-
close(1008, "device signature invalid");
|
|
415
|
+
rejectDeviceSignatureInvalid();
|
|
398
416
|
return;
|
|
399
417
|
}
|
|
400
418
|
}
|
|
401
419
|
else if (!signatureOk) {
|
|
402
|
-
|
|
403
|
-
setCloseCause("device-auth-invalid", {
|
|
404
|
-
reason: "device-signature",
|
|
405
|
-
client: connectParams.client.id,
|
|
406
|
-
deviceId: device.id,
|
|
407
|
-
});
|
|
408
|
-
send({
|
|
409
|
-
type: "res",
|
|
410
|
-
id: frame.id,
|
|
411
|
-
ok: false,
|
|
412
|
-
error: errorShape(ErrorCodes.INVALID_REQUEST, "device signature invalid"),
|
|
413
|
-
});
|
|
414
|
-
close(1008, "device signature invalid");
|
|
420
|
+
rejectDeviceSignatureInvalid();
|
|
415
421
|
return;
|
|
416
422
|
}
|
|
417
423
|
devicePublicKey = normalizeDevicePublicKeyBase64Url(device.publicKey);
|
|
@@ -432,60 +438,41 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
432
438
|
return;
|
|
433
439
|
}
|
|
434
440
|
}
|
|
435
|
-
const authResult = await authorizeGatewayConnect({
|
|
436
|
-
auth: resolvedAuth,
|
|
437
|
-
connectAuth: connectParams.auth,
|
|
438
|
-
req: upgradeReq,
|
|
439
|
-
trustedProxies,
|
|
440
|
-
});
|
|
441
|
-
let authOk = authResult.ok;
|
|
442
|
-
let authMethod = authResult.method ?? (resolvedAuth.mode === "password" ? "password" : "token");
|
|
443
441
|
if (!authOk && connectParams.auth?.token && device) {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
442
|
+
if (rateLimiter) {
|
|
443
|
+
const deviceRateCheck = rateLimiter.check(clientIp, AUTH_RATE_LIMIT_SCOPE_DEVICE_TOKEN);
|
|
444
|
+
if (!deviceRateCheck.allowed) {
|
|
445
|
+
authResult = {
|
|
446
|
+
ok: false,
|
|
447
|
+
reason: "rate_limited",
|
|
448
|
+
rateLimited: true,
|
|
449
|
+
retryAfterMs: deviceRateCheck.retryAfterMs,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (!authResult.rateLimited) {
|
|
454
|
+
const tokenCheck = await verifyDeviceToken({
|
|
455
|
+
deviceId: device.id,
|
|
456
|
+
token: connectParams.auth.token,
|
|
457
|
+
role,
|
|
458
|
+
scopes,
|
|
459
|
+
});
|
|
460
|
+
if (tokenCheck.ok) {
|
|
461
|
+
authOk = true;
|
|
462
|
+
authMethod = "device-token";
|
|
463
|
+
rateLimiter?.reset(clientIp, AUTH_RATE_LIMIT_SCOPE_DEVICE_TOKEN);
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
authResult = { ok: false, reason: "device_token_mismatch" };
|
|
467
|
+
rateLimiter?.recordFailure(clientIp, AUTH_RATE_LIMIT_SCOPE_DEVICE_TOKEN);
|
|
468
|
+
}
|
|
453
469
|
}
|
|
454
470
|
}
|
|
455
471
|
if (!authOk) {
|
|
456
|
-
|
|
457
|
-
logWsControl.warn(`unauthorized conn=${connId} remote=${remoteAddr ?? "?"} client=${clientLabel} ${connectParams.client.mode} v${connectParams.client.version} reason=${authResult.reason ?? "unknown"}`);
|
|
458
|
-
const authProvided = connectParams.auth?.token
|
|
459
|
-
? "token"
|
|
460
|
-
: connectParams.auth?.password
|
|
461
|
-
? "password"
|
|
462
|
-
: "none";
|
|
463
|
-
const authMessage = formatGatewayAuthFailureMessage({
|
|
464
|
-
authMode: resolvedAuth.mode,
|
|
465
|
-
authProvided,
|
|
466
|
-
reason: authResult.reason,
|
|
467
|
-
client: connectParams.client,
|
|
468
|
-
});
|
|
469
|
-
setCloseCause("unauthorized", {
|
|
470
|
-
authMode: resolvedAuth.mode,
|
|
471
|
-
authProvided,
|
|
472
|
-
authReason: authResult.reason,
|
|
473
|
-
allowTailscale: resolvedAuth.allowTailscale,
|
|
474
|
-
client: connectParams.client.id,
|
|
475
|
-
clientDisplayName: connectParams.client.displayName,
|
|
476
|
-
mode: connectParams.client.mode,
|
|
477
|
-
version: connectParams.client.version,
|
|
478
|
-
});
|
|
479
|
-
send({
|
|
480
|
-
type: "res",
|
|
481
|
-
id: frame.id,
|
|
482
|
-
ok: false,
|
|
483
|
-
error: errorShape(ErrorCodes.INVALID_REQUEST, authMessage),
|
|
484
|
-
});
|
|
485
|
-
close(1008, truncateCloseReason(authMessage));
|
|
472
|
+
rejectUnauthorized(authResult);
|
|
486
473
|
return;
|
|
487
474
|
}
|
|
488
|
-
const skipPairing = allowControlUiBypass &&
|
|
475
|
+
const skipPairing = allowControlUiBypass && sharedAuthOk;
|
|
489
476
|
if (device && devicePublicKey && !skipPairing) {
|
|
490
477
|
const requirePairing = async (reason, _paired) => {
|
|
491
478
|
const pairing = await requestDevicePairing({
|
|
@@ -540,35 +527,40 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
540
527
|
const isPaired = paired?.publicKey === devicePublicKey;
|
|
541
528
|
if (!isPaired) {
|
|
542
529
|
const ok = await requirePairing("not-paired");
|
|
543
|
-
if (!ok)
|
|
530
|
+
if (!ok) {
|
|
544
531
|
return;
|
|
532
|
+
}
|
|
545
533
|
}
|
|
546
534
|
else {
|
|
547
535
|
const allowedRoles = new Set(Array.isArray(paired.roles) ? paired.roles : paired.role ? [paired.role] : []);
|
|
548
536
|
if (allowedRoles.size === 0) {
|
|
549
537
|
const ok = await requirePairing("role-upgrade", paired);
|
|
550
|
-
if (!ok)
|
|
538
|
+
if (!ok) {
|
|
551
539
|
return;
|
|
540
|
+
}
|
|
552
541
|
}
|
|
553
542
|
else if (!allowedRoles.has(role)) {
|
|
554
543
|
const ok = await requirePairing("role-upgrade", paired);
|
|
555
|
-
if (!ok)
|
|
544
|
+
if (!ok) {
|
|
556
545
|
return;
|
|
546
|
+
}
|
|
557
547
|
}
|
|
558
548
|
const pairedScopes = Array.isArray(paired.scopes) ? paired.scopes : [];
|
|
559
549
|
if (scopes.length > 0) {
|
|
560
550
|
if (pairedScopes.length === 0) {
|
|
561
551
|
const ok = await requirePairing("scope-upgrade", paired);
|
|
562
|
-
if (!ok)
|
|
552
|
+
if (!ok) {
|
|
563
553
|
return;
|
|
554
|
+
}
|
|
564
555
|
}
|
|
565
556
|
else {
|
|
566
557
|
const allowedScopes = new Set(pairedScopes);
|
|
567
558
|
const missingScope = scopes.find((scope) => !allowedScopes.has(scope));
|
|
568
559
|
if (missingScope) {
|
|
569
560
|
const ok = await requirePairing("scope-upgrade", paired);
|
|
570
|
-
if (!ok)
|
|
561
|
+
if (!ok) {
|
|
571
562
|
return;
|
|
563
|
+
}
|
|
572
564
|
}
|
|
573
565
|
}
|
|
574
566
|
}
|
|
@@ -642,10 +634,7 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
642
634
|
type: "hello-ok",
|
|
643
635
|
protocol: PROTOCOL_VERSION,
|
|
644
636
|
server: {
|
|
645
|
-
version: process.env
|
|
646
|
-
process.env.CLAWDBOT_VERSION ??
|
|
647
|
-
process.env.npm_package_version ??
|
|
648
|
-
"dev",
|
|
637
|
+
version: resolveRuntimeServiceVersion(process.env, "dev"),
|
|
649
638
|
commit: process.env.GIT_COMMIT,
|
|
650
639
|
host: os.hostname(),
|
|
651
640
|
connId,
|
|
@@ -673,6 +662,7 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
673
662
|
connect: connectParams,
|
|
674
663
|
connId,
|
|
675
664
|
presenceKey,
|
|
665
|
+
clientIp: reportedClientIp,
|
|
676
666
|
};
|
|
677
667
|
setClient(nextClient);
|
|
678
668
|
setHandshakeState("connected");
|
|
@@ -684,8 +674,9 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
684
674
|
const instanceIdRaw = connectParams.client.instanceId;
|
|
685
675
|
const instanceId = typeof instanceIdRaw === "string" ? instanceIdRaw.trim() : "";
|
|
686
676
|
const nodeIdsForPairing = new Set([nodeSession.nodeId]);
|
|
687
|
-
if (instanceId)
|
|
677
|
+
if (instanceId) {
|
|
688
678
|
nodeIdsForPairing.add(instanceId);
|
|
679
|
+
}
|
|
689
680
|
for (const nodeId of nodeIdsForPairing) {
|
|
690
681
|
void updatePairedNodeMetadata(nodeId, {
|
|
691
682
|
lastConnectedAtMs: nodeSession.connectedAtMs,
|