kinetex 1.0.0 → 1.2.0

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.
Files changed (61) hide show
  1. package/README.md +104 -16
  2. package/dist/browser/kinetex.esm.js +18 -18
  3. package/dist/browser/kinetex.js +581 -275
  4. package/dist/browser/kinetex.min.js +18 -18
  5. package/dist/cjs/client.js +180 -11
  6. package/dist/cjs/cookie-store.js +10 -0
  7. package/dist/cjs/core.js +53 -27
  8. package/dist/cjs/graphql.js +37 -8
  9. package/dist/cjs/interceptors.js +22 -11
  10. package/dist/cjs/logging.js +10 -2
  11. package/dist/cjs/mod.js +1 -1
  12. package/dist/cjs/pagination.js +6 -1
  13. package/dist/cjs/progress.js +13 -6
  14. package/dist/cjs/response.js +8 -6
  15. package/dist/cjs/sse.js +5 -2
  16. package/dist/cjs/utils.js +250 -57
  17. package/dist/cjs/ws.js +16 -2
  18. package/dist/esm/client.js +180 -11
  19. package/dist/esm/client.js.map +1 -1
  20. package/dist/esm/cookie-store.js +10 -0
  21. package/dist/esm/cookie-store.js.map +1 -1
  22. package/dist/esm/core.js +53 -27
  23. package/dist/esm/core.js.map +1 -1
  24. package/dist/esm/graphql.js +37 -8
  25. package/dist/esm/graphql.js.map +1 -1
  26. package/dist/esm/interceptors.js +22 -11
  27. package/dist/esm/interceptors.js.map +1 -1
  28. package/dist/esm/logging.js +10 -2
  29. package/dist/esm/logging.js.map +1 -1
  30. package/dist/esm/mod.js +1 -1
  31. package/dist/esm/mod.js.map +1 -1
  32. package/dist/esm/pagination.js +6 -1
  33. package/dist/esm/pagination.js.map +1 -1
  34. package/dist/esm/progress.js +13 -6
  35. package/dist/esm/progress.js.map +1 -1
  36. package/dist/esm/response.js +8 -6
  37. package/dist/esm/response.js.map +1 -1
  38. package/dist/esm/sse.js +5 -2
  39. package/dist/esm/sse.js.map +1 -1
  40. package/dist/esm/utils.js +250 -57
  41. package/dist/esm/utils.js.map +1 -1
  42. package/dist/esm/ws.js +16 -2
  43. package/dist/esm/ws.js.map +1 -1
  44. package/dist/types/client.d.ts.map +1 -1
  45. package/dist/types/cookie-store.d.ts.map +1 -1
  46. package/dist/types/core.d.ts +4 -0
  47. package/dist/types/core.d.ts.map +1 -1
  48. package/dist/types/graphql.d.ts.map +1 -1
  49. package/dist/types/interceptors.d.ts.map +1 -1
  50. package/dist/types/logging.d.ts.map +1 -1
  51. package/dist/types/mod.d.ts +1 -1
  52. package/dist/types/mod.d.ts.map +1 -1
  53. package/dist/types/pagination.d.ts.map +1 -1
  54. package/dist/types/progress.d.ts.map +1 -1
  55. package/dist/types/response.d.ts.map +1 -1
  56. package/dist/types/sse.d.ts.map +1 -1
  57. package/dist/types/utils.d.ts +18 -2
  58. package/dist/types/utils.d.ts.map +1 -1
  59. package/dist/types/ws.d.ts +4 -0
  60. package/dist/types/ws.d.ts.map +1 -1
  61. package/package.json +5 -5
@@ -1,4 +1,4 @@
1
- /* kinetex v1.0.0 | MIT | https://github.com/kinetexjs/kinetex */
1
+ /* kinetex v1.2.0 | MIT | https://github.com/kinetexjs/kinetex */
2
2
  "use strict";
3
3
  var kinetex = (() => {
4
4
  var __create = Object.create;
@@ -51,160 +51,6 @@ var kinetex = (() => {
51
51
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
52
52
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
53
53
 
54
- // dist/esm/types.js
55
- function toRequestId(id) {
56
- return id;
57
- }
58
- function validateErrorCode(code) {
59
- if (typeof code === "string") {
60
- for (const valid of VALID_ERROR_CODES) {
61
- if (valid === code)
62
- return valid;
63
- }
64
- }
65
- return void 0;
66
- }
67
- var VALID_ERROR_CODES, KinetexError, SizeLimitError, HTTPStatusError, AbortError, NetworkError, ValidationError, AuthError, ProxyError, RedirectError, TimeoutError;
68
- var init_types = __esm({
69
- "dist/esm/types.js"() {
70
- "use strict";
71
- VALID_ERROR_CODES = [
72
- "ENETWORK",
73
- "ETIMEOUT",
74
- "EABORT",
75
- "EHTTPSTATUS",
76
- "ESIZELIMIT",
77
- "EPARSE",
78
- "EVALIDATION",
79
- "EAUTH",
80
- "EPROXY",
81
- "EREDIRECT",
82
- "EUNKNOWN"
83
- ];
84
- KinetexError = class extends Error {
85
- /**
86
- * @param message - Human-readable error description.
87
- * @param code - Machine-readable error code.
88
- * @param options - Additional context (request, response, cause).
89
- */
90
- constructor(message, code, options) {
91
- super(message);
92
- /** Machine-readable error code. */
93
- __publicField(this, "code");
94
- /** Original request, if available. */
95
- __publicField(this, "request");
96
- /** Response, if available (for HTTP-level errors). */
97
- __publicField(this, "response");
98
- /** Underlying cause. */
99
- __publicField(this, "cause");
100
- this.name = "KinetexError";
101
- this.code = code;
102
- if (options?.request !== void 0)
103
- this.request = options.request;
104
- if (options?.response !== void 0)
105
- this.response = options.response;
106
- if (options?.cause !== void 0)
107
- this.cause = options.cause;
108
- }
109
- /** True if the error is a network/transport failure (not proxy-related). */
110
- get isNetwork() {
111
- return this.code === "ENETWORK";
112
- }
113
- /** True if the error is a proxy-related failure. */
114
- get isProxy() {
115
- return this.code === "EPROXY";
116
- }
117
- /** True if the error is a timeout. */
118
- get isTimeout() {
119
- return this.code === "ETIMEOUT";
120
- }
121
- /** True if the request was aborted by the caller. */
122
- get isAbort() {
123
- return this.code === "EABORT";
124
- }
125
- /** True if the server returned a 4xx or 5xx status. */
126
- get isHTTPError() {
127
- return this.code === "EHTTPSTATUS";
128
- }
129
- /** HTTP status code shorthand (null for non-HTTP errors). */
130
- get status() {
131
- return this.response?.status ?? null;
132
- }
133
- };
134
- SizeLimitError = class extends KinetexError {
135
- constructor(bytesRead, limit, request) {
136
- super(`Response size limit exceeded: ${bytesRead} bytes read, limit is ${limit} bytes`, "ESIZELIMIT", request !== void 0 ? { request } : {});
137
- __publicField(this, "bytesRead");
138
- __publicField(this, "limit");
139
- this.bytesRead = bytesRead;
140
- this.limit = limit;
141
- this.name = "SizeLimitError";
142
- }
143
- };
144
- HTTPStatusError = class extends KinetexError {
145
- constructor(response, request) {
146
- super(`HTTP ${response.status} ${response.statusText} \u2014 ${response.url}`, "EHTTPSTATUS", {
147
- request,
148
- response
149
- });
150
- this.name = "HTTPStatusError";
151
- }
152
- /** True if the status is a 4xx client error. */
153
- get isClientError() {
154
- return (this.status ?? 0) >= 400 && (this.status ?? 0) < 500;
155
- }
156
- /** True if the status is a 5xx server error. */
157
- get isServerError() {
158
- return (this.status ?? 0) >= 500;
159
- }
160
- };
161
- AbortError = class extends KinetexError {
162
- constructor(request) {
163
- super("Request was aborted", "EABORT", request !== void 0 ? { request } : {});
164
- this.name = "AbortError";
165
- }
166
- };
167
- NetworkError = class extends KinetexError {
168
- constructor(message, request) {
169
- super(message, "ENETWORK", request !== void 0 ? { request } : {});
170
- this.name = "NetworkError";
171
- }
172
- };
173
- ValidationError = class extends KinetexError {
174
- constructor(message, request) {
175
- super(message, "EVALIDATION", request !== void 0 ? { request } : {});
176
- this.name = "ValidationError";
177
- }
178
- };
179
- AuthError = class extends KinetexError {
180
- constructor(message, request) {
181
- super(message, "EAUTH", request !== void 0 ? { request } : {});
182
- this.name = "AuthError";
183
- }
184
- };
185
- ProxyError = class extends KinetexError {
186
- constructor(message, request) {
187
- super(message, "EPROXY", request !== void 0 ? { request } : {});
188
- this.name = "ProxyError";
189
- }
190
- };
191
- RedirectError = class extends KinetexError {
192
- constructor(message, request) {
193
- super(message, "EREDIRECT", request !== void 0 ? { request } : {});
194
- this.name = "RedirectError";
195
- }
196
- };
197
- TimeoutError = class extends KinetexError {
198
- constructor(timeoutMs, request) {
199
- super(`Request timed out after ${timeoutMs}ms`, "ETIMEOUT", request !== void 0 ? { request } : {});
200
- __publicField(this, "timeoutMs");
201
- this.timeoutMs = timeoutMs;
202
- this.name = "TimeoutError";
203
- }
204
- };
205
- }
206
- });
207
-
208
54
  // dist/esm/utils.js
209
55
  function getProcess() {
210
56
  if (_process)
@@ -346,6 +192,25 @@ var kinetex = (() => {
346
192
  const result = safeJSONParse(text);
347
193
  return result.success ? result.value : text;
348
194
  }
195
+ function sanitizeParsedJSON(value) {
196
+ if (value === null || typeof value !== "object")
197
+ return value;
198
+ if (Array.isArray(value)) {
199
+ for (let i = 0; i < value.length; i++) {
200
+ value[i] = sanitizeParsedJSON(value[i]);
201
+ }
202
+ return value;
203
+ }
204
+ const obj = value;
205
+ for (const key of Object.keys(obj)) {
206
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
207
+ delete obj[key];
208
+ continue;
209
+ }
210
+ sanitizeParsedJSON(obj[key]);
211
+ }
212
+ return value;
213
+ }
349
214
  function parseUntrustedJSON(text) {
350
215
  return safeJSONParse(text, {
351
216
  maxDepth: 16,
@@ -401,6 +266,130 @@ var kinetex = (() => {
401
266
  }
402
267
  return true;
403
268
  }
269
+ function isBlockedIPv4(n) {
270
+ return IPV4_BLOCKED_RANGES.some(([lo, hi]) => n >= lo && n <= hi);
271
+ }
272
+ function parseIPv4Component(p) {
273
+ if (p === "")
274
+ return null;
275
+ if (/^0[xX][0-9a-fA-F]+$/.test(p))
276
+ return parseInt(p, 16);
277
+ if (/^0[0-7]+$/.test(p))
278
+ return parseInt(p, 8);
279
+ if (/^\d+$/.test(p))
280
+ return parseInt(p, 10);
281
+ return null;
282
+ }
283
+ function parseIPv4Host(host) {
284
+ if (host.includes(":") || !/^[0-9a-fA-FxX.]+$/.test(host))
285
+ return null;
286
+ const bare = host.endsWith(".") ? host.slice(0, -1) : host;
287
+ if (bare === "")
288
+ return null;
289
+ const parts = bare.split(".");
290
+ if (parts.length > 4 || parts.length < 1)
291
+ return null;
292
+ if (parts.length === 1) {
293
+ const v = parseIPv4Component(parts[0]);
294
+ if (v === null || v > 4294967295)
295
+ return null;
296
+ return v >>> 0;
297
+ }
298
+ const octets = [];
299
+ for (let i = 0; i < parts.length - 1; i++) {
300
+ const v = parseIPv4Component(parts[i]);
301
+ if (v === null || v > 255)
302
+ return null;
303
+ octets.push(v);
304
+ }
305
+ const last = parseIPv4Component(parts[parts.length - 1]);
306
+ const lastCap = 2 ** (8 * (5 - parts.length));
307
+ if (last === null || last >= lastCap)
308
+ return null;
309
+ for (let shift = 8 * (4 - parts.length); shift >= 0; shift -= 8) {
310
+ octets.push(last >>> shift & 255);
311
+ }
312
+ return (octets[0] << 24 | octets[1] << 16 | octets[2] << 8 | octets[3]) >>> 0;
313
+ }
314
+ function parseIPv6Host(host) {
315
+ if (!host.includes(":"))
316
+ return null;
317
+ const bare = host.split("%")[0];
318
+ const halves = bare.split("::");
319
+ if (halves.length > 2)
320
+ return null;
321
+ const expandGroups = (part) => {
322
+ if (part === "")
323
+ return [];
324
+ const groups = part.split(":");
325
+ const out = [];
326
+ for (let i = 0; i < groups.length; i++) {
327
+ const g2 = groups[i];
328
+ if (g2 === "")
329
+ return null;
330
+ if (g2.includes(".")) {
331
+ if (i !== groups.length - 1)
332
+ return null;
333
+ const n = parseIPv4Host(g2);
334
+ if (n === null)
335
+ return null;
336
+ out.push(n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255);
337
+ } else {
338
+ if (!/^[0-9a-fA-F]{1,4}$/.test(g2))
339
+ return null;
340
+ const v = parseInt(g2, 16);
341
+ out.push(v >>> 8 & 255, v & 255);
342
+ }
343
+ }
344
+ return out;
345
+ };
346
+ let bytes;
347
+ if (halves.length === 2) {
348
+ const left = expandGroups(halves[0]);
349
+ const right = expandGroups(halves[1]);
350
+ if (!left || !right)
351
+ return null;
352
+ const fill = 16 - left.length - right.length;
353
+ if (fill < 0)
354
+ return null;
355
+ bytes = [...left, ...new Array(fill).fill(0), ...right];
356
+ } else {
357
+ const all = expandGroups(bare);
358
+ if (!all || all.length !== 16)
359
+ return null;
360
+ bytes = all;
361
+ }
362
+ return bytes.length === 16 ? new Uint8Array(bytes) : null;
363
+ }
364
+ function isBlockedIPv6(b) {
365
+ const read32 = (i) => (b[i] << 24 | b[i + 1] << 16 | b[i + 2] << 8 | b[i + 3]) >>> 0;
366
+ if (b.every((x) => x === 0))
367
+ return true;
368
+ if (b.slice(0, 15).every((x) => x === 0) && b[15] === 1)
369
+ return true;
370
+ const first = b[0];
371
+ if ((first & 254) === 252)
372
+ return true;
373
+ if (first === 254 && (b[1] & 192) === 128)
374
+ return true;
375
+ if (first === 255)
376
+ return true;
377
+ if (b[0] === 32 && b[1] === 1 && b[2] === 13 && b[3] === 184)
378
+ return true;
379
+ if (b.slice(0, 10).every((x) => x === 0) && b[10] === 255 && b[11] === 255) {
380
+ return isBlockedIPv4(read32(12));
381
+ }
382
+ if (b.slice(0, 12).every((x) => x === 0)) {
383
+ return isBlockedIPv4(read32(12));
384
+ }
385
+ if (b[0] === 0 && b[1] === 100 && b[2] === 255 && b[3] === 155 && b.slice(4, 12).every((x) => x === 0)) {
386
+ return isBlockedIPv4(read32(12));
387
+ }
388
+ if (b[0] === 32 && b[1] === 2) {
389
+ return isBlockedIPv4(read32(2));
390
+ }
391
+ return false;
392
+ }
404
393
  function isSafeURL(url, allowedSchemes = ["http", "https"]) {
405
394
  try {
406
395
  const parsed = typeof url === "string" ? new URL(url) : url;
@@ -412,14 +401,20 @@ var kinetex = (() => {
412
401
  return false;
413
402
  }
414
403
  let host = parsed.hostname.toLowerCase();
415
- if (host.startsWith("[") && host.endsWith("]"))
404
+ const isV6Literal = host.startsWith("[") && host.endsWith("]");
405
+ if (isV6Literal)
416
406
  host = host.slice(1, -1);
417
- if (host === "0:0:0:0:0:0:0:1")
418
- host = "::1";
419
- if (host === "localhost" || host === "0.0.0.0")
420
- return false;
421
- if (PRIVATE_IP_RANGES.some((r) => r.test(host)))
407
+ if (host === "localhost" || host.endsWith(".localhost") || host === "0.0.0.0")
422
408
  return false;
409
+ if (isV6Literal || host.includes(":")) {
410
+ const v6 = parseIPv6Host(host);
411
+ if (v6 === null || isBlockedIPv6(v6))
412
+ return false;
413
+ } else {
414
+ const v4 = parseIPv4Host(host);
415
+ if (v4 !== null && isBlockedIPv4(v4))
416
+ return false;
417
+ }
423
418
  if (parsed.hostname.includes("..") || parsed.pathname.includes("..")) {
424
419
  return false;
425
420
  }
@@ -463,6 +458,8 @@ var kinetex = (() => {
463
458
  }
464
459
  const cloned = {};
465
460
  for (const key in value) {
461
+ if (key === "__proto__" || key === "constructor" || key === "prototype")
462
+ continue;
466
463
  if (Object.prototype.hasOwnProperty.call(value, key)) {
467
464
  cloned[key] = deepClone(value[key]);
468
465
  }
@@ -619,17 +616,14 @@ var kinetex = (() => {
619
616
  return false;
620
617
  }
621
618
  function randomBytes(byteCount) {
619
+ if (!Number.isInteger(byteCount) || byteCount < 0 || byteCount > 65536) {
620
+ throw new Error(`randomBytes: invalid byteCount ${byteCount}`);
621
+ }
622
622
  const arr = new Uint8Array(byteCount);
623
623
  if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
624
624
  crypto.getRandomValues(arr);
625
625
  } else {
626
- const uuid = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID().replace(/-/g, "") : "";
627
- if (uuid.length >= byteCount * 2) {
628
- return uuid.slice(0, byteCount * 2);
629
- }
630
- for (let i = 0; i < arr.length; i++) {
631
- arr[i] = Math.floor(Math.random() * 256);
632
- }
626
+ throw new Error("randomBytes: no CSPRNG available in this runtime \u2014 crypto.getRandomValues is required");
633
627
  }
634
628
  return Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("");
635
629
  }
@@ -681,7 +675,7 @@ var kinetex = (() => {
681
675
  });
682
676
  return result;
683
677
  }
684
- var _process, _Buffer, DEFAULT_LIMITS, HEADER_NAME_PATTERN, FORBIDDEN_SCHEMES, PRIVATE_IP_RANGES, cachedRuntime;
678
+ var _process, _Buffer, DEFAULT_LIMITS, HEADER_NAME_PATTERN, FORBIDDEN_SCHEMES, IPV4_BLOCKED_RANGES, cachedRuntime;
685
679
  var init_utils = __esm({
686
680
  "dist/esm/utils.js"() {
687
681
  "use strict";
@@ -717,53 +711,194 @@ var kinetex = (() => {
717
711
  "webcal",
718
712
  "urn"
719
713
  ]);
720
- PRIVATE_IP_RANGES = [
721
- /^127\./,
722
- // loopback
723
- /^10\./,
724
- // RFC 1918
725
- /^192\.168\./,
726
- // RFC 1918
727
- /^172\.(1[6-9]|2\d|3[01])\./,
728
- // RFC 1918
729
- /^169\.254\./,
730
- // link-local (AWS IMDS, etc.)
731
- /^0\./,
732
- // this-network
733
- /^::1$/i,
734
- // IPv6 loopback
735
- /^fc00:/i,
736
- // IPv6 ULA (fc00::/7 - includes fc00: and fd00:)
737
- /^fd00:/i,
738
- // IPv6 ULA (fd00::/8 - unique local)
739
- /^fe80:/i,
740
- // IPv6 link-local (fe80::/10)
741
- /^2001:db8:/i,
742
- // IPv6 documentation range
743
- /^::0?ffff:/i,
744
- // IPv4-mapped IPv6 (::ffff:...)
745
- /^0:0:0:0:0:ffff:/i,
746
- // IPv4-mapped compressed form
747
- // FIX 4: IPv4-mapped IPv6 SSRF bypass — e.g. http://[::ffff:127.0.0.1]
748
- /^::ffff:127\./i,
749
- // IPv4-mapped loopback
750
- /^::ffff:10\./i,
751
- // IPv4-mapped RFC 1918
752
- /^::ffff:192\.168\./i,
753
- // IPv4-mapped RFC 1918
754
- /^::ffff:172\.(1[6-9]|2\d|3[01])\./i,
755
- // IPv4-mapped RFC 1918
756
- /^::ffff:169\.254\./i,
757
- // IPv4-mapped link-local
758
- /^0:0:0:0:0:ffff:7f/i,
759
- // compressed form of ::ffff:127.x
760
- /^::$/i
761
- // IPv4-compatible :: (any ::/96)
714
+ IPV4_BLOCKED_RANGES = [
715
+ [0, 16777215],
716
+ // 0.0.0.0/8 — this-network
717
+ [167772160, 184549375],
718
+ // 10.0.0.0/8 — RFC 1918 private
719
+ [1681915904, 1686110207],
720
+ // 100.64.0.0/10 — CGNAT (RFC 6598)
721
+ [2130706432, 2147483647],
722
+ // 127.0.0.0/8 — loopback
723
+ [2851995648, 2852061183],
724
+ // 169.254.0.0/16 — link-local (AWS IMDS etc.)
725
+ [2886729728, 2887778303],
726
+ // 172.16.0.0/12 — RFC 1918 private
727
+ [3221225472, 3221225727],
728
+ // 192.0.0.0/24 — IETF protocol assignments
729
+ [3221225984, 3221226239],
730
+ // 192.0.2.0/24 — TEST-NET-1
731
+ [3232235520, 3232301055],
732
+ // 192.168.0.0/16 — RFC 1918 private
733
+ [3323068416, 3323199487],
734
+ // 198.18.0.0/15 — benchmarking
735
+ [3325256704, 3325256959],
736
+ // 198.51.100.0/24 — TEST-NET-2
737
+ [3405803776, 3405804031],
738
+ // 203.0.113.0/24 — TEST-NET-3
739
+ [3758096384, 4026531839],
740
+ // 224.0.0.0/4 — multicast
741
+ [4026531840, 4294967295]
742
+ // 240.0.0.0/4 — reserved (incl. 255.255.255.255)
762
743
  ];
763
744
  cachedRuntime = null;
764
745
  }
765
746
  });
766
747
 
748
+ // dist/esm/types.js
749
+ function toRequestId(id) {
750
+ return id;
751
+ }
752
+ function validateErrorCode(code) {
753
+ if (typeof code === "string") {
754
+ for (const valid of VALID_ERROR_CODES) {
755
+ if (valid === code)
756
+ return valid;
757
+ }
758
+ }
759
+ return void 0;
760
+ }
761
+ var VALID_ERROR_CODES, KinetexError, SizeLimitError, HTTPStatusError, AbortError, NetworkError, ValidationError, AuthError, ProxyError, RedirectError, TimeoutError;
762
+ var init_types = __esm({
763
+ "dist/esm/types.js"() {
764
+ "use strict";
765
+ VALID_ERROR_CODES = [
766
+ "ENETWORK",
767
+ "ETIMEOUT",
768
+ "EABORT",
769
+ "EHTTPSTATUS",
770
+ "ESIZELIMIT",
771
+ "EPARSE",
772
+ "EVALIDATION",
773
+ "EAUTH",
774
+ "EPROXY",
775
+ "EREDIRECT",
776
+ "EUNKNOWN"
777
+ ];
778
+ KinetexError = class extends Error {
779
+ /**
780
+ * @param message - Human-readable error description.
781
+ * @param code - Machine-readable error code.
782
+ * @param options - Additional context (request, response, cause).
783
+ */
784
+ constructor(message, code, options) {
785
+ super(message);
786
+ /** Machine-readable error code. */
787
+ __publicField(this, "code");
788
+ /** Original request, if available. */
789
+ __publicField(this, "request");
790
+ /** Response, if available (for HTTP-level errors). */
791
+ __publicField(this, "response");
792
+ /** Underlying cause. */
793
+ __publicField(this, "cause");
794
+ this.name = "KinetexError";
795
+ this.code = code;
796
+ if (options?.request !== void 0)
797
+ this.request = options.request;
798
+ if (options?.response !== void 0)
799
+ this.response = options.response;
800
+ if (options?.cause !== void 0)
801
+ this.cause = options.cause;
802
+ }
803
+ /** True if the error is a network/transport failure (not proxy-related). */
804
+ get isNetwork() {
805
+ return this.code === "ENETWORK";
806
+ }
807
+ /** True if the error is a proxy-related failure. */
808
+ get isProxy() {
809
+ return this.code === "EPROXY";
810
+ }
811
+ /** True if the error is a timeout. */
812
+ get isTimeout() {
813
+ return this.code === "ETIMEOUT";
814
+ }
815
+ /** True if the request was aborted by the caller. */
816
+ get isAbort() {
817
+ return this.code === "EABORT";
818
+ }
819
+ /** True if the server returned a 4xx or 5xx status. */
820
+ get isHTTPError() {
821
+ return this.code === "EHTTPSTATUS";
822
+ }
823
+ /** HTTP status code shorthand (null for non-HTTP errors). */
824
+ get status() {
825
+ return this.response?.status ?? null;
826
+ }
827
+ };
828
+ SizeLimitError = class extends KinetexError {
829
+ constructor(bytesRead, limit, request) {
830
+ super(`Response size limit exceeded: ${bytesRead} bytes read, limit is ${limit} bytes`, "ESIZELIMIT", request !== void 0 ? { request } : {});
831
+ __publicField(this, "bytesRead");
832
+ __publicField(this, "limit");
833
+ this.bytesRead = bytesRead;
834
+ this.limit = limit;
835
+ this.name = "SizeLimitError";
836
+ }
837
+ };
838
+ HTTPStatusError = class extends KinetexError {
839
+ constructor(response, request) {
840
+ super(`HTTP ${response.status} ${response.statusText} \u2014 ${response.url}`, "EHTTPSTATUS", {
841
+ request,
842
+ response
843
+ });
844
+ this.name = "HTTPStatusError";
845
+ }
846
+ /** True if the status is a 4xx client error. */
847
+ get isClientError() {
848
+ return (this.status ?? 0) >= 400 && (this.status ?? 0) < 500;
849
+ }
850
+ /** True if the status is a 5xx server error. */
851
+ get isServerError() {
852
+ return (this.status ?? 0) >= 500;
853
+ }
854
+ };
855
+ AbortError = class extends KinetexError {
856
+ constructor(request) {
857
+ super("Request was aborted", "EABORT", request !== void 0 ? { request } : {});
858
+ this.name = "AbortError";
859
+ }
860
+ };
861
+ NetworkError = class extends KinetexError {
862
+ constructor(message, request) {
863
+ super(message, "ENETWORK", request !== void 0 ? { request } : {});
864
+ this.name = "NetworkError";
865
+ }
866
+ };
867
+ ValidationError = class extends KinetexError {
868
+ constructor(message, request) {
869
+ super(message, "EVALIDATION", request !== void 0 ? { request } : {});
870
+ this.name = "ValidationError";
871
+ }
872
+ };
873
+ AuthError = class extends KinetexError {
874
+ constructor(message, request) {
875
+ super(message, "EAUTH", request !== void 0 ? { request } : {});
876
+ this.name = "AuthError";
877
+ }
878
+ };
879
+ ProxyError = class extends KinetexError {
880
+ constructor(message, request) {
881
+ super(message, "EPROXY", request !== void 0 ? { request } : {});
882
+ this.name = "ProxyError";
883
+ }
884
+ };
885
+ RedirectError = class extends KinetexError {
886
+ constructor(message, request) {
887
+ super(message, "EREDIRECT", request !== void 0 ? { request } : {});
888
+ this.name = "RedirectError";
889
+ }
890
+ };
891
+ TimeoutError = class extends KinetexError {
892
+ constructor(timeoutMs, request) {
893
+ super(`Request timed out after ${timeoutMs}ms`, "ETIMEOUT", request !== void 0 ? { request } : {});
894
+ __publicField(this, "timeoutMs");
895
+ this.timeoutMs = timeoutMs;
896
+ this.name = "TimeoutError";
897
+ }
898
+ };
899
+ }
900
+ });
901
+
767
902
  // dist/esm/cache.js
768
903
  var cache_exports = {};
769
904
  __export(cache_exports, {
@@ -3706,7 +3841,7 @@ var kinetex = (() => {
3706
3841
  const charset = options.charset ?? parseContentType2(ct ?? "")?.charset ?? null;
3707
3842
  const text = decodeBody(bytes, charset, url);
3708
3843
  try {
3709
- return JSON.parse(text);
3844
+ return sanitizeParsedJSON(JSON.parse(text));
3710
3845
  } catch (err) {
3711
3846
  throw new ResponseDecodeError(`JSON parse failed: ${err}`, "json", url);
3712
3847
  }
@@ -3761,7 +3896,7 @@ var kinetex = (() => {
3761
3896
  const trimmed = buffer.trim();
3762
3897
  if (trimmed) {
3763
3898
  try {
3764
- yield JSON.parse(trimmed);
3899
+ yield sanitizeParsedJSON(JSON.parse(trimmed));
3765
3900
  } catch (err) {
3766
3901
  options.onParseError?.(err, trimmed);
3767
3902
  }
@@ -3776,7 +3911,7 @@ var kinetex = (() => {
3776
3911
  if (!trimmed || trimmed.startsWith("#"))
3777
3912
  continue;
3778
3913
  try {
3779
- yield JSON.parse(trimmed);
3914
+ yield sanitizeParsedJSON(JSON.parse(trimmed));
3780
3915
  } catch (err) {
3781
3916
  options.onParseError?.(err, trimmed);
3782
3917
  }
@@ -3809,7 +3944,7 @@ var kinetex = (() => {
3809
3944
  const remaining = flushBuffer();
3810
3945
  if (remaining) {
3811
3946
  try {
3812
- const obj = JSON.parse(remaining);
3947
+ const obj = sanitizeParsedJSON(JSON.parse(remaining));
3813
3948
  options.onObject?.(obj);
3814
3949
  yield obj;
3815
3950
  } catch (err) {
@@ -3830,7 +3965,7 @@ var kinetex = (() => {
3830
3965
  if (depth === 0 && startIdx !== -1) {
3831
3966
  const objStr = buffer.slice(startIdx, i + 1);
3832
3967
  try {
3833
- const obj = JSON.parse(objStr);
3968
+ const obj = sanitizeParsedJSON(JSON.parse(objStr));
3834
3969
  options.onObject?.(obj);
3835
3970
  yield obj;
3836
3971
  } catch (err) {
@@ -4374,7 +4509,7 @@ var kinetex = (() => {
4374
4509
  }
4375
4510
  }
4376
4511
  function createTransport(fetchFn, preferHTTP2 = true, sessionOptions, transportOptions) {
4377
- if (IS_NODE && preferHTTP2) {
4512
+ if (IS_NODE && preferHTTP2 && !fetchFn) {
4378
4513
  return new NodeHTTP2Transport({
4379
4514
  ...sessionOptions?.sessionTTLMs !== void 0 ? { sessionTTLMs: sessionOptions.sessionTTLMs } : {},
4380
4515
  ...sessionOptions?.pingIntervalMs !== void 0 ? { pingIntervalMs: sessionOptions.pingIntervalMs } : {},
@@ -4399,8 +4534,11 @@ var kinetex = (() => {
4399
4534
  }, timeoutMs);
4400
4535
  const signal = mergeSignals(request.signal, controller.signal) ?? null;
4401
4536
  const req = { ...request, signal };
4537
+ const timeoutPromise = new Promise((_, reject) => {
4538
+ controller.signal.addEventListener("abort", () => reject(new TimeoutError(timeoutMs, request)), { once: true });
4539
+ });
4402
4540
  try {
4403
- const result = await transport.send(req);
4541
+ const result = await Promise.race([transport.send(req), timeoutPromise]);
4404
4542
  clearTimeout(timer);
4405
4543
  if (controller.signal.aborted) {
4406
4544
  throw new TimeoutError(timeoutMs, request);
@@ -4408,7 +4546,7 @@ var kinetex = (() => {
4408
4546
  return result;
4409
4547
  } catch (err) {
4410
4548
  clearTimeout(timer);
4411
- if (controller.signal.aborted) {
4549
+ if (controller.signal.aborted && !(err instanceof TimeoutError)) {
4412
4550
  throw new TimeoutError(timeoutMs, request);
4413
4551
  }
4414
4552
  throw err;
@@ -4782,6 +4920,8 @@ var kinetex = (() => {
4782
4920
  __publicField(this, "sessionUsage", /* @__PURE__ */ new Map());
4783
4921
  __publicField(this, "_strict");
4784
4922
  __publicField(this, "_onDroppedHeader");
4923
+ /** Optional CA bundle for origins with self-signed / private-PKI certificates. */
4924
+ __publicField(this, "_ca");
4785
4925
  /** FIX 11: Configurable connect timeout (replaces hardcoded 30 000 ms) */
4786
4926
  __publicField(this, "_connectTimeoutMs");
4787
4927
  /** FIX 11: Configurable per-request stream timeout (replaces hardcoded 30 000 ms) */
@@ -4793,6 +4933,7 @@ var kinetex = (() => {
4793
4933
  this.maxSessions = options.maxSessions ?? 100;
4794
4934
  this._strict = options.strict ?? false;
4795
4935
  this._onDroppedHeader = options.onDroppedHeader;
4936
+ this._ca = options.ca;
4796
4937
  this._connectTimeoutMs = options.connectTimeoutMs ?? 3e4;
4797
4938
  this._requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
4798
4939
  }
@@ -4842,7 +4983,10 @@ var kinetex = (() => {
4842
4983
  }
4843
4984
  }
4844
4985
  const session = await new Promise((resolve, reject) => {
4845
- const s = http2.connect(origin, { rejectUnauthorized: true });
4986
+ const s = http2.connect(origin, {
4987
+ rejectUnauthorized: true,
4988
+ ...this._ca !== void 0 ? { ca: this._ca } : {}
4989
+ });
4846
4990
  const connectTimeout = setTimeout(() => {
4847
4991
  s.destroy();
4848
4992
  reject(new KinetexError(`HTTP/2 connection to ${origin} timed out`, "ETIMEOUT", {
@@ -4955,27 +5099,28 @@ var kinetex = (() => {
4955
5099
  ":authority": currentUrl.host,
4956
5100
  ...currentReq.headers
4957
5101
  };
4958
- if (this._strict) {
4959
- for (const [hName, hValue] of Object.entries(h2ReqHeaders)) {
4960
- if (hName.startsWith(":"))
4961
- continue;
4962
- const hStr = Array.isArray(hValue) ? hValue.join(", ") : String(hValue);
4963
- let hasForbidden = false;
4964
- for (let ci = 0; ci < hStr.length; ci++) {
4965
- const code = hStr.charCodeAt(ci);
4966
- if (code >= 0 && code <= 8 || code >= 10 && code <= 31 || code === 127) {
4967
- hasForbidden = true;
4968
- break;
4969
- }
5102
+ for (const [hName, hValue] of Object.entries(h2ReqHeaders)) {
5103
+ if (hName.startsWith(":"))
5104
+ continue;
5105
+ const hStr = Array.isArray(hValue) ? hValue.join(", ") : String(hValue);
5106
+ let hasForbidden = false;
5107
+ for (let ci = 0; ci < hStr.length; ci++) {
5108
+ const code = hStr.charCodeAt(ci);
5109
+ if (code >= 0 && code <= 8 || code >= 10 && code <= 31 || code === 127) {
5110
+ hasForbidden = true;
5111
+ break;
4970
5112
  }
4971
- if (hasForbidden) {
4972
- if (this._onDroppedHeader) {
4973
- this._onDroppedHeader(hName, hStr);
4974
- delete h2ReqHeaders[hName];
4975
- } else {
4976
- throw new KinetexError(`Strict mode: header "${hName}" contains forbidden control characters`, "EVALIDATION", { request: currentReq });
4977
- }
5113
+ }
5114
+ if (hasForbidden) {
5115
+ if (this._strict) {
5116
+ throw new KinetexError(`Strict mode: header "${hName}" contains forbidden control characters`, "EVALIDATION", { request: currentReq });
4978
5117
  }
5118
+ if (this._onDroppedHeader) {
5119
+ this._onDroppedHeader(hName, hStr);
5120
+ } else if (typeof console !== "undefined") {
5121
+ console.warn(`[kinetex] Invalid header dropped (HTTP/2): "${hName}" \u2014 value contains illegal control characters. Pass strictHeaders: true to throw instead.`);
5122
+ }
5123
+ delete h2ReqHeaders[hName];
4979
5124
  }
4980
5125
  }
4981
5126
  const endStream = !currentReq.body || currentReq.method === "GET" || currentReq.method === "HEAD";
@@ -7325,6 +7470,10 @@ var kinetex = (() => {
7325
7470
  continue;
7326
7471
  if (!sameSiteAllows(cookie.sameSite, context))
7327
7472
  continue;
7473
+ if (cookie.name.startsWith("__Secure-") && !(cookie.secure && isSecure))
7474
+ continue;
7475
+ if (cookie.name.startsWith("__Host-") && !(cookie.secure && isSecure && cookie.path === "/" && cookie.hostOnly))
7476
+ continue;
7328
7477
  result.push(cookie);
7329
7478
  }
7330
7479
  }
@@ -7797,9 +7946,11 @@ var kinetex = (() => {
7797
7946
  const [head, ...rest] = path;
7798
7947
  if (head === void 0)
7799
7948
  return;
7949
+ if (head === "__proto__" || head === "constructor" || head === "prototype")
7950
+ return;
7800
7951
  const o = obj;
7801
7952
  if (rest.length === 0) {
7802
- if (head in o)
7953
+ if (Object.prototype.hasOwnProperty.call(o, head))
7803
7954
  o[head] = "***";
7804
7955
  return;
7805
7956
  }
@@ -7882,6 +8033,7 @@ var kinetex = (() => {
7882
8033
  var init_logging = __esm({
7883
8034
  "dist/esm/logging.js"() {
7884
8035
  "use strict";
8036
+ init_utils();
7885
8037
  getNodeEnv = () => {
7886
8038
  try {
7887
8039
  return globalThis.process?.env?.["NODE_ENV"];
@@ -8241,7 +8393,7 @@ var kinetex = (() => {
8241
8393
  const size = str.length;
8242
8394
  if (ct.includes("application/json") && this.bodyFields.length > 0) {
8243
8395
  try {
8244
- const parsed = JSON.parse(str);
8396
+ const parsed = sanitizeParsedJSON(JSON.parse(str));
8245
8397
  for (const path of this.bodyFields)
8246
8398
  redactObjectPath(parsed, path.split("."));
8247
8399
  str = JSON.stringify(parsed);
@@ -8512,7 +8664,7 @@ var kinetex = (() => {
8512
8664
  if (!evt.data && evt.data !== "")
8513
8665
  continue;
8514
8666
  try {
8515
- const data = JSON.parse(evt.data, options.reviver);
8667
+ const data = sanitizeParsedJSON(JSON.parse(evt.data, options.reviver));
8516
8668
  yield { event: evt.event, data, id: evt.id };
8517
8669
  } catch (err) {
8518
8670
  options.onError?.(err, evt);
@@ -8552,6 +8704,7 @@ var kinetex = (() => {
8552
8704
  var init_sse = __esm({
8553
8705
  "dist/esm/sse.js"() {
8554
8706
  "use strict";
8707
+ init_utils();
8555
8708
  SSEParser = class {
8556
8709
  constructor() {
8557
8710
  __publicField(this, "lines", []);
@@ -8991,7 +9144,7 @@ var kinetex = (() => {
8991
9144
  onJSON(eventType, handler) {
8992
9145
  return this.on(eventType, async (data, evt) => {
8993
9146
  try {
8994
- const parsed = JSON.parse(data);
9147
+ const parsed = sanitizeParsedJSON(JSON.parse(data));
8995
9148
  await handler(parsed, evt);
8996
9149
  } catch {
8997
9150
  }
@@ -9315,11 +9468,18 @@ var kinetex = (() => {
9315
9468
  let current = obj;
9316
9469
  for (let i = 0; i < parts.length - 1; i++) {
9317
9470
  const key = parts[i];
9471
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
9472
+ throw new ValidationError(`Invalid upload path: "${path}" \u2014 reserved key "${key}"`);
9473
+ }
9318
9474
  if (!current[key] || typeof current[key] !== "object")
9319
9475
  current[key] = {};
9320
9476
  current = current[key];
9321
9477
  }
9322
- current[parts[parts.length - 1]] = value;
9478
+ const leaf = parts[parts.length - 1];
9479
+ if (leaf === "__proto__" || leaf === "constructor" || leaf === "prototype") {
9480
+ throw new ValidationError(`Invalid upload path: "${path}" \u2014 reserved key "${leaf}"`);
9481
+ }
9482
+ current[leaf] = value;
9323
9483
  }
9324
9484
  async function parseGraphQLResponse(response, req) {
9325
9485
  const ct = response.headers.get("content-type") ?? "";
@@ -9330,7 +9490,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9330
9490
  }
9331
9491
  let json;
9332
9492
  try {
9333
- json = await response.json();
9493
+ json = sanitizeParsedJSON(await response.json());
9334
9494
  } catch (err) {
9335
9495
  throw new GraphQLClientError("Failed to parse GraphQL response as JSON", "EPARSE", void 0, req, void 0, err);
9336
9496
  }
@@ -9366,7 +9526,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9366
9526
  fetchHeaders["content-type"] = "application/json";
9367
9527
  const controller = new AbortController();
9368
9528
  const timer = config.timeoutMs > 0 ? setTimeout(() => controller.abort(), config.timeoutMs) : null;
9369
- signal?.addEventListener("abort", () => controller.abort(), { once: true });
9529
+ const onExternalAbort = () => controller.abort();
9530
+ signal?.addEventListener("abort", onExternalAbort, { once: true });
9370
9531
  let response;
9371
9532
  try {
9372
9533
  response = await config.fetch(fetchUrl, {
@@ -9385,6 +9546,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9385
9546
  } finally {
9386
9547
  if (timer)
9387
9548
  clearTimeout(timer);
9549
+ signal?.removeEventListener("abort", onExternalAbort);
9388
9550
  }
9389
9551
  return parseGraphQLResponse(response, req);
9390
9552
  }
@@ -9477,6 +9639,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9477
9639
  "dist/esm/graphql.js"() {
9478
9640
  "use strict";
9479
9641
  init_types();
9642
+ init_utils();
9480
9643
  GraphQLClientError = class extends Error {
9481
9644
  /**
9482
9645
  * @param message - Human-readable error description
@@ -9742,7 +9905,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9742
9905
  delete headers["content-type"];
9743
9906
  const form = buildMultipartBody(req, uploads);
9744
9907
  const controller = new AbortController();
9745
- options.signal?.addEventListener("abort", () => controller.abort(), { once: true });
9908
+ const onExternalAbort = () => controller.abort();
9909
+ options.signal?.addEventListener("abort", onExternalAbort, { once: true });
9746
9910
  this.config.onRequest(req);
9747
9911
  let response;
9748
9912
  try {
@@ -9756,6 +9920,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9756
9920
  const clientErr = new GraphQLClientError(err instanceof Error ? err.message : "Upload network error", "ENETWORK", void 0, req, void 0, err);
9757
9921
  this.config.onError(clientErr, req);
9758
9922
  throw clientErr;
9923
+ } finally {
9924
+ options.signal?.removeEventListener("abort", onExternalAbort);
9759
9925
  }
9760
9926
  const gqlRes = await parseGraphQLResponse(response, req);
9761
9927
  this.config.onResponse(gqlRes, req);
@@ -9786,7 +9952,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9786
9952
  async batch(requests, options = {}) {
9787
9953
  const headers = await buildHeaders(this.config);
9788
9954
  const controller = new AbortController();
9789
- options.signal?.addEventListener("abort", () => controller.abort(), { once: true });
9955
+ const onExternalAbort = () => controller.abort();
9956
+ options.signal?.addEventListener("abort", onExternalAbort, { once: true });
9790
9957
  let response;
9791
9958
  try {
9792
9959
  response = await this.config.fetch(this.config.url, {
@@ -9797,8 +9964,10 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9797
9964
  });
9798
9965
  } catch (err) {
9799
9966
  throw new GraphQLClientError(err instanceof Error ? err.message : "Batch network error", "ENETWORK", void 0, requests[0], void 0, err);
9967
+ } finally {
9968
+ options.signal?.removeEventListener("abort", onExternalAbort);
9800
9969
  }
9801
- const rawResults = await response.json();
9970
+ const rawResults = sanitizeParsedJSON(await response.json());
9802
9971
  if (!Array.isArray(rawResults)) {
9803
9972
  throw new GraphQLClientError("Batch response must be an array of GraphQL responses", "EINVALIDRESPONSE", void 0, requests[0], rawResults);
9804
9973
  }
@@ -9886,7 +10055,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9886
10055
  continue;
9887
10056
  let gqlRes;
9888
10057
  try {
9889
- gqlRes = JSON.parse(event.data);
10058
+ gqlRes = sanitizeParsedJSON(JSON.parse(event.data));
9890
10059
  } catch {
9891
10060
  continue;
9892
10061
  }
@@ -10413,7 +10582,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
10413
10582
  }
10414
10583
  function deserializePaginationState(serialized) {
10415
10584
  try {
10416
- return JSON.parse(atob(serialized));
10585
+ return sanitizeParsedJSON(JSON.parse(atob(serialized)));
10417
10586
  } catch {
10418
10587
  throw new Error("Invalid pagination state string");
10419
10588
  }
@@ -10460,6 +10629,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
10460
10629
  var init_pagination = __esm({
10461
10630
  "dist/esm/pagination.js"() {
10462
10631
  "use strict";
10632
+ init_utils();
10463
10633
  }
10464
10634
  });
10465
10635
 
@@ -10768,6 +10938,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
10768
10938
  retryLink: () => retryLink,
10769
10939
  safeJSONParse: () => safeJSONParse,
10770
10940
  safeParseURL: () => safeParseURL,
10941
+ sanitizeParsedJSON: () => sanitizeParsedJSON,
10771
10942
  sanitizeURL: () => sanitizeURL,
10772
10943
  securityHeaders: () => securityHeaders,
10773
10944
  sendWithTimeout: () => sendWithTimeout,
@@ -10936,11 +11107,12 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
10936
11107
  controller.error(new DOMException("Upload aborted", "AbortError"));
10937
11108
  return;
10938
11109
  }
10939
- options.signal?.addEventListener("abort", () => {
11110
+ const onUploadAbort = () => {
10940
11111
  reader.cancel("aborted").catch(() => {
10941
11112
  });
10942
11113
  controller.error(new DOMException("Upload aborted", "AbortError"));
10943
- }, { once: true });
11114
+ };
11115
+ options.signal?.addEventListener("abort", onUploadAbort, { once: true });
10944
11116
  try {
10945
11117
  while (true) {
10946
11118
  const { done, value } = await reader.read();
@@ -10955,6 +11127,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
10955
11127
  } catch (err) {
10956
11128
  tracker.complete();
10957
11129
  controller.error(err);
11130
+ } finally {
11131
+ options.signal?.removeEventListener("abort", onUploadAbort);
10958
11132
  }
10959
11133
  }
10960
11134
  });
@@ -11045,11 +11219,11 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11045
11219
  });
11046
11220
  throw new DOMException("Stream aborted", "AbortError");
11047
11221
  }
11222
+ const abortHandler = () => {
11223
+ reader.cancel("aborted").catch(() => {
11224
+ });
11225
+ };
11048
11226
  if (options.signal) {
11049
- const abortHandler = () => {
11050
- reader.cancel("aborted").catch(() => {
11051
- });
11052
- };
11053
11227
  options.signal.addEventListener("abort", abortHandler, { once: true });
11054
11228
  }
11055
11229
  try {
@@ -11062,6 +11236,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11062
11236
  }
11063
11237
  } finally {
11064
11238
  reader.releaseLock();
11239
+ options.signal?.removeEventListener("abort", abortHandler);
11065
11240
  }
11066
11241
  tracker.complete();
11067
11242
  const totalBytes = chunks.reduce((acc, c) => acc + c.byteLength, 0);
@@ -11782,6 +11957,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11782
11957
  }
11783
11958
 
11784
11959
  // dist/esm/ws.js
11960
+ init_utils();
11785
11961
  var WSError = class extends Error {
11786
11962
  /**
11787
11963
  * @param message - Human-readable error description
@@ -11874,6 +12050,10 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11874
12050
  __publicField(this, "_openWaiters", []);
11875
12051
  __publicField(this, "_correlations", /* @__PURE__ */ new Map());
11876
12052
  __publicField(this, "_aborted", false);
12053
+ /** Optional external abort signal from config (listener detached on close). */
12054
+ __publicField(this, "_externalSignal", null);
12055
+ /** Bound abort handler for the optional external `cfg.signal` (removed on close). */
12056
+ __publicField(this, "_onExternalAbort", () => this.close(1e3, "AbortSignal aborted"));
11877
12057
  // ── Async iterator ──────────────────────────────────────────────────────
11878
12058
  __publicField(this, "_iterQueue", []);
11879
12059
  __publicField(this, "_iterWaiter", null);
@@ -11937,7 +12117,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11937
12117
  this._rooms.push({ room: r, joinedAt: 0 });
11938
12118
  }
11939
12119
  }
11940
- cfg.signal?.addEventListener("abort", () => this.close(1e3, "AbortSignal aborted"), {
12120
+ this._externalSignal = cfg.signal ?? null;
12121
+ this._externalSignal?.addEventListener("abort", this._onExternalAbort, {
11941
12122
  once: true
11942
12123
  });
11943
12124
  }
@@ -12018,6 +12199,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12018
12199
  */
12019
12200
  close(code = 1e3, reason = "Client closed") {
12020
12201
  this._aborted = true;
12202
+ this._externalSignal?.removeEventListener("abort", this._onExternalAbort);
12203
+ this._externalSignal = null;
12021
12204
  this._stopAll();
12022
12205
  for (const w of this._drainWaiters.splice(0))
12023
12206
  w();
@@ -12452,7 +12635,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12452
12635
  data = evt.data;
12453
12636
  bytes = new TextEncoder().encode(data).byteLength;
12454
12637
  try {
12455
- json = JSON.parse(data);
12638
+ json = sanitizeParsedJSON(JSON.parse(data));
12456
12639
  } catch {
12457
12640
  }
12458
12641
  } else if (evt.data instanceof ArrayBuffer) {
@@ -13157,27 +13340,33 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13157
13340
  }, ms);
13158
13341
  const existing = ctx.request.signal;
13159
13342
  if (existing) {
13160
- existing.addEventListener("abort", () => {
13343
+ const onExistingAbort = () => {
13161
13344
  clearTimeout(timer);
13162
13345
  controller.abort(existing.reason);
13163
- }, { once: true });
13346
+ };
13347
+ existing.addEventListener("abort", onExistingAbort, { once: true });
13348
+ ctx.store.set(TIMEOUT_SIGNAL_KEY, { existing, onExistingAbort });
13164
13349
  }
13165
13350
  ctx.request = { ...ctx.request, signal: controller.signal };
13166
13351
  ctx.store.set(TIMEOUT_TIMER_KEY, timer);
13167
13352
  };
13168
- const responseInterceptor = (ctx) => {
13353
+ const cleanupTimeout = (ctx) => {
13169
13354
  const timer = ctx.store.get(TIMEOUT_TIMER_KEY);
13170
13355
  if (timer) {
13171
13356
  clearTimeout(timer);
13172
13357
  ctx.store.delete(TIMEOUT_TIMER_KEY);
13173
13358
  }
13359
+ const merged = ctx.store.get(TIMEOUT_SIGNAL_KEY);
13360
+ if (merged) {
13361
+ merged.existing.removeEventListener("abort", merged.onExistingAbort);
13362
+ ctx.store.delete(TIMEOUT_SIGNAL_KEY);
13363
+ }
13364
+ };
13365
+ const responseInterceptor = (ctx) => {
13366
+ cleanupTimeout(ctx);
13174
13367
  };
13175
13368
  const errorInterceptor = (ctx) => {
13176
- const timer = ctx.store.get(TIMEOUT_TIMER_KEY);
13177
- if (timer) {
13178
- clearTimeout(timer);
13179
- ctx.store.delete(TIMEOUT_TIMER_KEY);
13180
- }
13369
+ cleanupTimeout(ctx);
13181
13370
  };
13182
13371
  return {
13183
13372
  id: "timeout",
@@ -13187,6 +13376,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13187
13376
  };
13188
13377
  }
13189
13378
  var TIMEOUT_TIMER_KEY = /* @__PURE__ */ Symbol("timeoutTimer");
13379
+ var TIMEOUT_SIGNAL_KEY = /* @__PURE__ */ Symbol("timeoutSignal");
13190
13380
  var TimeoutError2 = class extends Error {
13191
13381
  constructor(message, timeoutMs) {
13192
13382
  super(message);
@@ -14868,6 +15058,34 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
14868
15058
  function randomHex(len) {
14869
15059
  return randomBytes(len / 2);
14870
15060
  }
15061
+ var HAR_REDACT_HEADERS = /* @__PURE__ */ new Set([
15062
+ "authorization",
15063
+ "proxy-authorization",
15064
+ "cookie",
15065
+ "set-cookie",
15066
+ "x-api-key",
15067
+ "x-auth-token",
15068
+ "x-access-token",
15069
+ "x-refresh-token",
15070
+ "x-csrf-token",
15071
+ "x-session-id",
15072
+ "x-session-token",
15073
+ "x-secret",
15074
+ "x-secret-key",
15075
+ "x-private-key",
15076
+ "api-key",
15077
+ "apikey",
15078
+ "bearer",
15079
+ "token",
15080
+ "authentication",
15081
+ "credentials",
15082
+ "password",
15083
+ "passwd",
15084
+ "secret"
15085
+ ]);
15086
+ function redactHARHeader(name, value) {
15087
+ return HAR_REDACT_HEADERS.has(name.toLowerCase()) ? { name, value: "***REDACTED***" } : { name, value };
15088
+ }
14871
15089
  var HARRecorder = class {
14872
15090
  /**
14873
15091
  * @param maxEntries - Maximum number of entries before eviction (default 10000).
@@ -14919,7 +15137,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
14919
15137
  method: req.method,
14920
15138
  url: req.url,
14921
15139
  httpVersion: res.httpVersion,
14922
- headers: Object.entries(req.headers).map(([name, value]) => ({ name, value })),
15140
+ headers: Object.entries(req.headers).map(([name, value]) => redactHARHeader(name, value)),
14923
15141
  queryString: (() => {
14924
15142
  try {
14925
15143
  return Array.from(new URL(req.url).searchParams.entries()).map(([name, value]) => ({
@@ -14946,7 +15164,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
14946
15164
  status: res.status,
14947
15165
  statusText: res.statusText,
14948
15166
  httpVersion: res.httpVersion,
14949
- headers: Object.entries(res.headers).map(([name, value]) => ({ name, value })),
15167
+ headers: Object.entries(res.headers).map(([name, value]) => redactHARHeader(name, value)),
14950
15168
  content: {
14951
15169
  size: res.rawBody?.byteLength ?? 0,
14952
15170
  mimeType: res.headers["content-type"] ?? "application/octet-stream",
@@ -14994,7 +15212,11 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
14994
15212
  switch (auth.type) {
14995
15213
  case "bearer": {
14996
15214
  const token = typeof auth.token === "function" ? await auth.token() : auth.token;
14997
- headers["authorization"] = `Bearer ${token}`;
15215
+ const headerValue = `Bearer ${token}`;
15216
+ if (!isValidHeaderValue(headerValue)) {
15217
+ throw new KinetexError("Invalid bearer token \u2014 contains forbidden characters (CRLF/CTL)", "EVALIDATION");
15218
+ }
15219
+ headers["authorization"] = headerValue;
14998
15220
  break;
14999
15221
  }
15000
15222
  case "basic": {
@@ -15015,7 +15237,13 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15015
15237
  }
15016
15238
  case "apikey": {
15017
15239
  const key = typeof auth.key === "function" ? await auth.key() : auth.key;
15018
- headers[auth.header.toLowerCase()] = key;
15240
+ if (!isValidHeaderName(auth.header)) {
15241
+ throw new KinetexError(`Invalid apikey auth header name: "${auth.header}"`, "EVALIDATION");
15242
+ }
15243
+ if (!isValidHeaderValue(String(key))) {
15244
+ throw new KinetexError(`Invalid apikey value for "${auth.header}" \u2014 contains forbidden characters`, "EVALIDATION");
15245
+ }
15246
+ headers[auth.header.toLowerCase()] = String(key);
15019
15247
  break;
15020
15248
  }
15021
15249
  case "digest": {
@@ -15034,6 +15262,37 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15034
15262
  }
15035
15263
  return { ...req, headers };
15036
15264
  }
15265
+ var CROSS_ORIGIN_STRIP_HEADERS = /* @__PURE__ */ new Set([
15266
+ "authorization",
15267
+ "cookie",
15268
+ "proxy-authorization",
15269
+ "x-api-key",
15270
+ "x-auth-token",
15271
+ "x-access-token",
15272
+ "x-refresh-token",
15273
+ "x-csrf-token",
15274
+ "x-session-id",
15275
+ "x-session-token",
15276
+ "x-secret",
15277
+ "x-secret-key",
15278
+ "x-private-key",
15279
+ "api-key",
15280
+ "apikey",
15281
+ "www-authenticate"
15282
+ ]);
15283
+ function redactUserInfo(url) {
15284
+ try {
15285
+ const u = new URL(url);
15286
+ if (u.username || u.password) {
15287
+ u.username = "";
15288
+ u.password = "";
15289
+ return u.toString();
15290
+ }
15291
+ return url;
15292
+ } catch {
15293
+ return url.replace(/\/\/[^/@]*@/, "//");
15294
+ }
15295
+ }
15037
15296
  function buildURL(base, url, params) {
15038
15297
  const MAX_QUERY_PARAM_COUNT = 100;
15039
15298
  const MAX_URL_LENGTH = 8192;
@@ -15054,7 +15313,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15054
15313
  }
15055
15314
  if (!params || Object.keys(params).length === 0) {
15056
15315
  if (!isSafeURL(full)) {
15057
- throw new KinetexError(`URL "${full}" failed safety check \u2014 blocked private/loopback address or forbidden scheme`, "EVALIDATION");
15316
+ throw new KinetexError(`URL "${redactUserInfo(full)}" failed safety check \u2014 blocked private/loopback address or forbidden scheme`, "EVALIDATION");
15058
15317
  }
15059
15318
  return full;
15060
15319
  }
@@ -15088,7 +15347,7 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15088
15347
  throw new KinetexError(`URL length ${result.length} bytes exceeds limit of ${MAX_URL_LENGTH} bytes`, "EVALIDATION");
15089
15348
  }
15090
15349
  if (!isSafeURL(result)) {
15091
- throw new KinetexError(`URL "${result}" failed safety check \u2014 blocked private/loopback address or forbidden scheme`, "EVALIDATION");
15350
+ throw new KinetexError(`URL "${redactUserInfo(result)}" failed safety check \u2014 blocked private/loopback address or forbidden scheme`, "EVALIDATION");
15092
15351
  }
15093
15352
  return result;
15094
15353
  } catch (err) {
@@ -15126,6 +15385,9 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15126
15385
  if (result.length > MAX_URL_LENGTH) {
15127
15386
  throw new KinetexError(`URL length ${result.length} bytes exceeds limit of ${MAX_URL_LENGTH} bytes`, "EVALIDATION");
15128
15387
  }
15388
+ if (!isSafeURL(result)) {
15389
+ throw new KinetexError(`URL "${redactUserInfo(result)}" failed safety check \u2014 blocked private/loopback address or forbidden scheme`, "EVALIDATION");
15390
+ }
15129
15391
  return result;
15130
15392
  }
15131
15393
  }
@@ -15873,6 +16135,10 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15873
16135
  throw new KinetexError(`Invalid HTTP method '${method}'. Valid methods: ${VALID_METHODS.join(", ")}`, "EVALIDATION");
15874
16136
  }
15875
16137
  const fullUrl = buildURL(options.baseURL ?? this.cfg.baseURL, url, mergeParams(this.cfg.params, options.params));
16138
+ const proxy = options.proxy ?? this.cfg.proxy;
16139
+ if (proxy) {
16140
+ throw new KinetexError("proxy is configured but kinetex's built-in transports cannot route through it: HTTP(S) proxies require a custom fetch with a proxy agent (e.g. undici ProxyAgent passed via the `fetch` option), and SOCKS5 requires createSocks5Tunnel() from kinetex/socks5. Set up one of those instead of relying on `proxy` silently doing nothing.", "EVALIDATION");
16141
+ }
15876
16142
  if (this.cfg.httpsOnly) {
15877
16143
  try {
15878
16144
  const parsedUrl = new URL(fullUrl);
@@ -15896,9 +16162,25 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
15896
16162
  bodySize2 = options.body.byteLength;
15897
16163
  } else if (options.body instanceof Blob) {
15898
16164
  bodySize2 = options.body.size;
16165
+ } else if (ArrayBuffer.isView(options.body)) {
16166
+ bodySize2 = options.body.byteLength;
16167
+ } else if (options.body instanceof URLSearchParams) {
16168
+ bodySize2 = new TextEncoder().encode(options.body.toString()).byteLength;
15899
16169
  } else if (options.body instanceof FormData) {
16170
+ const boundaryOverhead = 76;
16171
+ for (const [name, value] of options.body) {
16172
+ bodySize2 += new TextEncoder().encode(name).byteLength + boundaryOverhead;
16173
+ if (typeof value === "string") {
16174
+ bodySize2 += new TextEncoder().encode(value).byteLength;
16175
+ } else {
16176
+ bodySize2 += value.size;
16177
+ }
16178
+ }
16179
+ bodySize2 += boundaryOverhead;
16180
+ } else if (options.body instanceof ReadableStream) {
16181
+ throw new KinetexError(`maxRequestSize cannot be enforced for ReadableStream bodies \u2014 pass maxRequestSize: 0 to opt out, or buffer the body first`, "EVALIDATION");
15900
16182
  } else if (options.body && typeof options.body === "object") {
15901
- bodySize2 = JSON.stringify(options.body).length;
16183
+ bodySize2 = new TextEncoder().encode(JSON.stringify(options.body)).byteLength;
15902
16184
  }
15903
16185
  if (bodySize2 > maxRequestSize) {
15904
16186
  throw new KinetexError(`Request body size ${bodySize2} bytes exceeds limit of ${maxRequestSize} bytes`, "EVALIDATION");
@@ -16078,10 +16360,26 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16078
16360
  async _sendFollowingRedirects(req, timeout, jar, appliedAuth) {
16079
16361
  const MAX_REDIRECTS = 20;
16080
16362
  let currentReq = { ...req, redirect: "manual" };
16363
+ const origin0 = (() => {
16364
+ try {
16365
+ return new URL(req.url).origin;
16366
+ } catch {
16367
+ return null;
16368
+ }
16369
+ })();
16081
16370
  const visited = /* @__PURE__ */ new Set();
16082
16371
  for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
16083
16372
  if (hop > 0 && appliedAuth) {
16084
- currentReq = await applyAuth(currentReq, appliedAuth);
16373
+ const sameOrigin = (() => {
16374
+ try {
16375
+ return new URL(currentReq.url).origin === origin0;
16376
+ } catch {
16377
+ return false;
16378
+ }
16379
+ })();
16380
+ if (sameOrigin) {
16381
+ currentReq = await applyAuth(currentReq, appliedAuth);
16382
+ }
16085
16383
  }
16086
16384
  if (hop > 0 && this.cfg.hooks?.onBeforeRequest) {
16087
16385
  for (const fn of this.cfg.hooks.onBeforeRequest) {
@@ -16153,6 +16451,14 @@ ${errors.map((e) => ` - ${e.message}`).join("\n")}`);
16153
16451
  } else {
16154
16452
  delete nextHeaders["cookie"];
16155
16453
  }
16454
+ try {
16455
+ if (new URL(nextUrl).origin !== new URL(currentReq.url).origin) {
16456
+ for (const h of CROSS_ORIGIN_STRIP_HEADERS) {
16457
+ delete nextHeaders[h];
16458
+ }
16459
+ }
16460
+ } catch {
16461
+ }
16156
16462
  currentReq = {
16157
16463
  ...currentReq,
16158
16464
  url: nextUrl,