@openclaw/gateway-client 2026.8.1-beta.1 → 2026.8.1-beta.3

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/index.mjs CHANGED
@@ -1,1003 +1,344 @@
1
- import { C as buildDeviceAuthPayload, S as shouldRetryGatewayWithDeviceToken, T as normalizeDeviceMetadataForAuth, _ as GatewayProtocolRequestError, a as createSessionProjection, b as resolveGatewayConnectScopes, c as normalizeSessionProjectionRunId, d as readSessionMessageSequence, f as reconcileSessionProjectionSnapshot, g as GatewayProtocolClient, h as shouldPauseGatewayReconnect, i as resetGatewaySessionMessageSubscriptionCoordinator, l as projectLiveSessionMessage, m as reduceSessionProjectionRunEvent, n as getGatewaySessionMessageSubscriptionCoordinator, o as hasSessionProjectionAcceptedFinal, p as reduceSessionProjection, r as releaseGatewaySessionMessageSubscription, s as isLocallyOptimisticSessionMessage, t as GatewaySessionMessageSubscriptionCoordinator, u as readSessionMessageIdentity, v as GatewayBrowserDeviceAuthLifecycle, w as buildDeviceAuthPayloadV3, x as selectGatewayConnectAuth, y as buildGatewayConnectAuth } from "./session-subscriptions-D4Xv60cN.mjs";
1
+ import { C as resolveGatewayConnectScopes, D as buildDeviceAuthPayloadV3, E as buildDeviceAuthPayload, O as normalizeDeviceMetadataForAuth, S as buildGatewayConnectAuth, T as shouldRetryGatewayWithDeviceToken, _ as GatewayProtocolRequestError, a as createSessionProjection, b as gatewayOriginScope, c as projectLiveSessionMessage, d as reduceSessionProjectionRunEvent, f as normalizeSessionProjectionRunId, g as GatewayProtocolClient, h as shouldPauseGatewayReconnect, i as resetGatewaySessionMessageSubscriptionCoordinator, l as reconcileSessionProjectionSnapshot, m as readSessionMessageSequence, n as getGatewaySessionMessageSubscriptionCoordinator, o as hasSessionProjectionAcceptedFinal, p as readSessionMessageIdentity, r as releaseGatewaySessionMessageSubscription, s as isLocallyOptimisticSessionMessage, t as GatewaySessionMessageSubscriptionCoordinator, u as reduceSessionProjection, v as GatewayProtocolRequestTimeoutError, w as selectGatewayConnectAuth, x as GatewayBrowserDeviceAuthLifecycle, y as gatewayCredentialScope } from "./session-subscriptions-OJnDJHPL.mjs";
2
2
  import { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, MAX_CONNECT_CHALLENGE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_DELAY_MS, MIN_CONNECT_CHALLENGE_TIMEOUT_MS, addSafeTimeoutDelayGraceMs, clampConnectChallengeTimeoutMs, clearGatewayConnectTimeout, getConnectChallengeTimeoutMsFromEnv, resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs, resolvePreauthHandshakeTimeoutMs, resolveSafeTimeoutDelayMs, startGatewayConnectTimeout } from "./timeouts.mjs";
3
+ import { rawDataToString } from "./websocket-data.mjs";
3
4
  import { n as startGatewayClientWithReadinessWait, r as waitForEventLoopReady, t as startGatewayClientWhenEventLoopReady } from "./readiness-CrWxd4TI.mjs";
5
+ import { GatewayScopeUpgrade } from "./scope-upgrade.mjs";
4
6
  import { ConnectErrorDetailCodes, formatConnectErrorMessage, readConnectErrorDetailCode } from "@openclaw/gateway-protocol/connect-error-details";
5
7
  import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "@openclaw/gateway-protocol/client-info";
6
8
  import { resolveGatewayStartupRetryAfterMs } from "@openclaw/gateway-protocol/startup-unavailable";
7
- import { MIN_CLIENT_PROTOCOL_VERSION, PROTOCOL_VERSION } from "@openclaw/gateway-protocol/version";
9
+ import { MIN_CLIENT_PROTOCOL_VERSION, MIN_NODE_PROTOCOL_VERSION, MIN_PROBE_PROTOCOL_VERSION, PROTOCOL_VERSION } from "@openclaw/gateway-protocol/version";
8
10
  import { randomUUID } from "node:crypto";
9
11
  import { WebSocket } from "ws";
10
- import { Buffer as Buffer$1 } from "node:buffer";
11
- //#region \0rolldown/runtime.js
12
- var __create = Object.create;
13
- var __defProp = Object.defineProperty;
14
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
15
- var __getOwnPropNames = Object.getOwnPropertyNames;
16
- var __getProtoOf = Object.getPrototypeOf;
17
- var __hasOwnProp = Object.prototype.hasOwnProperty;
18
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
19
- var __copyProps = (to, from, except, desc) => {
20
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
21
- key = keys[i];
22
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
23
- get: ((k) => from[k]).bind(null, key),
24
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
25
- });
26
- }
27
- return to;
28
- };
29
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
30
- value: mod,
31
- enumerable: true
32
- }) : target, mod));
12
+ import ipaddr from "ipaddr.js";
13
+ //#region packages/normalization-core/src/string-coerce.ts
14
+ /** Trims string input and returns null for non-strings or empty strings. */
15
+ function normalizeNullableString(value) {
16
+ if (typeof value !== "string") return null;
17
+ const trimmed = value.trim();
18
+ return trimmed ? trimmed : null;
19
+ }
20
+ /** Trims string input and returns undefined for non-strings or empty strings. */
21
+ function normalizeOptionalString(value) {
22
+ return normalizeNullableString(value) ?? void 0;
23
+ }
24
+ /** Lowercases a normalized optional string. */
25
+ function normalizeOptionalLowercaseString(value) {
26
+ return normalizeOptionalString(value)?.toLowerCase();
27
+ }
28
+ /** Lowercases a normalized string or returns an empty string when absent. */
29
+ function normalizeLowercaseStringOrEmpty(value) {
30
+ return normalizeOptionalLowercaseString(value) ?? "";
31
+ }
33
32
  //#endregion
34
- //#region packages/net-policy/src/ip.ts
35
- var import_ipaddr = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
36
- (function(root) {
37
- "use strict";
38
- const ipv4Part = "(0?\\d+|0x[a-f0-9]+)";
39
- const ipv4Regexes = {
40
- fourOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}$`, "i"),
41
- threeOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}$`, "i"),
42
- twoOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}$`, "i"),
43
- longValue: new RegExp(`^${ipv4Part}$`, "i")
44
- };
45
- const octalRegex = new RegExp(`^0[0-7]+$`, "i");
46
- const hexRegex = new RegExp(`^0x[a-f0-9]+$`, "i");
47
- const zoneIndex = "%[0-9a-z]{1,}";
48
- const ipv6Part = "(?:[0-9a-f]+::?)+";
49
- const ipv6Regexes = {
50
- zoneIndex: new RegExp(zoneIndex, "i"),
51
- "native": new RegExp(`^(::)?(${ipv6Part})?([0-9a-f]+)?(::)?(${zoneIndex})?$`, "i"),
52
- deprecatedTransitional: new RegExp(`^(?:::)(${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}(${zoneIndex})?)$`, "i"),
53
- transitional: new RegExp(`^((?:${ipv6Part})|(?:::)(?:${ipv6Part})?)${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}(${zoneIndex})?$`, "i")
54
- };
55
- function expandIPv6(string, parts) {
56
- if (string.indexOf("::") !== string.lastIndexOf("::")) return null;
57
- let colonCount = 0;
58
- let lastColon = -1;
59
- let zoneId = (string.match(ipv6Regexes.zoneIndex) || [])[0];
60
- let replacement, replacementCount;
61
- if (zoneId) {
62
- zoneId = zoneId.substring(1);
63
- string = string.replace(/%.+$/, "");
64
- }
65
- while ((lastColon = string.indexOf(":", lastColon + 1)) >= 0) colonCount++;
66
- if (string.substr(0, 2) === "::") colonCount--;
67
- if (string.substr(-2, 2) === "::") colonCount--;
68
- if (colonCount > parts) return null;
69
- replacementCount = parts - colonCount;
70
- replacement = ":";
71
- while (replacementCount--) replacement += "0:";
72
- string = string.replace("::", replacement);
73
- if (string[0] === ":") string = string.slice(1);
74
- if (string[string.length - 1] === ":") string = string.slice(0, -1);
75
- parts = (function() {
76
- const ref = string.split(":");
77
- const results = [];
78
- for (let i = 0; i < ref.length; i++) results.push(parseInt(ref[i], 16));
79
- return results;
80
- })();
33
+ //#region packages/net-policy/src/redact-sensitive-url.ts
34
+ const SENSITIVE_URL_QUERY_PARAM_NAMES = /* @__PURE__ */ new Set([
35
+ "token",
36
+ "key",
37
+ "api_key",
38
+ "apikey",
39
+ "secret",
40
+ "access_token",
41
+ "auth_token",
42
+ "password",
43
+ "pass",
44
+ "passwd",
45
+ "auth",
46
+ "jwt",
47
+ "session",
48
+ "id_token",
49
+ "code",
50
+ "client_secret",
51
+ "app_secret",
52
+ "hook_token",
53
+ "refresh_token",
54
+ "signature",
55
+ "x_amz_signature",
56
+ "x_amz_security_token",
57
+ "private_key",
58
+ "credential",
59
+ "authorization",
60
+ "sig",
61
+ "x_api_key",
62
+ "x_access_token",
63
+ "x_auth_token"
64
+ ]);
65
+ const URL_QUERY_NAME_SEPARATOR_RE = /[\p{C}\p{Z}\u115F\u1160\u3164\uFFA0+]/gu;
66
+ const SUFFIXED_OR_SCOPED_TOKEN_QUERY_PARAM_RE = /(?:^|_)token(?:_[a-f0-9]{16,})?$/u;
67
+ const TELEGRAM_BOT_TOKEN_PATH_RE = /\/bot\d{6,}(?::|%3[aA])[A-Za-z0-9_-]{20,}(?=\/|$)/giu;
68
+ const MAX_NESTED_URL_REDACTION_DEPTH = 8;
69
+ const URL_SCHEME_RE = /(?:^|[^a-z\d+.-])[a-z][a-z\d+.-]{0,31}:/iu;
70
+ const SPECIAL_SCHEME_AUTHORITY_RE = /\b(?:https?|wss?|ftp):[\\/]{0,2}[^\\/?#\s]*/giu;
71
+ const SPECIAL_SCHEME_SPILLED_USERINFO_RE = /\b(?:https?|wss?|ftp):[\\/]{0,2}[^\s]*@[^\\/?#\s]*/giu;
72
+ const PROTOCOL_RELATIVE_AUTHORITY_RE = /[\\/]{2,}[^\\/?#\s]*/gu;
73
+ function redactSensitiveUrlPath(value) {
74
+ return value.replace(TELEGRAM_BOT_TOKEN_PATH_RE, "/bot***");
75
+ }
76
+ function normalizeUrlQueryParamName(name) {
77
+ let current = name.replace(URL_QUERY_NAME_SEPARATOR_RE, "");
78
+ for (let depth = 0; depth <= MAX_NESTED_URL_REDACTION_DEPTH; depth += 1) {
79
+ let decoded;
80
+ try {
81
+ decoded = decodeURIComponent(current).replace(URL_QUERY_NAME_SEPARATOR_RE, "");
82
+ } catch {
81
83
  return {
82
- parts,
83
- zoneId
84
+ value: normalizeLowercaseStringOrEmpty(current).replaceAll("-", "_"),
85
+ unresolvedEncoding: current.includes("%")
84
86
  };
85
87
  }
86
- function matchCIDR(first, second, partSize, cidrBits) {
87
- if (first.length !== second.length) throw new Error("ipaddr: cannot match CIDR for objects with different lengths");
88
- let part = 0;
89
- let shift;
90
- while (cidrBits > 0) {
91
- shift = partSize - cidrBits;
92
- if (shift < 0) shift = 0;
93
- if (first[part] >> shift !== second[part] >> shift) return false;
94
- cidrBits -= partSize;
95
- part += 1;
96
- }
97
- return true;
88
+ if (decoded === current) return {
89
+ value: normalizeLowercaseStringOrEmpty(current).replaceAll("-", "_"),
90
+ unresolvedEncoding: false
91
+ };
92
+ current = decoded;
93
+ }
94
+ return {
95
+ value: normalizeLowercaseStringOrEmpty(current).replaceAll("-", "_"),
96
+ unresolvedEncoding: current.includes("%")
97
+ };
98
+ }
99
+ function looksLikeNestedUrlValue(value) {
100
+ if (URL_SCHEME_RE.test(value)) return true;
101
+ const forwardAuthorityIndex = value.indexOf("//");
102
+ const backwardAuthorityIndex = value.indexOf("\\\\");
103
+ const authorityIndex = forwardAuthorityIndex < 0 ? backwardAuthorityIndex : backwardAuthorityIndex < 0 ? forwardAuthorityIndex : Math.min(forwardAuthorityIndex, backwardAuthorityIndex);
104
+ if (authorityIndex >= 0 && value.includes("@", authorityIndex + 2)) return true;
105
+ const queryIndex = value.search(/[?&]/u);
106
+ if (queryIndex >= 0 && value.includes("=", queryIndex + 1)) return true;
107
+ const fragmentIndex = value.indexOf("#");
108
+ if (fragmentIndex >= 0 && value.includes("=", fragmentIndex + 1)) return true;
109
+ return /%[\da-f]{2}/iu.test(value);
110
+ }
111
+ /** True for auth-like URL query parameter names that should be redacted. */
112
+ function isSensitiveUrlQueryParamName$1(name) {
113
+ const normalized = normalizeUrlQueryParamName(name);
114
+ return normalized.unresolvedEncoding || SENSITIVE_URL_QUERY_PARAM_NAMES.has(normalized.value) || SUFFIXED_OR_SCOPED_TOKEN_QUERY_PARAM_RE.test(normalized.value);
115
+ }
116
+ function redactDirectSensitiveUrl(value) {
117
+ try {
118
+ const parsed = new URL(value);
119
+ let mutated = false;
120
+ const redactedPath = redactSensitiveUrlPath(parsed.pathname);
121
+ if (redactedPath !== parsed.pathname) {
122
+ parsed.pathname = redactedPath;
123
+ mutated = true;
98
124
  }
99
- function parseIntAuto(string) {
100
- if (hexRegex.test(string)) return parseInt(string, 16);
101
- if (string[0] === "0" && !isNaN(parseInt(string[1], 10))) {
102
- if (octalRegex.test(string)) return parseInt(string, 8);
103
- throw new Error(`ipaddr: cannot parse ${string} as octal`);
104
- }
105
- return parseInt(string, 10);
125
+ if (parsed.username || parsed.password) {
126
+ parsed.username = parsed.username ? "***" : "";
127
+ parsed.password = parsed.password ? "***" : "";
128
+ mutated = true;
106
129
  }
107
- function padPart(part, length) {
108
- while (part.length < length) part = `0${part}`;
109
- return part;
130
+ for (const key of Array.from(parsed.searchParams.keys())) if (isSensitiveUrlQueryParamName$1(key)) {
131
+ parsed.searchParams.set(key, "***");
132
+ mutated = true;
110
133
  }
111
- const ipaddr = {};
112
- ipaddr.IPv4 = (function() {
113
- function IPv4(octets) {
114
- if (octets.length !== 4) throw new Error("ipaddr: ipv4 octet count should be 4");
115
- let i, octet;
116
- for (i = 0; i < octets.length; i++) {
117
- octet = octets[i];
118
- if (!(0 <= octet && octet <= 255)) throw new Error("ipaddr: ipv4 octet should fit in 8 bits");
119
- }
120
- this.octets = octets;
121
- }
122
- IPv4.prototype.SpecialRanges = {
123
- unspecified: [[new IPv4([
124
- 0,
125
- 0,
126
- 0,
127
- 0
128
- ]), 8]],
129
- broadcast: [[new IPv4([
130
- 255,
131
- 255,
132
- 255,
133
- 255
134
- ]), 32]],
135
- multicast: [[new IPv4([
136
- 224,
137
- 0,
138
- 0,
139
- 0
140
- ]), 4]],
141
- linkLocal: [[new IPv4([
142
- 169,
143
- 254,
144
- 0,
145
- 0
146
- ]), 16]],
147
- loopback: [[new IPv4([
148
- 127,
149
- 0,
150
- 0,
151
- 0
152
- ]), 8]],
153
- carrierGradeNat: [[new IPv4([
154
- 100,
155
- 64,
156
- 0,
157
- 0
158
- ]), 10]],
159
- "private": [
160
- [new IPv4([
161
- 10,
162
- 0,
163
- 0,
164
- 0
165
- ]), 8],
166
- [new IPv4([
167
- 172,
168
- 16,
169
- 0,
170
- 0
171
- ]), 12],
172
- [new IPv4([
173
- 192,
174
- 168,
175
- 0,
176
- 0
177
- ]), 16]
178
- ],
179
- reserved: [
180
- [new IPv4([
181
- 192,
182
- 0,
183
- 0,
184
- 0
185
- ]), 24],
186
- [new IPv4([
187
- 192,
188
- 0,
189
- 2,
190
- 0
191
- ]), 24],
192
- [new IPv4([
193
- 192,
194
- 88,
195
- 99,
196
- 0
197
- ]), 24],
198
- [new IPv4([
199
- 198,
200
- 18,
201
- 0,
202
- 0
203
- ]), 15],
204
- [new IPv4([
205
- 198,
206
- 51,
207
- 100,
208
- 0
209
- ]), 24],
210
- [new IPv4([
211
- 203,
212
- 0,
213
- 113,
214
- 0
215
- ]), 24],
216
- [new IPv4([
217
- 240,
218
- 0,
219
- 0,
220
- 0
221
- ]), 4]
222
- ],
223
- as112: [[new IPv4([
224
- 192,
225
- 175,
226
- 48,
227
- 0
228
- ]), 24], [new IPv4([
229
- 192,
230
- 31,
231
- 196,
232
- 0
233
- ]), 24]],
234
- amt: [[new IPv4([
235
- 192,
236
- 52,
237
- 193,
238
- 0
239
- ]), 24]]
240
- };
241
- IPv4.prototype.kind = function() {
242
- return "ipv4";
243
- };
244
- IPv4.prototype.match = function(other, cidrRange) {
245
- let ref;
246
- if (cidrRange === void 0) {
247
- ref = other;
248
- other = ref[0];
249
- cidrRange = ref[1];
250
- }
251
- if (other.kind() !== "ipv4") throw new Error("ipaddr: cannot match ipv4 address with non-ipv4 one");
252
- return matchCIDR(this.octets, other.octets, 8, cidrRange);
253
- };
254
- IPv4.prototype.prefixLengthFromSubnetMask = function() {
255
- let cidr = 0;
256
- let stop = false;
257
- const zerotable = {
258
- 0: 8,
259
- 128: 7,
260
- 192: 6,
261
- 224: 5,
262
- 240: 4,
263
- 248: 3,
264
- 252: 2,
265
- 254: 1,
266
- 255: 0
267
- };
268
- let i, octet, zeros;
269
- for (i = 3; i >= 0; i -= 1) {
270
- octet = this.octets[i];
271
- if (octet in zerotable) {
272
- zeros = zerotable[octet];
273
- if (stop && zeros !== 0) return null;
274
- if (zeros !== 8) stop = true;
275
- cidr += zeros;
276
- } else return null;
277
- }
278
- return 32 - cidr;
279
- };
280
- IPv4.prototype.range = function() {
281
- return ipaddr.subnetMatch(this, this.SpecialRanges);
282
- };
283
- IPv4.prototype.toByteArray = function() {
284
- return this.octets.slice(0);
285
- };
286
- IPv4.prototype.toIPv4MappedAddress = function() {
287
- return ipaddr.IPv6.parse(`::ffff:${this.toString()}`);
288
- };
289
- IPv4.prototype.toNormalizedString = function() {
290
- return this.toString();
291
- };
292
- IPv4.prototype.toString = function() {
293
- return this.octets.join(".");
294
- };
295
- return IPv4;
296
- })();
297
- ipaddr.IPv4.broadcastAddressFromCIDR = function(string) {
298
- try {
299
- const cidr = this.parseCIDR(string);
300
- const ipInterfaceOctets = cidr[0].toByteArray();
301
- const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
302
- const octets = [];
303
- let i = 0;
304
- while (i < 4) {
305
- octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255);
306
- i++;
307
- }
308
- return new this(octets);
309
- } catch (e) {
310
- throw new Error("ipaddr: the address does not have IPv4 CIDR format");
311
- }
312
- };
313
- ipaddr.IPv4.isIPv4 = function(string) {
314
- return this.parser(string) !== null;
315
- };
316
- ipaddr.IPv4.isValid = function(string) {
317
- try {
318
- new this(this.parser(string));
319
- return true;
320
- } catch (e) {
321
- return false;
322
- }
323
- };
324
- ipaddr.IPv4.isValidCIDR = function(string) {
325
- try {
326
- this.parseCIDR(string);
327
- return true;
328
- } catch (e) {
329
- return false;
330
- }
331
- };
332
- ipaddr.IPv4.isValidFourPartDecimal = function(string) {
333
- if (ipaddr.IPv4.isValid(string) && string.match(/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){3}$/)) return true;
334
- else return false;
335
- };
336
- ipaddr.IPv4.isValidCIDRFourPartDecimal = function(string) {
337
- const match = string.match(/^(.+)\/(\d+)$/);
338
- if (!ipaddr.IPv4.isValidCIDR(string) || !match) return false;
339
- return ipaddr.IPv4.isValidFourPartDecimal(match[1]);
340
- };
341
- ipaddr.IPv4.networkAddressFromCIDR = function(string) {
342
- let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets;
343
- try {
344
- cidr = this.parseCIDR(string);
345
- ipInterfaceOctets = cidr[0].toByteArray();
346
- subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
347
- octets = [];
348
- i = 0;
349
- while (i < 4) {
350
- octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10));
351
- i++;
352
- }
353
- return new this(octets);
354
- } catch (e) {
355
- throw new Error("ipaddr: the address does not have IPv4 CIDR format");
356
- }
357
- };
358
- ipaddr.IPv4.parse = function(string) {
359
- const parts = this.parser(string);
360
- if (parts === null) throw new Error("ipaddr: string is not formatted like an IPv4 Address");
361
- return new this(parts);
362
- };
363
- ipaddr.IPv4.parseCIDR = function(string) {
364
- let match;
365
- if (match = string.match(/^(.+)\/(\d+)$/)) {
366
- const maskLength = parseInt(match[2]);
367
- if (maskLength >= 0 && maskLength <= 32) {
368
- const parsed = [this.parse(match[1]), maskLength];
369
- Object.defineProperty(parsed, "toString", { value: function() {
370
- return this.join("/");
371
- } });
372
- return parsed;
373
- }
374
- }
375
- throw new Error("ipaddr: string is not formatted like an IPv4 CIDR range");
376
- };
377
- ipaddr.IPv4.parser = function(string) {
378
- let match, part, value;
379
- if (match = string.match(ipv4Regexes.fourOctet)) return (function() {
380
- const ref = match.slice(1, 6);
381
- const results = [];
382
- for (let i = 0; i < ref.length; i++) {
383
- part = ref[i];
384
- results.push(parseIntAuto(part));
385
- }
386
- return results;
387
- })();
388
- else if (match = string.match(ipv4Regexes.longValue)) {
389
- value = parseIntAuto(match[1]);
390
- if (value > 4294967295 || value < 0) throw new Error("ipaddr: address outside defined range");
391
- return (function() {
392
- const results = [];
393
- let shift;
394
- for (shift = 0; shift <= 24; shift += 8) results.push(value >> shift & 255);
395
- return results;
396
- })().reverse();
397
- } else if (match = string.match(ipv4Regexes.twoOctet)) return (function() {
398
- const ref = match.slice(1, 4);
399
- const results = [];
400
- value = parseIntAuto(ref[1]);
401
- if (value > 16777215 || value < 0) throw new Error("ipaddr: address outside defined range");
402
- results.push(parseIntAuto(ref[0]));
403
- results.push(value >> 16 & 255);
404
- results.push(value >> 8 & 255);
405
- results.push(value & 255);
406
- return results;
407
- })();
408
- else if (match = string.match(ipv4Regexes.threeOctet)) return (function() {
409
- const ref = match.slice(1, 5);
410
- const results = [];
411
- value = parseIntAuto(ref[2]);
412
- if (value > 65535 || value < 0) throw new Error("ipaddr: address outside defined range");
413
- results.push(parseIntAuto(ref[0]));
414
- results.push(parseIntAuto(ref[1]));
415
- results.push(value >> 8 & 255);
416
- results.push(value & 255);
417
- return results;
418
- })();
419
- else return null;
420
- };
421
- ipaddr.IPv4.subnetMaskFromPrefixLength = function(prefix) {
422
- prefix = parseInt(prefix);
423
- if (prefix < 0 || prefix > 32) throw new Error("ipaddr: invalid IPv4 prefix length");
424
- const octets = [
425
- 0,
426
- 0,
427
- 0,
428
- 0
429
- ];
430
- let j = 0;
431
- const filledOctetCount = Math.floor(prefix / 8);
432
- while (j < filledOctetCount) {
433
- octets[j] = 255;
434
- j++;
134
+ return mutated ? parsed.toString() : value;
135
+ } catch {
136
+ return value;
137
+ }
138
+ }
139
+ function redactQueryString(value, depth) {
140
+ const params = new URLSearchParams(value);
141
+ const entries = Array.from(params.entries());
142
+ const redactedEntries = [];
143
+ const seenSensitiveKeys = /* @__PURE__ */ new Set();
144
+ let mutated = false;
145
+ for (const [key, entryValue] of entries) {
146
+ if (isSensitiveUrlQueryParamName$1(key)) {
147
+ mutated = true;
148
+ if (!seenSensitiveKeys.has(key)) {
149
+ seenSensitiveKeys.add(key);
150
+ redactedEntries.push([key, "***"]);
435
151
  }
436
- if (filledOctetCount < 4) octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - prefix % 8;
437
- return new this(octets);
152
+ continue;
153
+ }
154
+ const redactedKey = redactNestedUrlValue(key, depth + 1);
155
+ const redactedValue = redactNestedUrlValue(entryValue, depth + 1);
156
+ if (redactedKey !== key || redactedValue !== entryValue) mutated = true;
157
+ redactedEntries.push([redactedKey, redactedValue]);
158
+ }
159
+ if (!mutated) return value;
160
+ const redactedParams = new URLSearchParams();
161
+ for (const [key, entryValue] of redactedEntries) redactedParams.append(key, entryValue);
162
+ return redactedParams.toString();
163
+ }
164
+ function redactUrlLikeFallback(value) {
165
+ return redactSensitiveUrlPath(redactEmbeddedUrlUserInfo(value).replace(/([?&])([^=&]+)=([^&]*)/g, (match, prefix, key) => isSensitiveUrlQueryParamName$1(key) ? `${prefix}${key}=***` : match));
166
+ }
167
+ function redactAuthorityUserInfo(candidate, authorityStart) {
168
+ const authority = candidate.slice(authorityStart);
169
+ const userInfoEnd = authority.lastIndexOf("@");
170
+ if (userInfoEnd < 0) return candidate;
171
+ return `${candidate.slice(0, authorityStart)}***:***@${authority.slice(userInfoEnd + 1)}`;
172
+ }
173
+ function redactEmbeddedUrlUserInfo(value) {
174
+ return value.replace(SPECIAL_SCHEME_AUTHORITY_RE, (candidate) => {
175
+ let authorityStart = candidate.indexOf(":") + 1;
176
+ while (authorityStart < candidate.length && (candidate[authorityStart] === "/" || candidate[authorityStart] === "\\")) authorityStart += 1;
177
+ return redactAuthorityUserInfo(candidate, authorityStart);
178
+ }).replace(SPECIAL_SCHEME_SPILLED_USERINFO_RE, (candidate) => {
179
+ let authorityStart = candidate.indexOf(":") + 1;
180
+ while (authorityStart < candidate.length && (candidate[authorityStart] === "/" || candidate[authorityStart] === "\\")) authorityStart += 1;
181
+ const userInfoEnd = candidate.lastIndexOf("@");
182
+ const firstReservedDelimiter = candidate.slice(authorityStart).search(/[\\/?#]/u);
183
+ if (userInfoEnd < 0 || firstReservedDelimiter < 0) return candidate;
184
+ const absoluteReservedDelimiter = authorityStart + firstReservedDelimiter;
185
+ if (absoluteReservedDelimiter >= userInfoEnd) return candidate;
186
+ const credentialSeparator = candidate.indexOf(":", authorityStart);
187
+ if (credentialSeparator < 0 || credentialSeparator > absoluteReservedDelimiter) return candidate;
188
+ const authorityPrefix = candidate.slice(authorityStart, absoluteReservedDelimiter);
189
+ const possiblePort = candidate.slice(credentialSeparator + 1, absoluteReservedDelimiter);
190
+ if (/^\d+$/u.test(possiblePort) || /^\[[^\]]+\](?::\d+)?$/u.test(authorityPrefix)) return candidate;
191
+ return `${candidate.slice(0, authorityStart)}***:***@${candidate.slice(userInfoEnd + 1)}`;
192
+ }).replace(PROTOCOL_RELATIVE_AUTHORITY_RE, (candidate) => {
193
+ let authorityStart = 0;
194
+ while (authorityStart < candidate.length && (candidate[authorityStart] === "/" || candidate[authorityStart] === "\\")) authorityStart += 1;
195
+ return redactAuthorityUserInfo(candidate, authorityStart);
196
+ });
197
+ }
198
+ function hasUnresolvedEmbeddedUrlUserInfo(value) {
199
+ for (const match of value.matchAll(/(?:\b(?:https?|wss?|ftp):[\\/]{0,2}|[\\/]{2,})/giu)) {
200
+ const remainder = value.slice((match.index ?? 0) + match[0].length);
201
+ const userInfoEnd = remainder.search(/(?<!\*\*\*:\*\*\*)@/u);
202
+ const authorityEnd = remainder.search(/[\\/?#]/u);
203
+ const pathBeforeAt = remainder.slice(authorityEnd + 1, userInfoEnd);
204
+ if (userInfoEnd >= 0 && (authorityEnd < 0 || userInfoEnd <= authorityEnd || remainder[authorityEnd] === "/" && (pathBeforeAt.includes(":") || /^[^/?#\s]+\.[^/?#\s]+(?:[/?#]|$)/u.test(remainder.slice(userInfoEnd + 1))))) return true;
205
+ }
206
+ return false;
207
+ }
208
+ function redactRelativeUrlFragment(value, depth) {
209
+ const fragmentIndex = value.indexOf("#");
210
+ if (fragmentIndex < 0) return value;
211
+ const fragment = value.slice(fragmentIndex + 1);
212
+ const redactedFragment = redactFragment(fragment, depth + 1);
213
+ return redactedFragment === fragment ? value : `${value.slice(0, fragmentIndex + 1)}${redactedFragment}`;
214
+ }
215
+ function redactFragment(value, depth) {
216
+ if (!value) return value;
217
+ if (depth > MAX_NESTED_URL_REDACTION_DEPTH && looksLikeNestedUrlValue(value)) return "***";
218
+ const wholeUrl = redactSensitiveUrlAtDepth(value, depth);
219
+ if (wholeUrl.parsedWholeUrl) return redactUrlLikeFallback(wholeUrl.value);
220
+ const candidate = value;
221
+ const firstQueryDelimiter = candidate.search(/[?&]/u);
222
+ const firstEquals = candidate.indexOf("=");
223
+ if (firstEquals >= 0 && (firstQueryDelimiter < 0 || firstEquals < firstQueryDelimiter)) return redactQueryString(candidate, depth);
224
+ const hashRouterQueryIndex = candidate.indexOf("?");
225
+ if (hashRouterQueryIndex >= 0) {
226
+ const redactedQuery = redactQueryString(candidate.slice(hashRouterQueryIndex + 1), depth);
227
+ return `${redactEncodedUrlLikeString(redactUrlLikeFallback(candidate.slice(0, hashRouterQueryIndex + 1)), depth + 1)}${redactedQuery}`;
228
+ }
229
+ const fallback = redactUrlLikeFallback(candidate);
230
+ if (!looksLikeNestedUrlValue(fallback)) return fallback;
231
+ let decoded;
232
+ try {
233
+ decoded = decodeURIComponent(fallback);
234
+ } catch {
235
+ return "***";
236
+ }
237
+ if (decoded === fallback) return fallback;
238
+ const redactedDecoded = redactFragment(decoded, depth + 1);
239
+ return redactedDecoded === decoded ? fallback : encodeURIComponent(redactedDecoded);
240
+ }
241
+ function redactEncodedNestedUrlPath(value, depth) {
242
+ if (!looksLikeNestedUrlValue(value)) return value;
243
+ if (depth > MAX_NESTED_URL_REDACTION_DEPTH) return "***";
244
+ let decoded;
245
+ try {
246
+ decoded = decodeURIComponent(value);
247
+ } catch {
248
+ return "***";
249
+ }
250
+ if (decoded === value) return value;
251
+ const direct = redactSensitiveUrlLikeStringAtDepth(decoded, depth);
252
+ if (direct.value !== decoded || hasUnresolvedEmbeddedUrlUserInfo(decoded)) return direct.value !== decoded ? direct.value : "***";
253
+ if (direct.parsedWholeUrl) return value;
254
+ const nested = redactEncodedNestedUrlPath(decoded, depth + 1);
255
+ return nested === decoded ? value : nested;
256
+ }
257
+ function redactSensitiveUrlAtDepth(value, depth) {
258
+ try {
259
+ const directRedaction = redactDirectSensitiveUrl(value);
260
+ const parsed = new URL(directRedaction);
261
+ if (depth > MAX_NESTED_URL_REDACTION_DEPTH) return {
262
+ value: "***",
263
+ parsedWholeUrl: true
438
264
  };
439
- ipaddr.IPv6 = (function() {
440
- function IPv6(parts, zoneId) {
441
- let i, part;
442
- if (parts.length === 16) {
443
- this.parts = [];
444
- for (i = 0; i <= 14; i += 2) this.parts.push(parts[i] << 8 | parts[i + 1]);
445
- } else if (parts.length === 8) this.parts = parts;
446
- else throw new Error("ipaddr: ipv6 part count should be 8 or 16");
447
- for (i = 0; i < this.parts.length; i++) {
448
- part = this.parts[i];
449
- if (!(0 <= part && part <= 65535)) throw new Error("ipaddr: ipv6 part should fit in 16 bits");
450
- }
451
- if (zoneId) this.zoneId = zoneId;
452
- }
453
- IPv6.prototype.SpecialRanges = {
454
- unspecified: [new IPv6([
455
- 0,
456
- 0,
457
- 0,
458
- 0,
459
- 0,
460
- 0,
461
- 0,
462
- 0
463
- ]), 128],
464
- linkLocal: [new IPv6([
465
- 65152,
466
- 0,
467
- 0,
468
- 0,
469
- 0,
470
- 0,
471
- 0,
472
- 0
473
- ]), 10],
474
- multicast: [new IPv6([
475
- 65280,
476
- 0,
477
- 0,
478
- 0,
479
- 0,
480
- 0,
481
- 0,
482
- 0
483
- ]), 8],
484
- loopback: [new IPv6([
485
- 0,
486
- 0,
487
- 0,
488
- 0,
489
- 0,
490
- 0,
491
- 0,
492
- 1
493
- ]), 128],
494
- uniqueLocal: [new IPv6([
495
- 64512,
496
- 0,
497
- 0,
498
- 0,
499
- 0,
500
- 0,
501
- 0,
502
- 0
503
- ]), 7],
504
- ipv4Mapped: [new IPv6([
505
- 0,
506
- 0,
507
- 0,
508
- 0,
509
- 0,
510
- 65535,
511
- 0,
512
- 0
513
- ]), 96],
514
- deprecatedSiteLocal: [new IPv6([
515
- 65216,
516
- 0,
517
- 0,
518
- 0,
519
- 0,
520
- 0,
521
- 0,
522
- 0
523
- ]), 10],
524
- discard: [new IPv6([
525
- 256,
526
- 0,
527
- 0,
528
- 0,
529
- 0,
530
- 0,
531
- 0,
532
- 0
533
- ]), 64],
534
- rfc6145: [new IPv6([
535
- 0,
536
- 0,
537
- 0,
538
- 0,
539
- 65535,
540
- 0,
541
- 0,
542
- 0
543
- ]), 96],
544
- rfc6052: [[new IPv6([
545
- 100,
546
- 65435,
547
- 0,
548
- 0,
549
- 0,
550
- 0,
551
- 0,
552
- 0
553
- ]), 96], [new IPv6([
554
- 100,
555
- 65435,
556
- 1,
557
- 0,
558
- 0,
559
- 0,
560
- 0,
561
- 0
562
- ]), 48]],
563
- "6to4": [new IPv6([
564
- 8194,
565
- 0,
566
- 0,
567
- 0,
568
- 0,
569
- 0,
570
- 0,
571
- 0
572
- ]), 16],
573
- teredo: [new IPv6([
574
- 8193,
575
- 0,
576
- 0,
577
- 0,
578
- 0,
579
- 0,
580
- 0,
581
- 0
582
- ]), 32],
583
- benchmarking: [new IPv6([
584
- 8193,
585
- 2,
586
- 0,
587
- 0,
588
- 0,
589
- 0,
590
- 0,
591
- 0
592
- ]), 48],
593
- amt: [new IPv6([
594
- 8193,
595
- 3,
596
- 0,
597
- 0,
598
- 0,
599
- 0,
600
- 0,
601
- 0
602
- ]), 32],
603
- as112v6: [[new IPv6([
604
- 8193,
605
- 4,
606
- 274,
607
- 0,
608
- 0,
609
- 0,
610
- 0,
611
- 0
612
- ]), 48], [new IPv6([
613
- 9760,
614
- 79,
615
- 32768,
616
- 0,
617
- 0,
618
- 0,
619
- 0,
620
- 0
621
- ]), 48]],
622
- deprecatedOrchid: [new IPv6([
623
- 8193,
624
- 16,
625
- 0,
626
- 0,
627
- 0,
628
- 0,
629
- 0,
630
- 0
631
- ]), 28],
632
- orchid2: [new IPv6([
633
- 8193,
634
- 32,
635
- 0,
636
- 0,
637
- 0,
638
- 0,
639
- 0,
640
- 0
641
- ]), 28],
642
- droneRemoteIdProtocolEntityTags: [new IPv6([
643
- 8193,
644
- 48,
645
- 0,
646
- 0,
647
- 0,
648
- 0,
649
- 0,
650
- 0
651
- ]), 28],
652
- segmentRouting: [new IPv6([
653
- 24320,
654
- 0,
655
- 0,
656
- 0,
657
- 0,
658
- 0,
659
- 0,
660
- 0
661
- ]), 16],
662
- reserved: [
663
- [new IPv6([
664
- 8193,
665
- 0,
666
- 0,
667
- 0,
668
- 0,
669
- 0,
670
- 0,
671
- 0
672
- ]), 23],
673
- [new IPv6([
674
- 8193,
675
- 3512,
676
- 0,
677
- 0,
678
- 0,
679
- 0,
680
- 0,
681
- 0
682
- ]), 32],
683
- [new IPv6([
684
- 16383,
685
- 0,
686
- 0,
687
- 0,
688
- 0,
689
- 0,
690
- 0,
691
- 0
692
- ]), 20]
693
- ]
694
- };
695
- IPv6.prototype.isIPv4MappedAddress = function() {
696
- return this.range() === "ipv4Mapped";
697
- };
698
- IPv6.prototype.kind = function() {
699
- return "ipv6";
700
- };
701
- IPv6.prototype.match = function(other, cidrRange) {
702
- let ref;
703
- if (cidrRange === void 0) {
704
- ref = other;
705
- other = ref[0];
706
- cidrRange = ref[1];
707
- }
708
- if (other.kind() !== "ipv6") throw new Error("ipaddr: cannot match ipv6 address with non-ipv6 one");
709
- return matchCIDR(this.parts, other.parts, 16, cidrRange);
710
- };
711
- IPv6.prototype.prefixLengthFromSubnetMask = function() {
712
- let cidr = 0;
713
- let stop = false;
714
- const zerotable = {
715
- 0: 16,
716
- 32768: 15,
717
- 49152: 14,
718
- 57344: 13,
719
- 61440: 12,
720
- 63488: 11,
721
- 64512: 10,
722
- 65024: 9,
723
- 65280: 8,
724
- 65408: 7,
725
- 65472: 6,
726
- 65504: 5,
727
- 65520: 4,
728
- 65528: 3,
729
- 65532: 2,
730
- 65534: 1,
731
- 65535: 0
732
- };
733
- let part, zeros;
734
- for (let i = 7; i >= 0; i -= 1) {
735
- part = this.parts[i];
736
- if (part in zerotable) {
737
- zeros = zerotable[part];
738
- if (stop && zeros !== 0) return null;
739
- if (zeros !== 16) stop = true;
740
- cidr += zeros;
741
- } else return null;
742
- }
743
- return 128 - cidr;
744
- };
745
- IPv6.prototype.range = function() {
746
- return ipaddr.subnetMatch(this, this.SpecialRanges);
747
- };
748
- IPv6.prototype.toByteArray = function() {
749
- let part;
750
- const bytes = [];
751
- const ref = this.parts;
752
- for (let i = 0; i < ref.length; i++) {
753
- part = ref[i];
754
- bytes.push(part >> 8);
755
- bytes.push(part & 255);
756
- }
757
- return bytes;
758
- };
759
- IPv6.prototype.toFixedLengthString = function() {
760
- const addr = (function() {
761
- const results = [];
762
- for (let i = 0; i < this.parts.length; i++) results.push(padPart(this.parts[i].toString(16), 4));
763
- return results;
764
- }).call(this).join(":");
765
- let suffix = "";
766
- if (this.zoneId) suffix = `%${this.zoneId}`;
767
- return addr + suffix;
768
- };
769
- IPv6.prototype.toIPv4Address = function() {
770
- if (!this.isIPv4MappedAddress()) throw new Error("ipaddr: trying to convert a generic ipv6 address to ipv4");
771
- const ref = this.parts.slice(-2);
772
- const high = ref[0];
773
- const low = ref[1];
774
- return new ipaddr.IPv4([
775
- high >> 8,
776
- high & 255,
777
- low >> 8,
778
- low & 255
779
- ]);
780
- };
781
- IPv6.prototype.toNormalizedString = function() {
782
- const addr = (function() {
783
- const results = [];
784
- for (let i = 0; i < this.parts.length; i++) results.push(this.parts[i].toString(16));
785
- return results;
786
- }).call(this).join(":");
787
- let suffix = "";
788
- if (this.zoneId) suffix = `%${this.zoneId}`;
789
- return addr + suffix;
790
- };
791
- IPv6.prototype.toRFC5952String = function() {
792
- const regex = /((^|:)(0(:|$)){2,})/g;
793
- const string = this.toNormalizedString();
794
- let bestMatchIndex = 0;
795
- let bestMatchLength = -1;
796
- let match;
797
- while (match = regex.exec(string)) if (match[0].length > bestMatchLength) {
798
- bestMatchIndex = match.index;
799
- bestMatchLength = match[0].length;
800
- }
801
- if (bestMatchLength < 0) return string;
802
- return `${string.substring(0, bestMatchIndex)}::${string.substring(bestMatchIndex + bestMatchLength)}`;
265
+ let mutated = directRedaction !== value;
266
+ const redactedNestedPath = redactEmbeddedUrlUserInfo(redactEncodedNestedUrlPath(parsed.pathname, depth + 1));
267
+ if (redactedNestedPath !== parsed.pathname) {
268
+ const originalPath = parsed.pathname;
269
+ parsed.pathname = redactedNestedPath;
270
+ if (parsed.pathname === originalPath) return {
271
+ value: directRedaction,
272
+ parsedWholeUrl: false
803
273
  };
804
- IPv6.prototype.toString = function() {
805
- return this.toRFC5952String();
806
- };
807
- return IPv6;
808
- })();
809
- ipaddr.IPv6.broadcastAddressFromCIDR = function(string) {
810
- try {
811
- const cidr = this.parseCIDR(string);
812
- const ipInterfaceOctets = cidr[0].toByteArray();
813
- const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
814
- const octets = [];
815
- let i = 0;
816
- while (i < 16) {
817
- octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255);
818
- i++;
819
- }
820
- return new this(octets);
821
- } catch (e) {
822
- throw new Error(`ipaddr: the address does not have IPv6 CIDR format (${e})`);
823
- }
824
- };
825
- ipaddr.IPv6.isIPv6 = function(string) {
826
- return this.parser(string) !== null;
827
- };
828
- ipaddr.IPv6.isValid = function(string) {
829
- if (typeof string === "string" && string.indexOf(":") === -1) return false;
830
- try {
831
- const addr = this.parser(string);
832
- new this(addr.parts, addr.zoneId);
833
- return true;
834
- } catch (e) {
835
- return false;
836
- }
837
- };
838
- ipaddr.IPv6.isValidCIDR = function(string) {
839
- if (typeof string === "string" && string.indexOf(":") === -1) return false;
840
- try {
841
- this.parseCIDR(string);
842
- return true;
843
- } catch (e) {
844
- return false;
845
- }
846
- };
847
- ipaddr.IPv6.networkAddressFromCIDR = function(string) {
848
- let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets;
849
- try {
850
- cidr = this.parseCIDR(string);
851
- ipInterfaceOctets = cidr[0].toByteArray();
852
- subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
853
- octets = [];
854
- i = 0;
855
- while (i < 16) {
856
- octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10));
857
- i++;
858
- }
859
- return new this(octets);
860
- } catch (e) {
861
- throw new Error(`ipaddr: the address does not have IPv6 CIDR format (${e})`);
862
- }
863
- };
864
- ipaddr.IPv6.parse = function(string) {
865
- const addr = this.parser(string);
866
- if (addr.parts === null) throw new Error("ipaddr: string is not formatted like an IPv6 Address");
867
- return new this(addr.parts, addr.zoneId);
868
- };
869
- ipaddr.IPv6.parseCIDR = function(string) {
870
- let maskLength, match, parsed;
871
- if (match = string.match(/^(.+)\/(\d+)$/)) {
872
- maskLength = parseInt(match[2]);
873
- if (maskLength >= 0 && maskLength <= 128) {
874
- parsed = [this.parse(match[1]), maskLength];
875
- Object.defineProperty(parsed, "toString", { value: function() {
876
- return this.join("/");
877
- } });
878
- return parsed;
879
- }
880
- }
881
- throw new Error("ipaddr: string is not formatted like an IPv6 CIDR range");
882
- };
883
- ipaddr.IPv6.parser = function(string) {
884
- let addr, i, match, octet, octets, zoneId;
885
- if (match = string.match(ipv6Regexes.deprecatedTransitional)) return this.parser(`::ffff:${match[1]}`);
886
- if (ipv6Regexes.native.test(string)) return expandIPv6(string, 8);
887
- if (match = string.match(ipv6Regexes.transitional)) {
888
- zoneId = match[6] || "";
889
- addr = match[1];
890
- if (!match[1].endsWith("::")) addr = addr.slice(0, -1);
891
- addr = expandIPv6(addr + zoneId, 6);
892
- if (addr.parts) {
893
- octets = [
894
- parseInt(match[2]),
895
- parseInt(match[3]),
896
- parseInt(match[4]),
897
- parseInt(match[5])
898
- ];
899
- for (i = 0; i < octets.length; i++) {
900
- octet = octets[i];
901
- if (!(0 <= octet && octet <= 255)) return null;
902
- }
903
- addr.parts.push(octets[0] << 8 | octets[1]);
904
- addr.parts.push(octets[2] << 8 | octets[3]);
905
- return {
906
- parts: addr.parts,
907
- zoneId: addr.zoneId
908
- };
909
- }
910
- }
911
- return null;
912
- };
913
- ipaddr.IPv6.subnetMaskFromPrefixLength = function(prefix) {
914
- prefix = parseInt(prefix);
915
- if (prefix < 0 || prefix > 128) throw new Error("ipaddr: invalid IPv6 prefix length");
916
- const octets = [
917
- 0,
918
- 0,
919
- 0,
920
- 0,
921
- 0,
922
- 0,
923
- 0,
924
- 0,
925
- 0,
926
- 0,
927
- 0,
928
- 0,
929
- 0,
930
- 0,
931
- 0,
932
- 0
933
- ];
934
- let j = 0;
935
- const filledOctetCount = Math.floor(prefix / 8);
936
- while (j < filledOctetCount) {
937
- octets[j] = 255;
938
- j++;
939
- }
940
- if (filledOctetCount < 16) octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - prefix % 8;
941
- return new this(octets);
942
- };
943
- ipaddr.fromByteArray = function(bytes) {
944
- const length = bytes.length;
945
- if (length === 4) return new ipaddr.IPv4(bytes);
946
- else if (length === 16) return new ipaddr.IPv6(bytes);
947
- else throw new Error("ipaddr: the binary input is neither an IPv6 nor IPv4 address");
948
- };
949
- ipaddr.isValid = function(string) {
950
- return ipaddr.IPv6.isValid(string) || ipaddr.IPv4.isValid(string);
951
- };
952
- ipaddr.isValidCIDR = function(string) {
953
- return ipaddr.IPv6.isValidCIDR(string) || ipaddr.IPv4.isValidCIDR(string);
954
- };
955
- ipaddr.parse = function(string) {
956
- if (ipaddr.IPv6.isValid(string)) return ipaddr.IPv6.parse(string);
957
- else if (ipaddr.IPv4.isValid(string)) return ipaddr.IPv4.parse(string);
958
- else throw new Error("ipaddr: the address has neither IPv6 nor IPv4 format");
959
- };
960
- ipaddr.parseCIDR = function(string) {
961
- try {
962
- return ipaddr.IPv6.parseCIDR(string);
963
- } catch (e) {
964
- try {
965
- return ipaddr.IPv4.parseCIDR(string);
966
- } catch (e2) {
967
- throw new Error("ipaddr: the address has neither IPv6 nor IPv4 CIDR format");
968
- }
969
- }
970
- };
971
- ipaddr.process = function(string) {
972
- const addr = this.parse(string);
973
- if (addr.kind() === "ipv6" && addr.isIPv4MappedAddress()) return addr.toIPv4Address();
974
- else return addr;
274
+ mutated = true;
275
+ }
276
+ const redactedQuery = redactQueryString(parsed.search.slice(1), depth);
277
+ if (redactedQuery !== parsed.search.slice(1)) {
278
+ parsed.search = redactedQuery;
279
+ mutated = true;
280
+ }
281
+ const fragment = parsed.hash.slice(1);
282
+ const redactedHash = redactFragment(fragment, depth + 1);
283
+ if (redactedHash !== fragment) {
284
+ parsed.hash = redactedHash;
285
+ mutated = true;
286
+ }
287
+ return {
288
+ value: mutated ? parsed.toString() : value,
289
+ parsedWholeUrl: true
975
290
  };
976
- ipaddr.subnetMatch = function(address, rangeList, defaultName) {
977
- let i, rangeName, rangeSubnets, subnet;
978
- if (defaultName === void 0 || defaultName === null) defaultName = "unicast";
979
- for (rangeName in rangeList) if (Object.prototype.hasOwnProperty.call(rangeList, rangeName)) {
980
- rangeSubnets = rangeList[rangeName];
981
- if (rangeSubnets[0] && !(rangeSubnets[0] instanceof Array)) rangeSubnets = [rangeSubnets];
982
- for (i = 0; i < rangeSubnets.length; i++) {
983
- subnet = rangeSubnets[i];
984
- if (address.kind() === subnet[0].kind() && address.match.apply(address, subnet)) return rangeName;
985
- }
986
- }
987
- return defaultName;
291
+ } catch {
292
+ return {
293
+ value,
294
+ parsedWholeUrl: false
988
295
  };
989
- if (typeof module !== "undefined" && module.exports) module.exports = ipaddr;
990
- else root.ipaddr = ipaddr;
991
- })(exports);
992
- })))(), 1);
993
- function normalizeOptionalString(value) {
994
- if (typeof value !== "string") return;
995
- return value.trim() || void 0;
296
+ }
996
297
  }
997
- function normalizeLowercaseStringOrEmpty$1(value) {
998
- return typeof value === "string" ? value.trim().toLowerCase() : "";
298
+ function redactSensitiveUrlLikeStringAtDepth(value, depth) {
299
+ const redactedUrl = redactSensitiveUrlAtDepth(value, depth);
300
+ if (redactedUrl.parsedWholeUrl) return redactedUrl;
301
+ return {
302
+ value: redactEncodedUrlLikeString(redactRelativeUrlFragment(redactUrlLikeFallback(redactedUrl.value), depth), depth + 1),
303
+ parsedWholeUrl: false
304
+ };
305
+ }
306
+ function redactEncodedUrlLikeString(value, depth) {
307
+ if (!looksLikeNestedUrlValue(value)) return value;
308
+ if (depth > MAX_NESTED_URL_REDACTION_DEPTH) return "***";
309
+ let decoded;
310
+ try {
311
+ decoded = decodeURIComponent(value);
312
+ } catch {
313
+ return "***";
314
+ }
315
+ if (decoded === value) return value;
316
+ const redactedDecoded = redactSensitiveUrlLikeStringAtDepth(decoded, depth + 1);
317
+ if (redactedDecoded.value !== decoded || redactedDecoded.parsedWholeUrl) return redactedDecoded.value === decoded ? value : redactedDecoded.value;
318
+ return hasUnresolvedEmbeddedUrlUserInfo(decoded) ? "***" : value;
319
+ }
320
+ function redactNestedUrlValue(value, depth) {
321
+ if (!looksLikeNestedUrlValue(value)) return value;
322
+ if (depth > MAX_NESTED_URL_REDACTION_DEPTH) return "***";
323
+ const direct = redactSensitiveUrlLikeStringAtDepth(value, depth);
324
+ if (direct.value !== value) return direct.value;
325
+ if (hasUnresolvedEmbeddedUrlUserInfo(value)) return "***";
326
+ if (direct.parsedWholeUrl) return value;
327
+ let decoded;
328
+ try {
329
+ decoded = decodeURIComponent(value);
330
+ } catch {
331
+ return "***";
332
+ }
333
+ if (decoded === value || !looksLikeNestedUrlValue(decoded)) return value;
334
+ const redactedDecoded = redactNestedUrlValue(decoded, depth + 1);
335
+ return redactedDecoded === decoded ? value : encodeURIComponent(redactedDecoded);
336
+ }
337
+ /** Redacts sensitive URL-looking substrings even when the full value is not a valid URL. */
338
+ function redactSensitiveUrlLikeString(value) {
339
+ return redactSensitiveUrlLikeStringAtDepth(value, 0).value;
999
340
  }
1000
- import_ipaddr.default.IPv4.parse("198.18.0.0");
341
+ ipaddr.IPv4.parse("198.18.0.0");
1001
342
  function stripIpv6Brackets(value) {
1002
343
  if (value.startsWith("[") && value.endsWith("]")) return value.slice(1, -1);
1003
344
  return value;
@@ -1020,13 +361,13 @@ function normalizeIpParseInput(raw) {
1020
361
  function parseCanonicalIpAddress(raw) {
1021
362
  const normalized = normalizeIpParseInput(raw);
1022
363
  if (!normalized) return;
1023
- return import_ipaddr.default.IPv4.isValidFourPartDecimal(normalized) || import_ipaddr.default.IPv6.isValid(normalized) ? import_ipaddr.default.parse(normalized) : void 0;
364
+ return ipaddr.IPv4.isValidFourPartDecimal(normalized) || ipaddr.IPv6.isValid(normalized) ? ipaddr.parse(normalized) : void 0;
1024
365
  }
1025
366
  /** Normalizes canonical IP literals and maps IPv4-mapped IPv6 addresses to IPv4 text. */
1026
367
  function normalizeIpAddress(raw) {
1027
368
  const parsed = parseCanonicalIpAddress(raw);
1028
369
  if (!parsed) return;
1029
- return normalizeLowercaseStringOrEmpty$1(normalizeIpv4MappedAddress(parsed).toString());
370
+ return normalizeLowercaseStringOrEmpty(normalizeIpv4MappedAddress(parsed).toString());
1030
371
  }
1031
372
  /** True when a canonical IP literal is loopback, including IPv4-mapped IPv6. */
1032
373
  function isLoopbackIpAddress(raw) {
@@ -1035,28 +376,19 @@ function isLoopbackIpAddress(raw) {
1035
376
  return normalizeIpv4MappedAddress(parsed).range() === "loopback";
1036
377
  }
1037
378
  //#endregion
1038
- //#region packages/net-policy/src/url-protocol.ts
1039
- function parseUrl(value) {
1040
- if (value instanceof URL) return value;
1041
- try {
1042
- return new URL(value);
1043
- } catch {
1044
- return null;
1045
- }
1046
- }
1047
- function isWssUrl(value) {
1048
- return parseUrl(value)?.protocol === "wss:";
1049
- }
1050
- //#endregion
1051
379
  //#region packages/gateway-client/src/client-address-utils.ts
1052
- function normalizeLowercaseStringOrEmpty(value) {
380
+ function normalizeGatewayErrorText(value) {
1053
381
  return typeof value === "string" ? value.trim().toLowerCase() : "";
1054
382
  }
1055
383
  function isSensitiveUrlQueryParamName(key) {
1056
384
  return /(?:token|password|secret|key|auth|credential)/iu.test(key);
1057
385
  }
1058
- function normalizeFingerprint(fingerprint) {
1059
- return (fingerprint ?? "").replaceAll(":", "").trim().toLowerCase();
386
+ const SHA256_HEX_FINGERPRINT = /^[a-fA-F0-9]{64}$/u;
387
+ const SHA256_COLON_FINGERPRINT = /^(?:[a-fA-F0-9]{2}:){31}[a-fA-F0-9]{2}$/u;
388
+ function normalizeTlsFingerprint(fingerprint) {
389
+ const value = (fingerprint ?? "").trim().replace(/^sha256:/iu, "");
390
+ if (SHA256_HEX_FINGERPRINT.test(value)) return value.toLowerCase();
391
+ return SHA256_COLON_FINGERPRINT.test(value) ? value.replaceAll(":", "").toLowerCase() : "";
1060
392
  }
1061
393
  function parseHostForAddressChecks(host) {
1062
394
  if (!host) return null;
@@ -1076,34 +408,34 @@ function parseGatewayIpAddress(host) {
1076
408
  return normalized ? parseCanonicalIpAddress(normalized) : void 0;
1077
409
  }
1078
410
  //#endregion
1079
- //#region packages/gateway-client/src/websocket-data.ts
1080
- function rawDataToString(data) {
1081
- if (Array.isArray(data)) return Buffer$1.concat(data).toString("utf8");
1082
- return data instanceof ArrayBuffer ? Buffer$1.from(data).toString("utf8") : data.toString("utf8");
1083
- }
1084
- //#endregion
1085
- //#region packages/gateway-client/src/client.ts
1086
- const DEFAULT_HOST_DEPS = {
1087
- loadOrCreateDeviceIdentity: () => void 0,
1088
- signDevicePayload: () => {
1089
- throw new Error("GatewayClient device signature dependency is not configured");
1090
- },
1091
- publicKeyRawBase64UrlFromPem: () => {
1092
- throw new Error("GatewayClient public key dependency is not configured");
1093
- },
1094
- loadDeviceAuthToken: () => null,
1095
- storeDeviceAuthToken: () => {},
1096
- clearDeviceAuthToken: () => {},
1097
- beforeConnect: () => {},
1098
- registerGatewayLoopbackBypass: () => void 0,
1099
- logDebug: () => {},
1100
- logError: () => {},
1101
- redactForLog: (message) => message,
1102
- normalizeTlsFingerprint: normalizeFingerprint
411
+ //#region packages/gateway-client/src/request-error.ts
412
+ var GatewayClientRequestError = class extends GatewayProtocolRequestError {
413
+ constructor(error) {
414
+ super({
415
+ ...error,
416
+ message: formatConnectErrorMessage({
417
+ message: error.message,
418
+ details: error.details
419
+ })
420
+ });
421
+ this.name = "GatewayClientRequestError";
422
+ }
1103
423
  };
1104
- function resolveHostDeps(overrides) {
1105
- return Object.fromEntries(Object.entries(DEFAULT_HOST_DEPS).map(([key, fallback]) => [key, overrides?.[key] ?? fallback]));
424
+ //#endregion
425
+ //#region packages/net-policy/src/url-protocol.ts
426
+ function parseUrl(value) {
427
+ if (value instanceof URL) return value;
428
+ try {
429
+ return new URL(value);
430
+ } catch {
431
+ return null;
432
+ }
1106
433
  }
434
+ function isWssUrl(value) {
435
+ return parseUrl(value)?.protocol === "wss:";
436
+ }
437
+ //#endregion
438
+ //#region packages/gateway-client/src/websocket-transport.ts
1107
439
  const PRIVATE_OR_LOOPBACK_IPV4_RANGES = /* @__PURE__ */ new Set([
1108
440
  "loopback",
1109
441
  "private",
@@ -1119,19 +451,16 @@ const PRIVATE_OR_LOOPBACK_IPV6_RANGES = /* @__PURE__ */ new Set([
1119
451
  function isPrivateOrLoopbackIpAddress(address) {
1120
452
  return (address.kind() === "ipv4" ? PRIVATE_OR_LOOPBACK_IPV4_RANGES : PRIVATE_OR_LOOPBACK_IPV6_RANGES).has(address.range());
1121
453
  }
1122
- function isLoopbackHost(host) {
454
+ function isGatewayLoopbackHost(host) {
1123
455
  const parsed = parseHostForAddressChecks(host);
1124
- if (!parsed) return false;
1125
- if (parsed.isLocalhost) return true;
1126
- return isLoopbackIpAddress(parsed.unbracketedHost);
456
+ return Boolean(parsed && (parsed.isLocalhost || isLoopbackIpAddress(parsed.unbracketedHost)));
1127
457
  }
1128
458
  function isPrivateOrLoopbackHost(host) {
1129
459
  const parsed = parseHostForAddressChecks(host);
1130
460
  if (!parsed) return false;
1131
461
  if (parsed.isLocalhost) return true;
1132
462
  const address = parseGatewayIpAddress(parsed.unbracketedHost);
1133
- if (!address) return false;
1134
- return isPrivateOrLoopbackIpAddress(address);
463
+ return Boolean(address && isPrivateOrLoopbackIpAddress(address));
1135
464
  }
1136
465
  function isTrustedPlaintextWebSocketHost(hostname) {
1137
466
  if (isPrivateOrLoopbackHost(hostname)) return true;
@@ -1144,7 +473,7 @@ function isSecureWebSocketUrl(rawUrl, options) {
1144
473
  const protocol = url.protocol === "https:" ? "wss:" : url.protocol === "http:" ? "ws:" : url.protocol;
1145
474
  if (protocol === "wss:") return true;
1146
475
  if (protocol !== "ws:") return false;
1147
- if (isLoopbackHost(url.hostname) || isTrustedPlaintextWebSocketHost(url.hostname)) return true;
476
+ if (isGatewayLoopbackHost(url.hostname) || isTrustedPlaintextWebSocketHost(url.hostname)) return true;
1148
477
  if (options?.allowPrivateWs === true) {
1149
478
  const hostForIpCheck = url.hostname.startsWith("[") && url.hostname.endsWith("]") ? url.hostname.slice(1, -1) : url.hostname;
1150
479
  return isPrivateOrLoopbackHost(url.hostname) || parseGatewayIpAddress(hostForIpCheck) === void 0;
@@ -1154,37 +483,119 @@ function isSecureWebSocketUrl(rawUrl, options) {
1154
483
  return false;
1155
484
  }
1156
485
  }
1157
- const DEFAULT_GATEWAY_CLIENT_URL = "ws://127.0.0.1:18789";
1158
- const DEFAULT_CLIENT_VERSION = "0.0.0";
1159
- var GatewayClientRequestError = class extends GatewayProtocolRequestError {
1160
- constructor(error) {
1161
- super({
1162
- ...error,
1163
- message: formatConnectErrorMessage({
1164
- message: error.message,
1165
- details: error.details
1166
- })
1167
- });
1168
- this.name = "GatewayClientRequestError";
486
+ var GatewayWebSocketTransportConfigurationError = class extends Error {};
487
+ function resolveGatewayWebSocketTransport(params) {
488
+ const usesTls = isWssUrl(params.url);
489
+ if (params.tlsFingerprint && !usesTls) throw new GatewayWebSocketTransportConfigurationError("gateway tls fingerprint requires wss:// gateway url");
490
+ const allowPrivateWs = (params.env ?? process.env).OPENCLAW_ALLOW_INSECURE_PRIVATE_WS === "1";
491
+ if (!isSecureWebSocketUrl(params.url, { allowPrivateWs })) {
492
+ let displayHost = params.url;
493
+ try {
494
+ displayHost = new URL(params.url).hostname || params.url;
495
+ } catch {}
496
+ throw new GatewayWebSocketTransportConfigurationError(`SECURITY ERROR: Cannot connect to "${displayHost}" over plaintext ws://. Both credentials and chat data would be exposed to network interception. Use wss:// for remote URLs. Safe defaults: keep gateway.bind=loopback and connect via SSH tunnel (ssh -N -L 18789:127.0.0.1:18789 user@gateway-host), or use Tailscale Serve/Funnel. ` + (allowPrivateWs ? "" : "Break-glass (trusted private networks only): set OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1. ") + "Run `openclaw doctor --fix` for guidance.");
1169
497
  }
498
+ const normalize = params.normalizeTlsFingerprint ?? normalizeTlsFingerprint;
499
+ const expectedFingerprint = params.tlsFingerprint ? normalizeTlsFingerprint(params.tlsFingerprint) : void 0;
500
+ if (params.tlsFingerprint && !expectedFingerprint) throw new GatewayWebSocketTransportConfigurationError("gateway tls fingerprint must be a SHA-256 fingerprint");
501
+ const options = { ...params.options };
502
+ if (usesTls && expectedFingerprint) {
503
+ options.rejectUnauthorized = false;
504
+ options.checkServerIdentity = (_hostValue, cert) => {
505
+ const fingerprintValue = typeof cert === "object" && cert && "fingerprint256" in cert ? cert.fingerprint256 ?? "" : "";
506
+ const canonicalFingerprint = normalizeTlsFingerprint(typeof fingerprintValue === "string" ? fingerprintValue : "");
507
+ const fingerprint = canonicalFingerprint ? normalize(canonicalFingerprint) : "";
508
+ const expected = normalize(expectedFingerprint);
509
+ if (!fingerprint) return /* @__PURE__ */ new Error("Missing server TLS fingerprint");
510
+ if (fingerprint !== expected) return /* @__PURE__ */ new Error("Server TLS fingerprint mismatch");
511
+ };
512
+ }
513
+ return {
514
+ options,
515
+ validateSocket: (socket) => {
516
+ if (!params.tlsFingerprint) return null;
517
+ const expected = expectedFingerprint ? normalize(expectedFingerprint) : "";
518
+ if (!expected) return /* @__PURE__ */ new Error("gateway tls fingerprint missing");
519
+ const rawSocket = socket["_socket"];
520
+ if (!rawSocket || typeof rawSocket.getPeerCertificate !== "function") return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
521
+ const canonicalFingerprint = normalizeTlsFingerprint(rawSocket.getPeerCertificate()?.fingerprint256 ?? "");
522
+ const fingerprint = canonicalFingerprint ? normalize(canonicalFingerprint) : "";
523
+ if (!fingerprint) return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
524
+ return fingerprint === expected ? null : /* @__PURE__ */ new Error("gateway tls fingerprint mismatch");
525
+ }
526
+ };
527
+ }
528
+ //#endregion
529
+ //#region packages/gateway-client/src/client.ts
530
+ const DEFAULT_HOST_DEPS = {
531
+ loadOrCreateDeviceIdentity: () => void 0,
532
+ signDevicePayload: () => {
533
+ throw new Error("GatewayClient device signature dependency is not configured");
534
+ },
535
+ publicKeyRawBase64UrlFromPem: () => {
536
+ throw new Error("GatewayClient public key dependency is not configured");
537
+ },
538
+ loadDeviceAuthToken: () => null,
539
+ storeDeviceAuthToken: () => {},
540
+ clearDeviceAuthToken: () => {},
541
+ beforeConnect: () => {},
542
+ registerGatewayLoopbackBypass: () => void 0,
543
+ logDebug: () => {},
544
+ logError: () => {},
545
+ redactForLog: (message) => message,
546
+ normalizeTlsFingerprint
1170
547
  };
1171
- var GatewayClientRequestTimeoutError = class extends Error {
548
+ function resolveHostDeps(overrides) {
549
+ return Object.fromEntries(Object.entries(DEFAULT_HOST_DEPS).map(([key, fallback]) => [key, overrides?.[key] ?? fallback]));
550
+ }
551
+ const DEFAULT_GATEWAY_CLIENT_URL = "ws://127.0.0.1:18789";
552
+ const DEFAULT_CLIENT_VERSION = "0.0.0";
553
+ const MAX_UPGRADE_ERROR_BODY_BYTES = 2 * 1024;
554
+ const UPGRADE_ERROR_BODY_TIMEOUT_MS = 1e3;
555
+ async function readUpgradeErrorBody(response) {
556
+ return await new Promise((resolve) => {
557
+ const chunks = [];
558
+ let totalBytes = 0;
559
+ let settled = false;
560
+ const finish = () => {
561
+ if (settled) return;
562
+ settled = true;
563
+ clearTimeout(timer);
564
+ response.off("data", onData);
565
+ response.off("end", finish);
566
+ response.off("error", finish);
567
+ response.off("aborted", finish);
568
+ resolve(Buffer.concat(chunks, totalBytes).toString("utf8").replace(/\s+/gu, " ").trim());
569
+ };
570
+ const stop = () => {
571
+ finish();
572
+ response.destroy();
573
+ };
574
+ const onData = (chunk) => {
575
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
576
+ const remaining = MAX_UPGRADE_ERROR_BODY_BYTES - totalBytes;
577
+ if (remaining > 0) {
578
+ const prefix = buffer.subarray(0, remaining);
579
+ chunks.push(prefix);
580
+ totalBytes += prefix.byteLength;
581
+ }
582
+ if (buffer.byteLength >= remaining) stop();
583
+ };
584
+ const timer = setTimeout(stop, UPGRADE_ERROR_BODY_TIMEOUT_MS);
585
+ timer.unref?.();
586
+ response.on("data", onData);
587
+ response.once("end", finish);
588
+ response.once("error", finish);
589
+ response.once("aborted", finish);
590
+ });
591
+ }
592
+ var GatewayClientRequestTimeoutError = class extends GatewayProtocolRequestTimeoutError {
1172
593
  constructor(params) {
1173
- super(`gateway request timeout for ${params.method}`);
594
+ super(params, `gateway request timeout for ${params.method}`);
1174
595
  this.name = "GatewayClientRequestTimeoutError";
1175
- this.method = params.method;
1176
- this.timeoutMs = params.timeoutMs;
1177
- this.requestSent = params.requestSent;
1178
- }
1179
- };
1180
- var GatewayClientTransientPreHelloCloseError = class extends Error {
1181
- constructor() {
1182
- super("gateway transient pre-hello clean close");
1183
- this.name = "GatewayClientTransientPreHelloCloseError";
1184
596
  }
1185
597
  };
1186
- var GatewayClientSocketFactoryConfigurationError = class extends Error {};
1187
- var GatewayClientTransportPolicyError = class extends GatewayClientSocketFactoryConfigurationError {};
598
+ var GatewayClientTransportPolicyError = class extends GatewayWebSocketTransportConfigurationError {};
1188
599
  const GATEWAY_CONNECT_ASSEMBLY_ERROR = Symbol("gateway.connectAssemblyError");
1189
600
  function markGatewayConnectAssemblyError(error) {
1190
601
  Object.defineProperty(error, GATEWAY_CONNECT_ASSEMBLY_ERROR, {
@@ -1206,10 +617,20 @@ function formatGatewayClientErrorForLog(err) {
1206
617
  const FORCE_STOP_TERMINATE_GRACE_MS = 250;
1207
618
  const STOP_AND_WAIT_TIMEOUT_MS = 1e3;
1208
619
  const MAX_SUPPRESSED_TRANSIENT_PRE_HELLO_CLEAN_CLOSES = 1;
620
+ function resolveLegacyNodePlatform(platform) {
621
+ switch (platform) {
622
+ case "macos": return "darwin";
623
+ case "windows": return "win32";
624
+ default: return;
625
+ }
626
+ }
1209
627
  var GatewayClient = class {
1210
628
  constructor(opts) {
1211
629
  this.ws = null;
1212
630
  this.stopped = false;
631
+ this.useLegacyNodeProtocolEnvelope = false;
632
+ this.nodeProtocolTransitionPending = false;
633
+ this.suppressNextHelloCallback = false;
1213
634
  this.pendingDeviceTokenRetry = false;
1214
635
  this.deviceTokenRetryBudgetUsed = false;
1215
636
  this.approvalRuntimeTokenCompatibilityDisabled = false;
@@ -1225,7 +646,7 @@ var GatewayClient = class {
1225
646
  ...opts,
1226
647
  deviceIdentity: opts.deviceIdentity === null ? void 0 : opts.deviceIdentity ?? this.deps.loadOrCreateDeviceIdentity()
1227
648
  };
1228
- this.requestTimeoutMs = typeof opts.requestTimeoutMs === "number" && Number.isFinite(opts.requestTimeoutMs) ? resolveSafeTimeoutDelayMs(opts.requestTimeoutMs, { minMs: 0 }) : DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS;
649
+ this.requestTimeoutMs = typeof opts.requestTimeoutMs === "number" && Number.isFinite(opts.requestTimeoutMs) ? opts.requestTimeoutMs : DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS;
1229
650
  const connectChallengeTimeoutMs = resolveConnectChallengeTimeoutMs(this.opts.connectChallengeTimeoutMs, {
1230
651
  env: this.opts.env,
1231
652
  configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
@@ -1264,7 +685,13 @@ var GatewayClient = class {
1264
685
  };
1265
686
  },
1266
687
  onConnectHello: (hello, context) => this.handleConnectHello(hello, context.plan),
1267
- onHello: (hello) => this.opts.onHelloOk?.(hello),
688
+ onHello: (hello) => {
689
+ if (this.suppressNextHelloCallback) {
690
+ this.suppressNextHelloCallback = false;
691
+ return;
692
+ }
693
+ this.opts.onHelloOk?.(hello);
694
+ },
1268
695
  onConnectFailure: (error, context) => this.handleConnectRequestFailure(error, context.plan),
1269
696
  resolveClose: (context) => this.resolveClose(context),
1270
697
  onClose: (context, decision) => {
@@ -1294,7 +721,7 @@ var GatewayClient = class {
1294
721
  maxMs: 3e4
1295
722
  },
1296
723
  requestTimeoutMs: this.requestTimeoutMs,
1297
- shouldRetrySocketFactoryError: (error) => !(error instanceof GatewayClientSocketFactoryConfigurationError) && !(error instanceof SyntaxError) && !(error instanceof TypeError) && !(error instanceof RangeError),
724
+ shouldRetrySocketFactoryError: (error) => !(error instanceof GatewayWebSocketTransportConfigurationError) && !(error instanceof SyntaxError) && !(error instanceof TypeError) && !(error instanceof RangeError),
1298
725
  rethrowSocketFactoryError: (error) => error instanceof GatewayClientTransportPolicyError
1299
726
  });
1300
727
  }
@@ -1310,7 +737,9 @@ var GatewayClient = class {
1310
737
  this.opts = {
1311
738
  ...this.opts,
1312
739
  caps: [...manifest.caps],
1313
- commands: [...manifest.commands]
740
+ commands: [...manifest.commands],
741
+ computerUse: manifest.computerUse === void 0 ? void 0 : structuredClone(manifest.computerUse),
742
+ workerRuns: manifest.workerRuns ? structuredClone(manifest.workerRuns) : void 0
1314
743
  };
1315
744
  if (!this.stopped) this.protocol.closeSocket(1012, "node manifest changed");
1316
745
  }
@@ -1320,36 +749,29 @@ var GatewayClient = class {
1320
749
  }
1321
750
  createSocket(handlers) {
1322
751
  const url = this.opts.url ?? DEFAULT_GATEWAY_CLIENT_URL;
1323
- const usesTls = isWssUrl(url);
1324
- if (this.opts.tlsFingerprint && !usesTls) throw new GatewayClientSocketFactoryConfigurationError("gateway tls fingerprint requires wss:// gateway url");
1325
- const allowPrivateWs = (this.opts.env ?? process.env).OPENCLAW_ALLOW_INSECURE_PRIVATE_WS === "1";
1326
- if (!isSecureWebSocketUrl(url, { allowPrivateWs })) {
1327
- let displayHost = url;
1328
- try {
1329
- displayHost = new URL(url).hostname || url;
1330
- } catch {}
1331
- throw new GatewayClientSocketFactoryConfigurationError(`SECURITY ERROR: Cannot connect to "${displayHost}" over plaintext ws://. Both credentials and chat data would be exposed to network interception. Use wss:// for remote URLs. Safe defaults: keep gateway.bind=loopback and connect via SSH tunnel (ssh -N -L 18789:127.0.0.1:18789 user@gateway-host), or use Tailscale Serve/Funnel. ` + (allowPrivateWs ? "" : "Break-glass (trusted private networks only): set OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1. ") + "Run `openclaw doctor --fix` for guidance.");
1332
- }
752
+ const configuredEdgeAuthHeaders = this.opts.edgeAuthHeaders;
753
+ const edgeAuthHeaders = configuredEdgeAuthHeaders && Object.keys(configuredEdgeAuthHeaders).length > 0 ? configuredEdgeAuthHeaders : void 0;
754
+ if (edgeAuthHeaders && new URL(url).protocol !== "wss:") throw new GatewayWebSocketTransportConfigurationError("edge auth headers require a wss:// Gateway URL");
755
+ const handshakeTimeoutMs = resolvePreauthHandshakeTimeoutMs({
756
+ env: this.opts.env,
757
+ configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
758
+ });
759
+ const transport = resolveGatewayWebSocketTransport({
760
+ url,
761
+ tlsFingerprint: this.opts.tlsFingerprint,
762
+ env: this.opts.env,
763
+ normalizeTlsFingerprint: this.deps.normalizeTlsFingerprint,
764
+ options: {
765
+ maxPayload: 25 * 1024 * 1024,
766
+ handshakeTimeout: handshakeTimeoutMs,
767
+ ...this.opts.origin ? { origin: this.opts.origin } : {},
768
+ ...edgeAuthHeaders ? {
769
+ followRedirects: false,
770
+ headers: edgeAuthHeaders
771
+ } : {}
772
+ }
773
+ });
1333
774
  this.deps.beforeConnect();
1334
- const wsOptions = {
1335
- maxPayload: 25 * 1024 * 1024,
1336
- handshakeTimeout: resolvePreauthHandshakeTimeoutMs({
1337
- env: this.opts.env,
1338
- configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
1339
- }),
1340
- ...this.opts.origin ? { origin: this.opts.origin } : {}
1341
- };
1342
- if (usesTls && this.opts.tlsFingerprint) {
1343
- wsOptions.rejectUnauthorized = false;
1344
- wsOptions.checkServerIdentity = (_hostValue, cert) => {
1345
- const fingerprintValue = typeof cert === "object" && cert && "fingerprint256" in cert ? cert.fingerprint256 ?? "" : "";
1346
- const fingerprint = this.deps.normalizeTlsFingerprint(typeof fingerprintValue === "string" ? fingerprintValue : "");
1347
- const expected = this.deps.normalizeTlsFingerprint(this.opts.tlsFingerprint ?? "");
1348
- if (!expected) return;
1349
- if (!fingerprint) return /* @__PURE__ */ new Error("Missing server TLS fingerprint");
1350
- if (fingerprint !== expected) return /* @__PURE__ */ new Error("Server TLS fingerprint mismatch");
1351
- };
1352
- }
1353
775
  let ws;
1354
776
  let unregisterGatewayLoopbackBypass;
1355
777
  try {
@@ -1358,7 +780,7 @@ var GatewayClient = class {
1358
780
  throw new GatewayClientTransportPolicyError(error instanceof Error ? error.message : String(error));
1359
781
  }
1360
782
  try {
1361
- ws = new WebSocket(url, wsOptions);
783
+ ws = new WebSocket(url, transport.options);
1362
784
  ws.binaryType = "nodebuffer";
1363
785
  } catch (error) {
1364
786
  throw error instanceof Error ? error : new Error(String(error));
@@ -1367,15 +789,14 @@ var GatewayClient = class {
1367
789
  }
1368
790
  this.ws = ws;
1369
791
  this.transportValidated = false;
792
+ let upgradeError;
1370
793
  ws.on("open", () => {
1371
794
  handlers.open();
1372
- if (usesTls && this.opts.tlsFingerprint) {
1373
- const tlsError = this.validateTlsFingerprint();
1374
- if (tlsError) {
1375
- handlers.error(tlsError);
1376
- ws.close(1008, tlsError.message);
1377
- return;
1378
- }
795
+ const tlsError = transport.validateSocket(ws);
796
+ if (tlsError) {
797
+ handlers.error(tlsError);
798
+ ws.close(1008, tlsError.message);
799
+ return;
1379
800
  }
1380
801
  this.transportValidated = true;
1381
802
  });
@@ -1386,7 +807,28 @@ var GatewayClient = class {
1386
807
  this.resolvePendingStop(ws);
1387
808
  handlers.close(code, reasonText);
1388
809
  });
810
+ ws.on("unexpected-response", (request, response) => {
811
+ readUpgradeErrorBody(response).then((body) => {
812
+ const statusCode = response.statusCode;
813
+ const rawLocation = response.headers.location;
814
+ const location = rawLocation ? redactSensitiveUrlLikeString(Array.isArray(rawLocation) ? rawLocation[0] ?? "" : rawLocation) : void 0;
815
+ upgradeError = new GatewayClientRequestError({
816
+ code: "UNAVAILABLE",
817
+ message: `gateway rejected websocket upgrade (HTTP ${statusCode ?? "unknown"})${body ? `: ${body}` : ""}`,
818
+ retryable: true,
819
+ details: {
820
+ reason: "websocket-upgrade-rejected",
821
+ ...statusCode === void 0 ? {} : { httpStatus: statusCode },
822
+ ...location ? { location } : {}
823
+ }
824
+ });
825
+ handlers.error(upgradeError);
826
+ request.destroy();
827
+ ws.close();
828
+ });
829
+ });
1389
830
  ws.on("error", (err) => {
831
+ if (upgradeError) return;
1390
832
  this.logDebug(`gateway client error: ${formatGatewayClientErrorForLog(err)}`);
1391
833
  handlers.error(err instanceof Error ? err : new Error(String(err)));
1392
834
  });
@@ -1486,22 +928,34 @@ var GatewayClient = class {
1486
928
  storedScopes,
1487
929
  defaultScopes: ["operator.admin"]
1488
930
  });
1489
- const platform = this.opts.platform ?? process.platform;
931
+ const clientMode = this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND;
932
+ const clientId = this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT;
933
+ const isBuiltInNodeHost = role === "node" && clientMode === GATEWAY_CLIENT_MODES.NODE && clientId === GATEWAY_CLIENT_NAMES.NODE_HOST;
934
+ const negotiatesNodeProtocol = this.shouldNegotiateLegacyNodeProtocol();
935
+ const useLegacyNodeProtocolEnvelope = isBuiltInNodeHost && (this.useLegacyNodeProtocolEnvelope || this.opts.maxProtocol === MIN_NODE_PROTOCOL_VERSION && (this.opts.minProtocol ?? MIN_NODE_PROTOCOL_VERSION) <= MIN_NODE_PROTOCOL_VERSION);
936
+ const minProtocol = useLegacyNodeProtocolEnvelope ? MIN_NODE_PROTOCOL_VERSION : negotiatesNodeProtocol ? PROTOCOL_VERSION : this.opts.minProtocol ?? (clientMode === GATEWAY_CLIENT_MODES.PROBE ? MIN_PROBE_PROTOCOL_VERSION : role === "node" && clientMode === GATEWAY_CLIENT_MODES.NODE ? MIN_NODE_PROTOCOL_VERSION : MIN_CLIENT_PROTOCOL_VERSION);
937
+ const maxProtocol = useLegacyNodeProtocolEnvelope ? MIN_NODE_PROTOCOL_VERSION : negotiatesNodeProtocol ? PROTOCOL_VERSION : this.opts.maxProtocol ?? PROTOCOL_VERSION;
938
+ const configuredPlatform = this.opts.platform ?? process.platform;
939
+ const platform = useLegacyNodeProtocolEnvelope ? resolveLegacyNodePlatform(configuredPlatform) ?? configuredPlatform : configuredPlatform;
940
+ const deviceFamily = useLegacyNodeProtocolEnvelope ? void 0 : this.opts.deviceFamily;
1490
941
  return {
1491
942
  params: {
1492
- minProtocol: this.opts.minProtocol ?? MIN_CLIENT_PROTOCOL_VERSION,
1493
- maxProtocol: this.opts.maxProtocol ?? PROTOCOL_VERSION,
943
+ minProtocol,
944
+ maxProtocol,
1494
945
  client: {
1495
- id: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
946
+ id: clientId,
1496
947
  displayName: this.opts.clientDisplayName,
1497
948
  version: this.opts.clientVersion ?? DEFAULT_CLIENT_VERSION,
949
+ buildId: this.opts.clientBuildId,
1498
950
  platform,
1499
- deviceFamily: this.opts.deviceFamily,
1500
- mode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND,
951
+ deviceFamily,
952
+ mode: clientMode,
1501
953
  instanceId: this.opts.instanceId
1502
954
  },
1503
955
  caps: Array.isArray(this.opts.caps) ? this.opts.caps : [],
1504
956
  commands: Array.isArray(this.opts.commands) ? this.opts.commands : void 0,
957
+ computerUse: useLegacyNodeProtocolEnvelope ? void 0 : this.opts.computerUse,
958
+ workerRuns: useLegacyNodeProtocolEnvelope ? void 0 : this.opts.workerRuns,
1505
959
  permissions: this.opts.permissions && typeof this.opts.permissions === "object" ? this.opts.permissions : void 0,
1506
960
  pathEnv: this.opts.pathEnv,
1507
961
  auth,
@@ -1513,30 +967,47 @@ var GatewayClient = class {
1513
967
  scopes,
1514
968
  signatureToken,
1515
969
  signedAtMs,
1516
- platform
970
+ platform,
971
+ deviceFamily,
972
+ clientMode
1517
973
  })
1518
974
  },
1519
975
  authApprovalRuntimeToken,
1520
976
  authAgentRuntimeIdentityToken,
1521
977
  resolvedDeviceToken,
978
+ storedScopes,
1522
979
  storedToken,
1523
980
  usingStoredDeviceToken
1524
981
  };
1525
982
  }
983
+ shouldNegotiateLegacyNodeProtocol() {
984
+ if (this.opts.role !== "node" || this.opts.mode !== GATEWAY_CLIENT_MODES.NODE || this.opts.clientName !== GATEWAY_CLIENT_NAMES.NODE_HOST) return false;
985
+ return (this.opts.minProtocol ?? MIN_NODE_PROTOCOL_VERSION) === MIN_NODE_PROTOCOL_VERSION && (this.opts.maxProtocol ?? PROTOCOL_VERSION) === PROTOCOL_VERSION;
986
+ }
987
+ shouldRetryWithLegacyNodeProtocol(error) {
988
+ if (this.useLegacyNodeProtocolEnvelope || !this.shouldNegotiateLegacyNodeProtocol() || !(error instanceof GatewayClientRequestError)) return false;
989
+ const detailCode = readConnectErrorDetailCode(error.details);
990
+ return error.details?.expectedProtocol === MIN_NODE_PROTOCOL_VERSION && (detailCode === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || normalizeGatewayErrorText(error.message).includes("protocol mismatch"));
991
+ }
992
+ shouldRetryWithCurrentNodeProtocol(error) {
993
+ if (!this.useLegacyNodeProtocolEnvelope || !this.shouldNegotiateLegacyNodeProtocol() || !(error instanceof GatewayClientRequestError)) return false;
994
+ const detailCode = readConnectErrorDetailCode(error.details);
995
+ return error.details?.expectedProtocol === PROTOCOL_VERSION && (detailCode === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || normalizeGatewayErrorText(error.message).includes("protocol mismatch"));
996
+ }
1526
997
  buildDeviceConnectParams(params) {
1527
998
  if (!this.opts.deviceIdentity) return;
1528
- const { nonce, role, scopes, signatureToken, signedAtMs, platform } = params;
999
+ const { nonce, role, scopes, signatureToken, signedAtMs, platform, deviceFamily, clientMode } = params;
1529
1000
  const payload = buildDeviceAuthPayloadV3({
1530
1001
  deviceId: this.opts.deviceIdentity.deviceId,
1531
1002
  clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
1532
- clientMode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND,
1003
+ clientMode,
1533
1004
  role,
1534
1005
  scopes,
1535
1006
  signedAtMs,
1536
1007
  token: signatureToken ?? null,
1537
1008
  nonce,
1538
1009
  platform,
1539
- deviceFamily: this.opts.deviceFamily
1010
+ deviceFamily
1540
1011
  });
1541
1012
  const signature = this.deps.signDevicePayload(this.opts.deviceIdentity.privateKeyPem, payload);
1542
1013
  return {
@@ -1548,24 +1019,67 @@ var GatewayClient = class {
1548
1019
  };
1549
1020
  }
1550
1021
  handleConnectHello(helloOk, assembled) {
1022
+ const reconnectWithCurrentNodeProtocol = this.useLegacyNodeProtocolEnvelope && this.shouldNegotiateLegacyNodeProtocol() && helloOk.protocol > MIN_NODE_PROTOCOL_VERSION;
1023
+ if (reconnectWithCurrentNodeProtocol) this.useLegacyNodeProtocolEnvelope = false;
1024
+ this.nodeProtocolTransitionPending = false;
1551
1025
  this.pendingDeviceTokenRetry = false;
1552
1026
  this.deviceTokenRetryBudgetUsed = false;
1553
1027
  this.suppressedTransientPreHelloCleanCloses = 0;
1554
1028
  const role = this.opts.role ?? "operator";
1555
1029
  const authInfo = helloOk.auth;
1556
- if (authInfo?.deviceToken && this.opts.deviceIdentity) this.deps.storeDeviceAuthToken({
1557
- deviceId: this.opts.deviceIdentity.deviceId,
1558
- role: authInfo.role ?? role,
1559
- token: authInfo.deviceToken,
1560
- scopes: authInfo.scopes ?? [],
1561
- env: this.opts.env
1562
- });
1030
+ if (authInfo?.deviceToken && this.opts.deviceIdentity) {
1031
+ const tokenRole = authInfo.role ?? role;
1032
+ const scopes = tokenRole === role && authInfo.deviceToken === assembled.storedToken ? assembled.storedScopes ?? authInfo.scopes ?? [] : authInfo.scopes ?? [];
1033
+ this.deps.storeDeviceAuthToken({
1034
+ deviceId: this.opts.deviceIdentity.deviceId,
1035
+ role: tokenRole,
1036
+ token: authInfo.deviceToken,
1037
+ scopes,
1038
+ env: this.opts.env
1039
+ });
1040
+ }
1041
+ if (this.opts.preferBootstrapToken) {
1042
+ this.opts.token = void 0;
1043
+ this.opts.bootstrapToken = void 0;
1044
+ this.opts.password = void 0;
1045
+ this.opts.preferBootstrapToken = false;
1046
+ }
1563
1047
  this.tickIntervalMs = typeof helloOk.policy?.tickIntervalMs === "number" ? helloOk.policy.tickIntervalMs : 3e4;
1048
+ if (reconnectWithCurrentNodeProtocol) {
1049
+ this.suppressNextHelloCallback = true;
1050
+ this.protocol.resetReconnectBackoff(250);
1051
+ this.protocol.closeSocket(1012, "gateway protocol upgraded");
1052
+ return;
1053
+ }
1564
1054
  this.lastTick = Date.now();
1565
1055
  this.startTickWatch();
1566
1056
  }
1567
1057
  handleConnectRequestFailure(error, assembled) {
1058
+ if (this.shouldRetryWithCurrentNodeProtocol(error)) {
1059
+ const resetBackoff = !this.nodeProtocolTransitionPending;
1060
+ this.useLegacyNodeProtocolEnvelope = false;
1061
+ this.nodeProtocolTransitionPending = true;
1062
+ if (resetBackoff) this.protocol.resetReconnectBackoff(250);
1063
+ this.logDebug("gateway rejected protocol v3; retrying node host with protocol v4");
1064
+ return {
1065
+ closeCode: 1008,
1066
+ closeReason: "connect retry"
1067
+ };
1068
+ }
1069
+ if (this.shouldRetryWithLegacyNodeProtocol(error)) {
1070
+ const resetBackoff = !this.nodeProtocolTransitionPending;
1071
+ this.useLegacyNodeProtocolEnvelope = true;
1072
+ this.nodeProtocolTransitionPending = true;
1073
+ if (resetBackoff) this.protocol.resetReconnectBackoff(250);
1074
+ this.logDebug("gateway rejected protocol v4; retrying node host with protocol v3");
1075
+ return {
1076
+ closeCode: 1008,
1077
+ closeReason: "connect retry"
1078
+ };
1079
+ }
1080
+ this.nodeProtocolTransitionPending = false;
1568
1081
  const role = this.opts.role ?? "operator";
1082
+ const detailCode = error instanceof GatewayClientRequestError ? readConnectErrorDetailCode(error.details) : null;
1569
1083
  const shouldRetryWithDeviceToken = shouldRetryGatewayWithDeviceToken({
1570
1084
  retryBudgetUsed: this.deviceTokenRetryBudgetUsed,
1571
1085
  currentDeviceToken: assembled.resolvedDeviceToken,
@@ -1574,7 +1088,7 @@ var GatewayClient = class {
1574
1088
  trustedEndpoint: this.isTrustedDeviceRetryEndpoint(),
1575
1089
  errorDetails: error instanceof GatewayClientRequestError ? error.details : void 0
1576
1090
  });
1577
- if (this.opts.deviceIdentity && assembled.usingStoredDeviceToken && error instanceof GatewayClientRequestError && readConnectErrorDetailCode(error.details) === ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH) {
1091
+ if (this.opts.deviceIdentity && assembled.usingStoredDeviceToken && detailCode === ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH) {
1578
1092
  const deviceId = this.opts.deviceIdentity.deviceId;
1579
1093
  try {
1580
1094
  this.deps.clearDeviceAuthToken({
@@ -1630,7 +1144,7 @@ var GatewayClient = class {
1630
1144
  }
1631
1145
  this.notifyConnectError(error);
1632
1146
  const message = `gateway connect failed: ${formatGatewayClientErrorForLog(error)}`;
1633
- if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE || isGatewayClientStoppedError(error)) this.logDebug(message);
1147
+ if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE || isGatewayClientStoppedError(error) || detailCode === ConnectErrorDetailCodes.AUTH_RATE_LIMITED) this.logDebug(message);
1634
1148
  else this.logError(message);
1635
1149
  return {
1636
1150
  closeCode: 1008,
@@ -1643,7 +1157,7 @@ var GatewayClient = class {
1643
1157
  const details = context.connectFailure?.error instanceof GatewayClientRequestError ? context.connectFailure.error.details : void 0;
1644
1158
  if (context.code === 1013 && context.connectFailure?.reconnectDelayMs !== void 0) return {
1645
1159
  retry: true,
1646
- notify: false,
1160
+ notify: this.opts.notifyOnStartupRetry === true,
1647
1161
  reconnectDelayMs: context.connectFailure.reconnectDelayMs
1648
1162
  };
1649
1163
  if (info.transientPreHelloCleanClose && this.suppressedTransientPreHelloCleanCloses < MAX_SUPPRESSED_TRANSIENT_PRE_HELLO_CLEAN_CLOSES) {
@@ -1651,7 +1165,7 @@ var GatewayClient = class {
1651
1165
  return {
1652
1166
  retry: true,
1653
1167
  notify: true,
1654
- pendingError: new GatewayClientTransientPreHelloCloseError()
1168
+ pendingError: /* @__PURE__ */ new Error("gateway transient pre-hello clean close")
1655
1169
  };
1656
1170
  }
1657
1171
  if (info.transientPreHelloCleanClose || context.connectRequestSent && !context.helloReceived && !context.connectFailure) {
@@ -1664,6 +1178,7 @@ var GatewayClient = class {
1664
1178
  details,
1665
1179
  deviceTokenRetryPending: this.pendingDeviceTokenRetry,
1666
1180
  tokenMismatchIsTerminal: true,
1181
+ protocolMismatchIsTerminal: !this.nodeProtocolTransitionPending,
1667
1182
  clientVersionMismatchIsTerminal: true
1668
1183
  })) {
1669
1184
  this.notifyReconnectPaused({
@@ -1687,11 +1202,13 @@ var GatewayClient = class {
1687
1202
  phase: context.helloReceived ? "post-hello" : "pre-hello",
1688
1203
  socketOpened: context.socketOpened,
1689
1204
  transportValidated: this.transportValidated,
1690
- transientPreHelloCleanClose: !context.helloReceived && context.code === 1e3 && context.reason === ""
1205
+ connectRequestSent: context.connectRequestSent,
1206
+ transientPreHelloCleanClose: !context.helloReceived && context.code === 1e3 && context.reason === "",
1207
+ ...context.connectFailure?.error ? { connectError: context.connectFailure.error } : {}
1691
1208
  };
1692
1209
  }
1693
1210
  clearStaleDeviceTokenForClose(code, reason) {
1694
- if (code !== 1008 || !normalizeLowercaseStringOrEmpty(reason).includes("device token mismatch") || this.opts.token || this.opts.password || !this.opts.deviceIdentity) return;
1211
+ if (code !== 1008 || !normalizeGatewayErrorText(reason).includes("device token mismatch") || this.opts.token || this.opts.password || !this.opts.deviceIdentity) return;
1695
1212
  const deviceId = this.opts.deviceIdentity.deviceId;
1696
1213
  const role = this.opts.role ?? "operator";
1697
1214
  try {
@@ -1724,14 +1241,14 @@ var GatewayClient = class {
1724
1241
  if (!params.authApprovalRuntimeToken) return false;
1725
1242
  if (!(params.error instanceof GatewayClientRequestError)) return false;
1726
1243
  if (params.error.gatewayCode !== "INVALID_REQUEST") return false;
1727
- const message = normalizeLowercaseStringOrEmpty(params.error.message);
1244
+ const message = normalizeGatewayErrorText(params.error.message);
1728
1245
  return message.includes("invalid connect params") && message.includes("approvalruntimetoken");
1729
1246
  }
1730
1247
  shouldFailClosedForUnsupportedAgentRuntimeIdentity(params) {
1731
1248
  if (!params.authAgentRuntimeIdentityToken) return false;
1732
1249
  if (!(params.error instanceof GatewayClientRequestError)) return false;
1733
1250
  if (params.error.gatewayCode !== "INVALID_REQUEST") return false;
1734
- const message = normalizeLowercaseStringOrEmpty(params.error.message);
1251
+ const message = normalizeGatewayErrorText(params.error.message);
1735
1252
  return message.includes("invalid connect params") && message.includes("agentruntimeidentitytoken");
1736
1253
  }
1737
1254
  isTrustedDeviceRetryEndpoint() {
@@ -1739,7 +1256,7 @@ var GatewayClient = class {
1739
1256
  try {
1740
1257
  const parsed = new URL(rawUrl);
1741
1258
  const protocol = parsed.protocol === "https:" ? "wss:" : parsed.protocol === "http:" ? "ws:" : parsed.protocol;
1742
- if (isLoopbackHost(parsed.hostname)) return true;
1259
+ if (isGatewayLoopbackHost(parsed.hostname)) return true;
1743
1260
  return protocol === "wss:" && Boolean(this.opts.tlsFingerprint?.trim());
1744
1261
  } catch {
1745
1262
  return false;
@@ -1754,6 +1271,7 @@ var GatewayClient = class {
1754
1271
  return selectGatewayConnectAuth({
1755
1272
  token: this.opts.token,
1756
1273
  bootstrapToken: this.opts.bootstrapToken,
1274
+ preferBootstrapToken: this.opts.preferBootstrapToken,
1757
1275
  deviceToken: this.opts.deviceToken,
1758
1276
  password: this.opts.password,
1759
1277
  approvalRuntimeToken: this.approvalRuntimeTokenCompatibilityDisabled ? void 0 : this.opts.approvalRuntimeToken,
@@ -1778,21 +1296,9 @@ var GatewayClient = class {
1778
1296
  if (gap > (typeof rawTimeoutMs === "number" && Number.isFinite(rawTimeoutMs) ? Math.max(1, rawTimeoutMs) : this.tickIntervalMs * 2)) this.protocol.closeSocket(4e3, "tick timeout");
1779
1297
  }, interval);
1780
1298
  }
1781
- validateTlsFingerprint() {
1782
- if (!this.opts.tlsFingerprint || !this.ws) return null;
1783
- const expected = this.deps.normalizeTlsFingerprint(this.opts.tlsFingerprint);
1784
- if (!expected) return /* @__PURE__ */ new Error("gateway tls fingerprint missing");
1785
- const socket = this.ws["_socket"];
1786
- if (!socket || typeof socket.getPeerCertificate !== "function") return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
1787
- const cert = socket.getPeerCertificate();
1788
- const fingerprint = this.deps.normalizeTlsFingerprint(cert?.fingerprint256 ?? "");
1789
- if (!fingerprint) return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
1790
- if (fingerprint !== expected) return /* @__PURE__ */ new Error("gateway tls fingerprint mismatch");
1791
- return null;
1792
- }
1793
1299
  async request(method, params, opts) {
1794
1300
  const expectFinal = opts?.expectFinal === true;
1795
- const timeoutMs = opts?.timeoutMs === null ? null : typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? resolveSafeTimeoutDelayMs(opts.timeoutMs, { minMs: 0 }) : expectFinal ? null : this.requestTimeoutMs;
1301
+ const timeoutMs = opts?.timeoutMs === null ? null : typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : expectFinal ? null : this.requestTimeoutMs;
1796
1302
  return this.protocol.request(method, params, {
1797
1303
  expectFinal,
1798
1304
  timeoutMs,
@@ -1808,4 +1314,15 @@ function createGatewayRequestAbortError(method) {
1808
1314
  return err;
1809
1315
  }
1810
1316
  //#endregion
1811
- export { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, GatewayBrowserDeviceAuthLifecycle, GatewayClient, GatewayClientRequestError, GatewayClientRequestTimeoutError, GatewaySessionMessageSubscriptionCoordinator, MAX_CONNECT_CHALLENGE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_DELAY_MS, MIN_CONNECT_CHALLENGE_TIMEOUT_MS, addSafeTimeoutDelayGraceMs, buildDeviceAuthPayload, buildDeviceAuthPayloadV3, buildGatewayConnectAuth, clampConnectChallengeTimeoutMs, clearGatewayConnectTimeout, createSessionProjection, getConnectChallengeTimeoutMsFromEnv, getGatewaySessionMessageSubscriptionCoordinator, hasSessionProjectionAcceptedFinal, isGatewayConnectAssemblyError, isLocallyOptimisticSessionMessage, normalizeDeviceMetadataForAuth, normalizeSessionProjectionRunId, projectLiveSessionMessage, readSessionMessageIdentity, readSessionMessageSequence, reconcileSessionProjectionSnapshot, reduceSessionProjection, reduceSessionProjectionRunEvent, releaseGatewaySessionMessageSubscription, resetGatewaySessionMessageSubscriptionCoordinator, resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs, resolveGatewayConnectScopes, resolvePreauthHandshakeTimeoutMs, resolveSafeTimeoutDelayMs, selectGatewayConnectAuth, shouldRetryGatewayWithDeviceToken, startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait, startGatewayConnectTimeout, waitForEventLoopReady };
1317
+ //#region packages/gateway-client/src/cloudflare-access.ts
1318
+ const CF_ACCESS_CLIENT_ID_HEADER = "CF-Access-Client-Id";
1319
+ const CF_ACCESS_CLIENT_SECRET_HEADER = "CF-Access-Client-Secret";
1320
+ /** Build only the two headers Cloudflare Access defines for service-token auth. */
1321
+ function buildCloudflareAccessHeaders(credentials) {
1322
+ return {
1323
+ [CF_ACCESS_CLIENT_ID_HEADER]: credentials.clientId,
1324
+ [CF_ACCESS_CLIENT_SECRET_HEADER]: credentials.clientSecret
1325
+ };
1326
+ }
1327
+ //#endregion
1328
+ export { CF_ACCESS_CLIENT_ID_HEADER, CF_ACCESS_CLIENT_SECRET_HEADER, DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, GatewayBrowserDeviceAuthLifecycle, GatewayClient, GatewayClientRequestError, GatewayClientRequestTimeoutError, GatewayScopeUpgrade, GatewaySessionMessageSubscriptionCoordinator, MAX_CONNECT_CHALLENGE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_DELAY_MS, MIN_CONNECT_CHALLENGE_TIMEOUT_MS, addSafeTimeoutDelayGraceMs, buildCloudflareAccessHeaders, buildDeviceAuthPayload, buildDeviceAuthPayloadV3, buildGatewayConnectAuth, clampConnectChallengeTimeoutMs, clearGatewayConnectTimeout, createSessionProjection, gatewayCredentialScope, gatewayOriginScope, getConnectChallengeTimeoutMsFromEnv, getGatewaySessionMessageSubscriptionCoordinator, hasSessionProjectionAcceptedFinal, isGatewayConnectAssemblyError, isLocallyOptimisticSessionMessage, normalizeDeviceMetadataForAuth, normalizeSessionProjectionRunId, projectLiveSessionMessage, readSessionMessageIdentity, readSessionMessageSequence, reconcileSessionProjectionSnapshot, reduceSessionProjection, reduceSessionProjectionRunEvent, releaseGatewaySessionMessageSubscription, resetGatewaySessionMessageSubscriptionCoordinator, resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs, resolveGatewayConnectScopes, resolvePreauthHandshakeTimeoutMs, resolveSafeTimeoutDelayMs, selectGatewayConnectAuth, shouldRetryGatewayWithDeviceToken, startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait, startGatewayConnectTimeout, waitForEventLoopReady };