@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,61 +1,222 @@
|
|
|
1
1
|
let handler = null;
|
|
2
|
-
let
|
|
2
|
+
let handlerGeneration = 0;
|
|
3
|
+
const pendingWakes = new Map();
|
|
3
4
|
let scheduled = false;
|
|
4
5
|
let running = false;
|
|
5
6
|
let timer = null;
|
|
7
|
+
let timerDueAt = null;
|
|
8
|
+
let timerKind = null;
|
|
6
9
|
const DEFAULT_COALESCE_MS = 250;
|
|
7
10
|
const DEFAULT_RETRY_MS = 1_000;
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
const HOOK_REASON_PREFIX = "hook:";
|
|
12
|
+
const REASON_PRIORITY = {
|
|
13
|
+
RETRY: 0,
|
|
14
|
+
INTERVAL: 1,
|
|
15
|
+
DEFAULT: 2,
|
|
16
|
+
ACTION: 3,
|
|
17
|
+
};
|
|
18
|
+
function isActionWakeReason(reason) {
|
|
19
|
+
return reason === "manual" || reason === "exec-event" || reason.startsWith(HOOK_REASON_PREFIX);
|
|
20
|
+
}
|
|
21
|
+
function resolveReasonPriority(reason) {
|
|
22
|
+
if (reason === "retry") {
|
|
23
|
+
return REASON_PRIORITY.RETRY;
|
|
24
|
+
}
|
|
25
|
+
if (reason === "interval") {
|
|
26
|
+
return REASON_PRIORITY.INTERVAL;
|
|
27
|
+
}
|
|
28
|
+
if (isActionWakeReason(reason)) {
|
|
29
|
+
return REASON_PRIORITY.ACTION;
|
|
30
|
+
}
|
|
31
|
+
return REASON_PRIORITY.DEFAULT;
|
|
32
|
+
}
|
|
33
|
+
function normalizeWakeReason(reason) {
|
|
34
|
+
if (typeof reason !== "string") {
|
|
35
|
+
return "requested";
|
|
36
|
+
}
|
|
37
|
+
const trimmed = reason.trim();
|
|
38
|
+
return trimmed.length > 0 ? trimmed : "requested";
|
|
39
|
+
}
|
|
40
|
+
function normalizeWakeTarget(value) {
|
|
41
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
42
|
+
return trimmed || undefined;
|
|
43
|
+
}
|
|
44
|
+
function getWakeTargetKey(params) {
|
|
45
|
+
const agentId = normalizeWakeTarget(params.agentId);
|
|
46
|
+
const sessionKey = normalizeWakeTarget(params.sessionKey);
|
|
47
|
+
return `${agentId ?? ""}::${sessionKey ?? ""}`;
|
|
48
|
+
}
|
|
49
|
+
function queuePendingWakeReason(params) {
|
|
50
|
+
const requestedAt = params?.requestedAt ?? Date.now();
|
|
51
|
+
const normalizedReason = normalizeWakeReason(params?.reason);
|
|
52
|
+
const normalizedAgentId = normalizeWakeTarget(params?.agentId);
|
|
53
|
+
const normalizedSessionKey = normalizeWakeTarget(params?.sessionKey);
|
|
54
|
+
const wakeTargetKey = getWakeTargetKey({
|
|
55
|
+
agentId: normalizedAgentId,
|
|
56
|
+
sessionKey: normalizedSessionKey,
|
|
57
|
+
});
|
|
58
|
+
const next = {
|
|
59
|
+
reason: normalizedReason,
|
|
60
|
+
priority: resolveReasonPriority(normalizedReason),
|
|
61
|
+
requestedAt,
|
|
62
|
+
agentId: normalizedAgentId,
|
|
63
|
+
sessionKey: normalizedSessionKey,
|
|
64
|
+
};
|
|
65
|
+
const previous = pendingWakes.get(wakeTargetKey);
|
|
66
|
+
if (!previous) {
|
|
67
|
+
pendingWakes.set(wakeTargetKey, next);
|
|
10
68
|
return;
|
|
69
|
+
}
|
|
70
|
+
if (next.priority > previous.priority) {
|
|
71
|
+
pendingWakes.set(wakeTargetKey, next);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (next.priority === previous.priority && next.requestedAt >= previous.requestedAt) {
|
|
75
|
+
pendingWakes.set(wakeTargetKey, next);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function schedule(coalesceMs, kind = "normal") {
|
|
79
|
+
const delay = Number.isFinite(coalesceMs) ? Math.max(0, coalesceMs) : DEFAULT_COALESCE_MS;
|
|
80
|
+
const dueAt = Date.now() + delay;
|
|
81
|
+
if (timer) {
|
|
82
|
+
// Keep retry cooldown as a hard minimum delay. This prevents the
|
|
83
|
+
// finally-path reschedule (often delay=0) from collapsing backoff.
|
|
84
|
+
if (timerKind === "retry") {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// If existing timer fires sooner or at the same time, keep it.
|
|
88
|
+
if (typeof timerDueAt === "number" && timerDueAt <= dueAt) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// New request needs to fire sooner — preempt the existing timer.
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
timer = null;
|
|
94
|
+
timerDueAt = null;
|
|
95
|
+
timerKind = null;
|
|
96
|
+
}
|
|
97
|
+
timerDueAt = dueAt;
|
|
98
|
+
timerKind = kind;
|
|
11
99
|
timer = setTimeout(async () => {
|
|
12
100
|
timer = null;
|
|
101
|
+
timerDueAt = null;
|
|
102
|
+
timerKind = null;
|
|
13
103
|
scheduled = false;
|
|
14
104
|
const active = handler;
|
|
15
|
-
if (!active)
|
|
105
|
+
if (!active) {
|
|
16
106
|
return;
|
|
107
|
+
}
|
|
17
108
|
if (running) {
|
|
18
109
|
scheduled = true;
|
|
19
|
-
schedule(
|
|
110
|
+
schedule(delay, kind);
|
|
20
111
|
return;
|
|
21
112
|
}
|
|
22
|
-
const
|
|
23
|
-
|
|
113
|
+
const pendingBatch = Array.from(pendingWakes.values());
|
|
114
|
+
pendingWakes.clear();
|
|
24
115
|
running = true;
|
|
25
116
|
try {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
117
|
+
for (const pendingWake of pendingBatch) {
|
|
118
|
+
const wakeOpts = {
|
|
119
|
+
reason: pendingWake.reason ?? undefined,
|
|
120
|
+
...(pendingWake.agentId ? { agentId: pendingWake.agentId } : {}),
|
|
121
|
+
...(pendingWake.sessionKey ? { sessionKey: pendingWake.sessionKey } : {}),
|
|
122
|
+
};
|
|
123
|
+
const res = await active(wakeOpts);
|
|
124
|
+
if (res.status === "skipped" && res.reason === "requests-in-flight") {
|
|
125
|
+
// The main lane is busy; retry this wake target soon.
|
|
126
|
+
queuePendingWakeReason({
|
|
127
|
+
reason: pendingWake.reason ?? "retry",
|
|
128
|
+
agentId: pendingWake.agentId,
|
|
129
|
+
sessionKey: pendingWake.sessionKey,
|
|
130
|
+
});
|
|
131
|
+
schedule(DEFAULT_RETRY_MS, "retry");
|
|
132
|
+
}
|
|
31
133
|
}
|
|
32
134
|
}
|
|
33
135
|
catch {
|
|
34
136
|
// Error is already logged by the heartbeat runner; schedule a retry.
|
|
35
|
-
|
|
36
|
-
|
|
137
|
+
for (const pendingWake of pendingBatch) {
|
|
138
|
+
queuePendingWakeReason({
|
|
139
|
+
reason: pendingWake.reason ?? "retry",
|
|
140
|
+
agentId: pendingWake.agentId,
|
|
141
|
+
sessionKey: pendingWake.sessionKey,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
schedule(DEFAULT_RETRY_MS, "retry");
|
|
37
145
|
}
|
|
38
146
|
finally {
|
|
39
147
|
running = false;
|
|
40
|
-
if (
|
|
41
|
-
schedule(
|
|
148
|
+
if (pendingWakes.size > 0 || scheduled) {
|
|
149
|
+
schedule(delay, "normal");
|
|
150
|
+
}
|
|
42
151
|
}
|
|
43
|
-
},
|
|
152
|
+
}, delay);
|
|
44
153
|
timer.unref?.();
|
|
45
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Register (or clear) the heartbeat wake handler.
|
|
157
|
+
* Returns a disposer function that clears this specific registration.
|
|
158
|
+
* Stale disposers (from previous registrations) are no-ops, preventing
|
|
159
|
+
* a race where an old runner's cleanup clears a newer runner's handler.
|
|
160
|
+
*/
|
|
46
161
|
export function setHeartbeatWakeHandler(next) {
|
|
162
|
+
handlerGeneration += 1;
|
|
163
|
+
const generation = handlerGeneration;
|
|
47
164
|
handler = next;
|
|
48
|
-
if (
|
|
49
|
-
|
|
165
|
+
if (next) {
|
|
166
|
+
// New lifecycle starting (e.g. after SIGUSR1 in-process restart).
|
|
167
|
+
// Clear any timer metadata from the previous lifecycle so stale retry
|
|
168
|
+
// cooldowns do not delay a fresh handler.
|
|
169
|
+
if (timer) {
|
|
170
|
+
clearTimeout(timer);
|
|
171
|
+
}
|
|
172
|
+
timer = null;
|
|
173
|
+
timerDueAt = null;
|
|
174
|
+
timerKind = null;
|
|
175
|
+
// Reset module-level execution state that may be stale from interrupted
|
|
176
|
+
// runs in the previous lifecycle. Without this, `running === true` from
|
|
177
|
+
// an interrupted heartbeat blocks all future schedule() attempts, and
|
|
178
|
+
// `scheduled === true` can cause spurious immediate re-runs.
|
|
179
|
+
running = false;
|
|
180
|
+
scheduled = false;
|
|
181
|
+
}
|
|
182
|
+
if (handler && pendingWakes.size > 0) {
|
|
183
|
+
schedule(DEFAULT_COALESCE_MS, "normal");
|
|
50
184
|
}
|
|
185
|
+
return () => {
|
|
186
|
+
if (handlerGeneration !== generation) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (handler !== next) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
handlerGeneration += 1;
|
|
193
|
+
handler = null;
|
|
194
|
+
};
|
|
51
195
|
}
|
|
52
196
|
export function requestHeartbeatNow(opts) {
|
|
53
|
-
|
|
54
|
-
|
|
197
|
+
queuePendingWakeReason({
|
|
198
|
+
reason: opts?.reason,
|
|
199
|
+
agentId: opts?.agentId,
|
|
200
|
+
sessionKey: opts?.sessionKey,
|
|
201
|
+
});
|
|
202
|
+
schedule(opts?.coalesceMs ?? DEFAULT_COALESCE_MS, "normal");
|
|
55
203
|
}
|
|
56
204
|
export function hasHeartbeatWakeHandler() {
|
|
57
205
|
return handler !== null;
|
|
58
206
|
}
|
|
59
207
|
export function hasPendingHeartbeatWake() {
|
|
60
|
-
return
|
|
208
|
+
return pendingWakes.size > 0 || Boolean(timer) || scheduled;
|
|
209
|
+
}
|
|
210
|
+
export function resetHeartbeatWakeStateForTests() {
|
|
211
|
+
if (timer) {
|
|
212
|
+
clearTimeout(timer);
|
|
213
|
+
}
|
|
214
|
+
timer = null;
|
|
215
|
+
timerDueAt = null;
|
|
216
|
+
timerKind = null;
|
|
217
|
+
pendingWakes.clear();
|
|
218
|
+
scheduled = false;
|
|
219
|
+
running = false;
|
|
220
|
+
handlerGeneration += 1;
|
|
221
|
+
handler = null;
|
|
61
222
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { runCommandWithTimeout } from "../process/exec.js";
|
|
5
|
+
import { resolveUserPath } from "../utils.js";
|
|
6
|
+
import { fileExists, resolveArchiveKind } from "./archive.js";
|
|
7
|
+
export async function withTempDir(prefix, fn) {
|
|
8
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
9
|
+
try {
|
|
10
|
+
return await fn(tmpDir);
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => undefined);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function resolveArchiveSourcePath(archivePath) {
|
|
17
|
+
const resolved = resolveUserPath(archivePath);
|
|
18
|
+
if (!(await fileExists(resolved))) {
|
|
19
|
+
return { ok: false, error: `archive not found: ${resolved}` };
|
|
20
|
+
}
|
|
21
|
+
if (!resolveArchiveKind(resolved)) {
|
|
22
|
+
return { ok: false, error: `unsupported archive: ${resolved}` };
|
|
23
|
+
}
|
|
24
|
+
return { ok: true, path: resolved };
|
|
25
|
+
}
|
|
26
|
+
export async function packNpmSpecToArchive(params) {
|
|
27
|
+
const res = await runCommandWithTimeout(["npm", "pack", params.spec, "--ignore-scripts"], {
|
|
28
|
+
timeoutMs: Math.max(params.timeoutMs, 300_000),
|
|
29
|
+
cwd: params.cwd,
|
|
30
|
+
env: {
|
|
31
|
+
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0",
|
|
32
|
+
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
if (res.code !== 0) {
|
|
36
|
+
return { ok: false, error: `npm pack failed: ${res.stderr.trim() || res.stdout.trim()}` };
|
|
37
|
+
}
|
|
38
|
+
const packed = (res.stdout || "")
|
|
39
|
+
.split("\n")
|
|
40
|
+
.map((line) => line.trim())
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.pop();
|
|
43
|
+
if (!packed) {
|
|
44
|
+
return { ok: false, error: "npm pack produced no archive" };
|
|
45
|
+
}
|
|
46
|
+
return { ok: true, archivePath: path.join(params.cwd, packed) };
|
|
47
|
+
}
|
package/dist/infra/net/ssrf.js
CHANGED
|
@@ -1,27 +1,44 @@
|
|
|
1
1
|
import { lookup as dnsLookupCb } from "node:dns";
|
|
2
2
|
import { lookup as dnsLookup } from "node:dns/promises";
|
|
3
3
|
import { Agent } from "undici";
|
|
4
|
+
import { normalizeHostname } from "./hostname.js";
|
|
4
5
|
export class SsrFBlockedError extends Error {
|
|
5
6
|
constructor(message) {
|
|
6
7
|
super(message);
|
|
7
8
|
this.name = "SsrFBlockedError";
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
|
-
const PRIVATE_IPV6_PREFIXES = ["fe80:", "fec0:", "fc", "fd"];
|
|
11
11
|
const BLOCKED_HOSTNAMES = new Set(["localhost", "metadata.google.internal"]);
|
|
12
|
-
function normalizeHostname(hostname) {
|
|
13
|
-
const normalized = hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
14
|
-
if (normalized.startsWith("[") && normalized.endsWith("]")) {
|
|
15
|
-
return normalized.slice(1, -1);
|
|
16
|
-
}
|
|
17
|
-
return normalized;
|
|
18
|
-
}
|
|
19
12
|
function normalizeHostnameSet(values) {
|
|
20
13
|
if (!values || values.length === 0) {
|
|
21
14
|
return new Set();
|
|
22
15
|
}
|
|
23
16
|
return new Set(values.map((value) => normalizeHostname(value)).filter(Boolean));
|
|
24
17
|
}
|
|
18
|
+
function normalizeHostnameAllowlist(values) {
|
|
19
|
+
if (!values || values.length === 0) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
return Array.from(new Set(values
|
|
23
|
+
.map((value) => normalizeHostname(value))
|
|
24
|
+
.filter((value) => value !== "*" && value !== "*." && value.length > 0)));
|
|
25
|
+
}
|
|
26
|
+
function isHostnameAllowedByPattern(hostname, pattern) {
|
|
27
|
+
if (pattern.startsWith("*.")) {
|
|
28
|
+
const suffix = pattern.slice(2);
|
|
29
|
+
if (!suffix || hostname === suffix) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return hostname.endsWith(`.${suffix}`);
|
|
33
|
+
}
|
|
34
|
+
return hostname === pattern;
|
|
35
|
+
}
|
|
36
|
+
function matchesHostnameAllowlist(hostname, allowlist) {
|
|
37
|
+
if (allowlist.length === 0) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return allowlist.some((pattern) => isHostnameAllowedByPattern(hostname, pattern));
|
|
41
|
+
}
|
|
25
42
|
function parseIpv4(address) {
|
|
26
43
|
const parts = address.split(".");
|
|
27
44
|
if (parts.length !== 4) {
|
|
@@ -33,33 +50,114 @@ function parseIpv4(address) {
|
|
|
33
50
|
}
|
|
34
51
|
return numbers;
|
|
35
52
|
}
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
53
|
+
function stripIpv6ZoneId(address) {
|
|
54
|
+
const index = address.indexOf("%");
|
|
55
|
+
return index >= 0 ? address.slice(0, index) : address;
|
|
56
|
+
}
|
|
57
|
+
function parseIpv6Hextets(address) {
|
|
58
|
+
let input = stripIpv6ZoneId(address.trim().toLowerCase());
|
|
59
|
+
if (!input) {
|
|
60
|
+
return null;
|
|
39
61
|
}
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
62
|
+
// Handle IPv4-embedded IPv6 like ::ffff:127.0.0.1 by converting the tail to 2 hextets.
|
|
63
|
+
if (input.includes(".")) {
|
|
64
|
+
const lastColon = input.lastIndexOf(":");
|
|
65
|
+
if (lastColon < 0) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const ipv4 = parseIpv4(input.slice(lastColon + 1));
|
|
69
|
+
if (!ipv4) {
|
|
44
70
|
return null;
|
|
45
71
|
}
|
|
46
|
-
|
|
72
|
+
const high = (ipv4[0] << 8) + ipv4[1];
|
|
73
|
+
const low = (ipv4[2] << 8) + ipv4[3];
|
|
74
|
+
input = `${input.slice(0, lastColon)}:${high.toString(16)}:${low.toString(16)}`;
|
|
75
|
+
}
|
|
76
|
+
const doubleColonParts = input.split("::");
|
|
77
|
+
if (doubleColonParts.length > 2) {
|
|
78
|
+
return null;
|
|
47
79
|
}
|
|
48
|
-
|
|
80
|
+
const headParts = doubleColonParts[0]?.length > 0 ? doubleColonParts[0].split(":").filter(Boolean) : [];
|
|
81
|
+
const tailParts = doubleColonParts.length === 2 && doubleColonParts[1]?.length > 0
|
|
82
|
+
? doubleColonParts[1].split(":").filter(Boolean)
|
|
83
|
+
: [];
|
|
84
|
+
const missingParts = 8 - headParts.length - tailParts.length;
|
|
85
|
+
if (missingParts < 0) {
|
|
49
86
|
return null;
|
|
50
87
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
high < 0 ||
|
|
56
|
-
low < 0 ||
|
|
57
|
-
high > 0xffff ||
|
|
58
|
-
low > 0xffff) {
|
|
88
|
+
const fullParts = doubleColonParts.length === 1
|
|
89
|
+
? input.split(":")
|
|
90
|
+
: [...headParts, ...Array.from({ length: missingParts }, () => "0"), ...tailParts];
|
|
91
|
+
if (fullParts.length !== 8) {
|
|
59
92
|
return null;
|
|
60
93
|
}
|
|
61
|
-
const
|
|
62
|
-
|
|
94
|
+
const hextets = [];
|
|
95
|
+
for (const part of fullParts) {
|
|
96
|
+
if (!part) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const value = Number.parseInt(part, 16);
|
|
100
|
+
if (Number.isNaN(value) || value < 0 || value > 0xffff) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
hextets.push(value);
|
|
104
|
+
}
|
|
105
|
+
return hextets;
|
|
106
|
+
}
|
|
107
|
+
function decodeIpv4FromHextets(high, low) {
|
|
108
|
+
return [(high >>> 8) & 0xff, high & 0xff, (low >>> 8) & 0xff, low & 0xff];
|
|
109
|
+
}
|
|
110
|
+
const EMBEDDED_IPV4_RULES = [
|
|
111
|
+
{
|
|
112
|
+
// IPv4-mapped: ::ffff:a.b.c.d and IPv4-compatible ::a.b.c.d.
|
|
113
|
+
matches: (hextets) => hextets[0] === 0 &&
|
|
114
|
+
hextets[1] === 0 &&
|
|
115
|
+
hextets[2] === 0 &&
|
|
116
|
+
hextets[3] === 0 &&
|
|
117
|
+
hextets[4] === 0 &&
|
|
118
|
+
(hextets[5] === 0xffff || hextets[5] === 0),
|
|
119
|
+
extract: (hextets) => [hextets[6], hextets[7]],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
// NAT64 well-known prefix: 64:ff9b::/96.
|
|
123
|
+
matches: (hextets) => hextets[0] === 0x0064 &&
|
|
124
|
+
hextets[1] === 0xff9b &&
|
|
125
|
+
hextets[2] === 0 &&
|
|
126
|
+
hextets[3] === 0 &&
|
|
127
|
+
hextets[4] === 0 &&
|
|
128
|
+
hextets[5] === 0,
|
|
129
|
+
extract: (hextets) => [hextets[6], hextets[7]],
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
// NAT64 local-use prefix: 64:ff9b:1::/48.
|
|
133
|
+
matches: (hextets) => hextets[0] === 0x0064 &&
|
|
134
|
+
hextets[1] === 0xff9b &&
|
|
135
|
+
hextets[2] === 0x0001 &&
|
|
136
|
+
hextets[3] === 0 &&
|
|
137
|
+
hextets[4] === 0 &&
|
|
138
|
+
hextets[5] === 0,
|
|
139
|
+
extract: (hextets) => [hextets[6], hextets[7]],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
// 6to4 prefix: 2002::/16 where hextets[1..2] carry IPv4.
|
|
143
|
+
matches: (hextets) => hextets[0] === 0x2002,
|
|
144
|
+
extract: (hextets) => [hextets[1], hextets[2]],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
// Teredo prefix: 2001:0000::/32 with client IPv4 obfuscated via XOR 0xffff.
|
|
148
|
+
matches: (hextets) => hextets[0] === 0x2001 && hextets[1] === 0x0000,
|
|
149
|
+
extract: (hextets) => [hextets[6] ^ 0xffff, hextets[7] ^ 0xffff],
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
function extractIpv4FromEmbeddedIpv6(hextets) {
|
|
153
|
+
for (const rule of EMBEDDED_IPV4_RULES) {
|
|
154
|
+
if (!rule.matches(hextets)) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const [high, low] = rule.extract(hextets);
|
|
158
|
+
return decodeIpv4FromHextets(high, low);
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
63
161
|
}
|
|
64
162
|
function isPrivateIpv4(parts) {
|
|
65
163
|
const [octet1, octet2] = parts;
|
|
@@ -94,18 +192,50 @@ export function isPrivateIpAddress(address) {
|
|
|
94
192
|
if (!normalized) {
|
|
95
193
|
return false;
|
|
96
194
|
}
|
|
97
|
-
if (normalized.startsWith("::ffff:")) {
|
|
98
|
-
const mapped = normalized.slice("::ffff:".length);
|
|
99
|
-
const ipv4 = parseIpv4FromMappedIpv6(mapped);
|
|
100
|
-
if (ipv4) {
|
|
101
|
-
return isPrivateIpv4(ipv4);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
195
|
if (normalized.includes(":")) {
|
|
105
|
-
|
|
196
|
+
const hextets = parseIpv6Hextets(normalized);
|
|
197
|
+
if (!hextets) {
|
|
198
|
+
// Security-critical parse failures should fail closed.
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
const isUnspecified = hextets[0] === 0 &&
|
|
202
|
+
hextets[1] === 0 &&
|
|
203
|
+
hextets[2] === 0 &&
|
|
204
|
+
hextets[3] === 0 &&
|
|
205
|
+
hextets[4] === 0 &&
|
|
206
|
+
hextets[5] === 0 &&
|
|
207
|
+
hextets[6] === 0 &&
|
|
208
|
+
hextets[7] === 0;
|
|
209
|
+
const isLoopback = hextets[0] === 0 &&
|
|
210
|
+
hextets[1] === 0 &&
|
|
211
|
+
hextets[2] === 0 &&
|
|
212
|
+
hextets[3] === 0 &&
|
|
213
|
+
hextets[4] === 0 &&
|
|
214
|
+
hextets[5] === 0 &&
|
|
215
|
+
hextets[6] === 0 &&
|
|
216
|
+
hextets[7] === 1;
|
|
217
|
+
if (isUnspecified || isLoopback) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
const embeddedIpv4 = extractIpv4FromEmbeddedIpv6(hextets);
|
|
221
|
+
if (embeddedIpv4) {
|
|
222
|
+
return isPrivateIpv4(embeddedIpv4);
|
|
223
|
+
}
|
|
224
|
+
// IPv6 private/internal ranges
|
|
225
|
+
// - link-local: fe80::/10
|
|
226
|
+
// - site-local (deprecated, but internal): fec0::/10
|
|
227
|
+
// - unique local: fc00::/7
|
|
228
|
+
const first = hextets[0];
|
|
229
|
+
if ((first & 0xffc0) === 0xfe80) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if ((first & 0xffc0) === 0xfec0) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
if ((first & 0xfe00) === 0xfc00) {
|
|
106
236
|
return true;
|
|
107
237
|
}
|
|
108
|
-
return
|
|
238
|
+
return false;
|
|
109
239
|
}
|
|
110
240
|
const ipv4 = parseIpv4(normalized);
|
|
111
241
|
if (!ipv4) {
|
|
@@ -171,7 +301,11 @@ export async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
|
|
|
171
301
|
}
|
|
172
302
|
const allowPrivateNetwork = Boolean(params.policy?.allowPrivateNetwork);
|
|
173
303
|
const allowedHostnames = normalizeHostnameSet(params.policy?.allowedHostnames);
|
|
304
|
+
const hostnameAllowlist = normalizeHostnameAllowlist(params.policy?.hostnameAllowlist);
|
|
174
305
|
const isExplicitAllowed = allowedHostnames.has(normalized);
|
|
306
|
+
if (!matchesHostnameAllowlist(normalized, hostnameAllowlist)) {
|
|
307
|
+
throw new SsrFBlockedError(`Blocked hostname (not in allowlist): ${hostname}`);
|
|
308
|
+
}
|
|
175
309
|
if (!allowPrivateNetwork && !isExplicitAllowed) {
|
|
176
310
|
if (isBlockedHostname(normalized)) {
|
|
177
311
|
throw new SsrFBlockedError(`Blocked hostname: ${hostname}`);
|