@mcp-z/client 1.1.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 (60) hide show
  1. package/README.md +5 -2
  2. package/dist/cjs/auth/capability-discovery.js +13 -7
  3. package/dist/cjs/auth/capability-discovery.js.map +1 -1
  4. package/dist/cjs/auth/discovery-fetch.d.cts +35 -0
  5. package/dist/cjs/auth/discovery-fetch.d.ts +35 -0
  6. package/dist/cjs/auth/discovery-fetch.js +658 -0
  7. package/dist/cjs/auth/discovery-fetch.js.map +1 -0
  8. package/dist/cjs/auth/index.d.cts +1 -0
  9. package/dist/cjs/auth/index.d.ts +1 -0
  10. package/dist/cjs/auth/index.js +7 -0
  11. package/dist/cjs/auth/index.js.map +1 -1
  12. package/dist/cjs/auth/interactive-oauth-flow.d.cts +12 -12
  13. package/dist/cjs/auth/interactive-oauth-flow.d.ts +12 -12
  14. package/dist/cjs/auth/interactive-oauth-flow.js +22 -16
  15. package/dist/cjs/auth/interactive-oauth-flow.js.map +1 -1
  16. package/dist/cjs/auth/rfc9728-discovery.d.cts +12 -25
  17. package/dist/cjs/auth/rfc9728-discovery.d.ts +12 -25
  18. package/dist/cjs/auth/rfc9728-discovery.js +59 -48
  19. package/dist/cjs/auth/rfc9728-discovery.js.map +1 -1
  20. package/dist/cjs/auth/types.d.cts +16 -0
  21. package/dist/cjs/auth/types.d.ts +16 -0
  22. package/dist/cjs/auth/types.js.map +1 -1
  23. package/dist/cjs/dcr/dcr-authenticator.d.cts +3 -4
  24. package/dist/cjs/dcr/dcr-authenticator.d.ts +3 -4
  25. package/dist/cjs/dcr/dcr-authenticator.js +22 -12
  26. package/dist/cjs/dcr/dcr-authenticator.js.map +1 -1
  27. package/dist/cjs/dcr/dynamic-client-registrar.d.cts +6 -17
  28. package/dist/cjs/dcr/dynamic-client-registrar.d.ts +6 -17
  29. package/dist/cjs/dcr/dynamic-client-registrar.js +10 -16
  30. package/dist/cjs/dcr/dynamic-client-registrar.js.map +1 -1
  31. package/dist/cjs/index.d.cts +1 -0
  32. package/dist/cjs/index.d.ts +1 -0
  33. package/dist/cjs/index.js +7 -0
  34. package/dist/cjs/index.js.map +1 -1
  35. package/dist/esm/auth/capability-discovery.js +12 -6
  36. package/dist/esm/auth/capability-discovery.js.map +1 -1
  37. package/dist/esm/auth/discovery-fetch.d.ts +35 -0
  38. package/dist/esm/auth/discovery-fetch.js +184 -0
  39. package/dist/esm/auth/discovery-fetch.js.map +1 -0
  40. package/dist/esm/auth/index.d.ts +1 -0
  41. package/dist/esm/auth/index.js +1 -0
  42. package/dist/esm/auth/index.js.map +1 -1
  43. package/dist/esm/auth/interactive-oauth-flow.d.ts +12 -12
  44. package/dist/esm/auth/interactive-oauth-flow.js +25 -16
  45. package/dist/esm/auth/interactive-oauth-flow.js.map +1 -1
  46. package/dist/esm/auth/rfc9728-discovery.d.ts +12 -25
  47. package/dist/esm/auth/rfc9728-discovery.js +49 -56
  48. package/dist/esm/auth/rfc9728-discovery.js.map +1 -1
  49. package/dist/esm/auth/types.d.ts +16 -0
  50. package/dist/esm/auth/types.js.map +1 -1
  51. package/dist/esm/dcr/dcr-authenticator.d.ts +3 -4
  52. package/dist/esm/dcr/dcr-authenticator.js +20 -11
  53. package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
  54. package/dist/esm/dcr/dynamic-client-registrar.d.ts +6 -17
  55. package/dist/esm/dcr/dynamic-client-registrar.js +13 -18
  56. package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -1
  57. package/dist/esm/index.d.ts +1 -0
  58. package/dist/esm/index.js +1 -0
  59. package/dist/esm/index.js.map +1 -1
  60. package/package.json +2 -7
@@ -0,0 +1,658 @@
1
+ /**
2
+ * Hardened fetch for OAuth discovery URLs, which the remote MCP server
3
+ * controls (SSRF mitigation). See ARCHITECTURE.md's "SSRF mitigation" section for the threat model.
4
+ */ "use strict";
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ function _export(target, all) {
9
+ for(var name in all)Object.defineProperty(target, name, {
10
+ enumerable: true,
11
+ get: Object.getOwnPropertyDescriptor(all, name).get
12
+ });
13
+ }
14
+ _export(exports, {
15
+ get DiscoveryFetchError () {
16
+ return DiscoveryFetchError;
17
+ },
18
+ get discoveryFetch () {
19
+ return discoveryFetch;
20
+ },
21
+ get isLoopbackUrl () {
22
+ return isLoopbackUrl;
23
+ },
24
+ get readDiscoveryJson () {
25
+ return readDiscoveryJson;
26
+ }
27
+ });
28
+ var _promises = /*#__PURE__*/ _interop_require_default(require("node:dns/promises"));
29
+ var _nodenet = require("node:net");
30
+ var _ipaddr = /*#__PURE__*/ _interop_require_default(require("ipaddr.js"));
31
+ function _assert_this_initialized(self) {
32
+ if (self === void 0) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
33
+ return self;
34
+ }
35
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
36
+ try {
37
+ var info = gen[key](arg);
38
+ var value = info.value;
39
+ } catch (error) {
40
+ reject(error);
41
+ return;
42
+ }
43
+ if (info.done) resolve(value);
44
+ else Promise.resolve(value).then(_next, _throw);
45
+ }
46
+ function _async_to_generator(fn) {
47
+ return function() {
48
+ var self = this, args = arguments;
49
+ return new Promise(function(resolve, reject) {
50
+ var gen = fn.apply(self, args);
51
+ function _next(value) {
52
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
53
+ }
54
+ function _throw(err) {
55
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
56
+ }
57
+ _next(undefined);
58
+ });
59
+ };
60
+ }
61
+ function _call_super(_this, derived, args) {
62
+ derived = _get_prototype_of(derived);
63
+ return _possible_constructor_return(_this, _is_native_reflect_construct() ? Reflect.construct(derived, args || [], _get_prototype_of(_this).constructor) : derived.apply(_this, args));
64
+ }
65
+ function _class_call_check(instance, Constructor) {
66
+ if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
67
+ }
68
+ function _construct(Parent, args, Class) {
69
+ if (_is_native_reflect_construct()) _construct = Reflect.construct;
70
+ else {
71
+ _construct = function construct(Parent, args, Class) {
72
+ var a = [
73
+ null
74
+ ];
75
+ a.push.apply(a, args);
76
+ var Constructor = Function.bind.apply(Parent, a);
77
+ var instance = new Constructor();
78
+ if (Class) _set_prototype_of(instance, Class.prototype);
79
+ return instance;
80
+ };
81
+ }
82
+ return _construct.apply(null, arguments);
83
+ }
84
+ function _define_property(obj, key, value) {
85
+ if (key in obj) {
86
+ Object.defineProperty(obj, key, {
87
+ value: value,
88
+ enumerable: true,
89
+ configurable: true,
90
+ writable: true
91
+ });
92
+ } else obj[key] = value;
93
+ return obj;
94
+ }
95
+ function _get_prototype_of(o) {
96
+ _get_prototype_of = Object.setPrototypeOf ? Object.getPrototypeOf : function getPrototypeOf(o) {
97
+ return o.__proto__ || Object.getPrototypeOf(o);
98
+ };
99
+ return _get_prototype_of(o);
100
+ }
101
+ function _inherits(subClass, superClass) {
102
+ if (typeof superClass !== "function" && superClass !== null) {
103
+ throw new TypeError("Super expression must either be null or a function");
104
+ }
105
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
106
+ constructor: {
107
+ value: subClass,
108
+ writable: true,
109
+ configurable: true
110
+ }
111
+ });
112
+ if (superClass) _set_prototype_of(subClass, superClass);
113
+ }
114
+ function _interop_require_default(obj) {
115
+ return obj && obj.__esModule ? obj : {
116
+ default: obj
117
+ };
118
+ }
119
+ function _is_native_function(fn) {
120
+ return Function.toString.call(fn).indexOf("[native code]") !== -1;
121
+ }
122
+ function _is_native_reflect_construct() {
123
+ try {
124
+ var result = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {}));
125
+ } catch (_) {}
126
+ return (_is_native_reflect_construct = function() {
127
+ return !!result;
128
+ })();
129
+ }
130
+ function _object_spread(target) {
131
+ for(var i = 1; i < arguments.length; i++){
132
+ var source = arguments[i] != null ? arguments[i] : {};
133
+ var ownKeys = Object.keys(source);
134
+ if (typeof Object.getOwnPropertySymbols === "function") {
135
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
136
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
137
+ }));
138
+ }
139
+ ownKeys.forEach(function(key) {
140
+ _define_property(target, key, source[key]);
141
+ });
142
+ }
143
+ return target;
144
+ }
145
+ function ownKeys(object, enumerableOnly) {
146
+ var keys = Object.keys(object);
147
+ if (Object.getOwnPropertySymbols) {
148
+ var symbols = Object.getOwnPropertySymbols(object);
149
+ if (enumerableOnly) {
150
+ symbols = symbols.filter(function(sym) {
151
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
152
+ });
153
+ }
154
+ keys.push.apply(keys, symbols);
155
+ }
156
+ return keys;
157
+ }
158
+ function _object_spread_props(target, source) {
159
+ source = source != null ? source : {};
160
+ if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
161
+ else {
162
+ ownKeys(Object(source)).forEach(function(key) {
163
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
164
+ });
165
+ }
166
+ return target;
167
+ }
168
+ function _possible_constructor_return(self, call) {
169
+ if (call && (_type_of(call) === "object" || typeof call === "function")) return call;
170
+ return _assert_this_initialized(self);
171
+ }
172
+ function _set_prototype_of(o, p) {
173
+ _set_prototype_of = Object.setPrototypeOf || function setPrototypeOf(o, p) {
174
+ o.__proto__ = p;
175
+ return o;
176
+ };
177
+ return _set_prototype_of(o, p);
178
+ }
179
+ function _ts_generator(thisArg, body) {
180
+ var f, y, t, _ = {
181
+ label: 0,
182
+ sent: function() {
183
+ if (t[0] & 1) throw t[1];
184
+ return t[1];
185
+ },
186
+ trys: [],
187
+ ops: []
188
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
189
+ return d(g, "next", {
190
+ value: verb(0)
191
+ }), d(g, "throw", {
192
+ value: verb(1)
193
+ }), d(g, "return", {
194
+ value: verb(2)
195
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
196
+ value: function() {
197
+ return this;
198
+ }
199
+ }), g;
200
+ function verb(n) {
201
+ return function(v) {
202
+ return step([
203
+ n,
204
+ v
205
+ ]);
206
+ };
207
+ }
208
+ function step(op) {
209
+ if (f) throw new TypeError("Generator is already executing.");
210
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
211
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
212
+ if (y = 0, t) op = [
213
+ op[0] & 2,
214
+ t.value
215
+ ];
216
+ switch(op[0]){
217
+ case 0:
218
+ case 1:
219
+ t = op;
220
+ break;
221
+ case 4:
222
+ _.label++;
223
+ return {
224
+ value: op[1],
225
+ done: false
226
+ };
227
+ case 5:
228
+ _.label++;
229
+ y = op[1];
230
+ op = [
231
+ 0
232
+ ];
233
+ continue;
234
+ case 7:
235
+ op = _.ops.pop();
236
+ _.trys.pop();
237
+ continue;
238
+ default:
239
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
240
+ _ = 0;
241
+ continue;
242
+ }
243
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
244
+ _.label = op[1];
245
+ break;
246
+ }
247
+ if (op[0] === 6 && _.label < t[1]) {
248
+ _.label = t[1];
249
+ t = op;
250
+ break;
251
+ }
252
+ if (t && _.label < t[2]) {
253
+ _.label = t[2];
254
+ _.ops.push(op);
255
+ break;
256
+ }
257
+ if (t[2]) _.ops.pop();
258
+ _.trys.pop();
259
+ continue;
260
+ }
261
+ op = body.call(thisArg, _);
262
+ } catch (e) {
263
+ op = [
264
+ 6,
265
+ e
266
+ ];
267
+ y = 0;
268
+ } finally{
269
+ f = t = 0;
270
+ }
271
+ if (op[0] & 5) throw op[1];
272
+ return {
273
+ value: op[0] ? op[1] : void 0,
274
+ done: true
275
+ };
276
+ }
277
+ }
278
+ function _type_of(obj) {
279
+ "@swc/helpers - typeof";
280
+ return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
281
+ }
282
+ function _wrap_native_super(Class) {
283
+ var _cache = typeof Map === "function" ? new Map() : undefined;
284
+ _wrap_native_super = function(Class) {
285
+ if (Class === null || !_is_native_function(Class)) return Class;
286
+ if (typeof Class !== "function") throw new TypeError("Super expression must either be null or a function");
287
+ if (typeof _cache !== "undefined") {
288
+ if (_cache.has(Class)) return _cache.get(Class);
289
+ _cache.set(Class, Wrapper);
290
+ }
291
+ function Wrapper() {
292
+ return _construct(Class, arguments, _get_prototype_of(this).constructor);
293
+ }
294
+ Wrapper.prototype = Object.create(Class.prototype, {
295
+ constructor: {
296
+ value: Wrapper,
297
+ enumerable: false,
298
+ writable: true,
299
+ configurable: true
300
+ }
301
+ });
302
+ return _set_prototype_of(Wrapper, Class);
303
+ };
304
+ return _wrap_native_super(Class);
305
+ }
306
+ var DiscoveryFetchError = /*#__PURE__*/ function(Error1) {
307
+ "use strict";
308
+ _inherits(DiscoveryFetchError, Error1);
309
+ function DiscoveryFetchError(message) {
310
+ _class_call_check(this, DiscoveryFetchError);
311
+ var _this;
312
+ _this = _call_super(this, DiscoveryFetchError, [
313
+ message
314
+ ]);
315
+ _this.name = 'DiscoveryFetchError';
316
+ return _this;
317
+ }
318
+ return DiscoveryFetchError;
319
+ }(_wrap_native_super(Error));
320
+ var DEFAULT_TIMEOUT_MS = 10000;
321
+ var MAX_REDIRECTS = 5;
322
+ var MAX_BODY_BYTES = 1000000; // 1 MB - discovery documents are small JSON
323
+ function stripBrackets(hostname) {
324
+ return hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;
325
+ }
326
+ /** True for the literal hostname `localhost` or an IP literal in the loopback range. */ function isLoopbackHost(hostname) {
327
+ var host = stripBrackets(hostname).toLowerCase();
328
+ if (host === 'localhost') return true;
329
+ if ((0, _nodenet.isIP)(host)) {
330
+ try {
331
+ return _ipaddr.default.process(host).range() === 'loopback';
332
+ } catch (unused) {
333
+ return false;
334
+ }
335
+ }
336
+ return false;
337
+ }
338
+ function isLoopbackUrl(rawUrl) {
339
+ try {
340
+ return isLoopbackHost(new URL(rawUrl).hostname);
341
+ } catch (unused) {
342
+ return false;
343
+ }
344
+ }
345
+ /** True when the address is anything other than ordinary public unicast space. */ function isBlockedAddress(address) {
346
+ try {
347
+ // ipaddr.process() normalizes IPv4-mapped IPv6 (::ffff:a.b.c.d) to IPv4 first.
348
+ return _ipaddr.default.process(address).range() !== 'unicast';
349
+ } catch (unused) {
350
+ return true; // unparseable - fail closed
351
+ }
352
+ }
353
+ /**
354
+ * Validates scheme and, for literal-IP hosts, address range. `allowLoopback`
355
+ * reflects trust in the calling server, never in `rawUrl` itself.
356
+ */ function assertSafeUrl(rawUrl, context, allowLoopback) {
357
+ var url;
358
+ try {
359
+ url = new URL(rawUrl);
360
+ } catch (unused) {
361
+ throw new DiscoveryFetchError("Refusing to fetch ".concat(context, ": invalid URL"));
362
+ }
363
+ if (url.protocol !== 'https:' && url.protocol !== 'http:') {
364
+ throw new DiscoveryFetchError("Refusing to fetch ".concat(context, ": unsupported scheme"));
365
+ }
366
+ var host = stripBrackets(url.hostname);
367
+ var trustedLoopback = allowLoopback && isLoopbackHost(host);
368
+ if (trustedLoopback) return url; // this call's own grant covers it, regardless of scheme
369
+ if (url.protocol === 'http:') {
370
+ throw new DiscoveryFetchError("Refusing to fetch ".concat(context, ": http:// is only allowed for a trusted loopback origin"));
371
+ }
372
+ if ((0, _nodenet.isIP)(host) && isBlockedAddress(host)) {
373
+ throw new DiscoveryFetchError("Refusing to fetch ".concat(context, ": address is not publicly routable"));
374
+ }
375
+ return url;
376
+ }
377
+ /**
378
+ * Resolves the hostname and rejects it if any address is private/reserved.
379
+ * Does not close the DNS-rebinding TOCTOU race against `fetch`'s own resolution a moment later.
380
+ */ function assertResolvesToSafeAddress(url, context, allowLoopback) {
381
+ return _async_to_generator(function() {
382
+ var host, addresses, records, unused;
383
+ return _ts_generator(this, function(_state) {
384
+ switch(_state.label){
385
+ case 0:
386
+ host = stripBrackets(url.hostname);
387
+ if ((0, _nodenet.isIP)(host)) return [
388
+ 2
389
+ ]; // literal IP already fully checked in assertSafeUrl
390
+ if (allowLoopback && isLoopbackHost(host)) return [
391
+ 2
392
+ ]; // literal "localhost" under a trusted-loopback grant
393
+ _state.label = 1;
394
+ case 1:
395
+ _state.trys.push([
396
+ 1,
397
+ 3,
398
+ ,
399
+ 4
400
+ ]);
401
+ return [
402
+ 4,
403
+ _promises.default.lookup(host, {
404
+ all: true,
405
+ verbatim: true
406
+ })
407
+ ];
408
+ case 2:
409
+ records = _state.sent();
410
+ addresses = records.map(function(record) {
411
+ return record.address;
412
+ });
413
+ return [
414
+ 3,
415
+ 4
416
+ ];
417
+ case 3:
418
+ unused = _state.sent();
419
+ throw new DiscoveryFetchError("Refusing to fetch ".concat(context, ": host could not be resolved"));
420
+ case 4:
421
+ if (addresses.length === 0 || addresses.some(function(address) {
422
+ return isBlockedAddress(address);
423
+ })) {
424
+ throw new DiscoveryFetchError("Refusing to fetch ".concat(context, ": host resolves to an address that is not publicly routable"));
425
+ }
426
+ return [
427
+ 2
428
+ ];
429
+ }
430
+ });
431
+ })();
432
+ }
433
+ function readLimited(response, context, maxBytes) {
434
+ return _async_to_generator(function() {
435
+ var _response_body, contentLength, reader, text, chunks, total, _ref, done, value;
436
+ return _ts_generator(this, function(_state) {
437
+ switch(_state.label){
438
+ case 0:
439
+ contentLength = response.headers.get('content-length');
440
+ if (contentLength && Number(contentLength) > maxBytes) {
441
+ throw new DiscoveryFetchError("Refusing to read ".concat(context, ": response too large"));
442
+ }
443
+ reader = (_response_body = response.body) === null || _response_body === void 0 ? void 0 : _response_body.getReader();
444
+ if (!!reader) return [
445
+ 3,
446
+ 2
447
+ ];
448
+ return [
449
+ 4,
450
+ response.text()
451
+ ];
452
+ case 1:
453
+ text = _state.sent();
454
+ if (Buffer.byteLength(text, 'utf8') > maxBytes) {
455
+ throw new DiscoveryFetchError("Refusing to read ".concat(context, ": response too large"));
456
+ }
457
+ return [
458
+ 2,
459
+ text
460
+ ];
461
+ case 2:
462
+ chunks = [];
463
+ total = 0;
464
+ _state.label = 3;
465
+ case 3:
466
+ _state.trys.push([
467
+ 3,
468
+ ,
469
+ 8,
470
+ 9
471
+ ]);
472
+ _state.label = 4;
473
+ case 4:
474
+ return [
475
+ 4,
476
+ reader.read()
477
+ ];
478
+ case 5:
479
+ _ref = _state.sent(), done = _ref.done, value = _ref.value;
480
+ if (done) return [
481
+ 3,
482
+ 7
483
+ ];
484
+ if (!value) return [
485
+ 3,
486
+ 6
487
+ ];
488
+ total += value.byteLength;
489
+ if (total > maxBytes) {
490
+ throw new DiscoveryFetchError("Refusing to read ".concat(context, ": response too large"));
491
+ }
492
+ chunks.push(value);
493
+ _state.label = 6;
494
+ case 6:
495
+ return [
496
+ 3,
497
+ 4
498
+ ];
499
+ case 7:
500
+ return [
501
+ 3,
502
+ 9
503
+ ];
504
+ case 8:
505
+ reader.releaseLock();
506
+ return [
507
+ 7
508
+ ];
509
+ case 9:
510
+ return [
511
+ 2,
512
+ Buffer.concat(chunks.map(function(chunk) {
513
+ return Buffer.from(chunk);
514
+ })).toString('utf8')
515
+ ];
516
+ }
517
+ });
518
+ })();
519
+ }
520
+ function fetchOnce(url, init, context, allowLoopback, timeoutMs) {
521
+ return _async_to_generator(function() {
522
+ var controller, timeout, unused;
523
+ return _ts_generator(this, function(_state) {
524
+ switch(_state.label){
525
+ case 0:
526
+ return [
527
+ 4,
528
+ assertResolvesToSafeAddress(url, context, allowLoopback)
529
+ ];
530
+ case 1:
531
+ _state.sent();
532
+ controller = new AbortController();
533
+ timeout = setTimeout(function() {
534
+ return controller.abort();
535
+ }, timeoutMs);
536
+ _state.label = 2;
537
+ case 2:
538
+ _state.trys.push([
539
+ 2,
540
+ 4,
541
+ 5,
542
+ 6
543
+ ]);
544
+ return [
545
+ 4,
546
+ fetch(url, _object_spread_props(_object_spread({}, init), {
547
+ redirect: 'manual',
548
+ signal: controller.signal
549
+ }))
550
+ ];
551
+ case 3:
552
+ return [
553
+ 2,
554
+ _state.sent()
555
+ ];
556
+ case 4:
557
+ unused = _state.sent();
558
+ // Never surface the underlying error (ECONNREFUSED host/port, DNS detail, etc.)
559
+ throw new DiscoveryFetchError("Failed to fetch ".concat(context));
560
+ case 5:
561
+ clearTimeout(timeout);
562
+ return [
563
+ 7
564
+ ];
565
+ case 6:
566
+ return [
567
+ 2
568
+ ];
569
+ }
570
+ });
571
+ })();
572
+ }
573
+ function discoveryFetch(_0) {
574
+ return _async_to_generator(function(rawUrl) {
575
+ var init, context, options, _options_allowLoopback, allowLoopback, _options_timeoutMs, timeoutMs, url, response, redirectsLeft, location, nextUrl;
576
+ var _arguments = arguments;
577
+ return _ts_generator(this, function(_state) {
578
+ switch(_state.label){
579
+ case 0:
580
+ init = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : 'discovery URL', options = _arguments.length > 3 && _arguments[3] !== void 0 ? _arguments[3] : {};
581
+ _options_allowLoopback = options.allowLoopback, allowLoopback = _options_allowLoopback === void 0 ? false : _options_allowLoopback, _options_timeoutMs = options.timeoutMs, timeoutMs = _options_timeoutMs === void 0 ? DEFAULT_TIMEOUT_MS : _options_timeoutMs;
582
+ url = assertSafeUrl(rawUrl, context, allowLoopback);
583
+ return [
584
+ 4,
585
+ fetchOnce(url, init, context, allowLoopback, timeoutMs)
586
+ ];
587
+ case 1:
588
+ response = _state.sent();
589
+ redirectsLeft = MAX_REDIRECTS;
590
+ _state.label = 2;
591
+ case 2:
592
+ if (!(response.status >= 300 && response.status < 400)) return [
593
+ 3,
594
+ 4
595
+ ];
596
+ location = response.headers.get('location');
597
+ if (!location) return [
598
+ 3,
599
+ 4
600
+ ];
601
+ if (redirectsLeft <= 0) {
602
+ throw new DiscoveryFetchError("Refusing to fetch ".concat(context, ": too many redirects"));
603
+ }
604
+ redirectsLeft -= 1;
605
+ nextUrl = void 0;
606
+ try {
607
+ nextUrl = new URL(location, url);
608
+ } catch (unused) {
609
+ throw new DiscoveryFetchError("Refusing to fetch ".concat(context, ": invalid redirect target"));
610
+ }
611
+ url = assertSafeUrl(nextUrl.toString(), context, allowLoopback);
612
+ return [
613
+ 4,
614
+ fetchOnce(url, init, context, allowLoopback, timeoutMs)
615
+ ];
616
+ case 3:
617
+ response = _state.sent();
618
+ return [
619
+ 3,
620
+ 2
621
+ ];
622
+ case 4:
623
+ return [
624
+ 2,
625
+ response
626
+ ];
627
+ }
628
+ });
629
+ }).apply(this, arguments);
630
+ }
631
+ function readDiscoveryJson(response, context) {
632
+ return _async_to_generator(function() {
633
+ var text;
634
+ return _ts_generator(this, function(_state) {
635
+ switch(_state.label){
636
+ case 0:
637
+ return [
638
+ 4,
639
+ readLimited(response, context, MAX_BODY_BYTES)
640
+ ];
641
+ case 1:
642
+ text = _state.sent();
643
+ try {
644
+ return [
645
+ 2,
646
+ JSON.parse(text)
647
+ ];
648
+ } catch (unused) {
649
+ throw new DiscoveryFetchError("Refusing to parse ".concat(context, ": invalid JSON"));
650
+ }
651
+ return [
652
+ 2
653
+ ];
654
+ }
655
+ });
656
+ })();
657
+ }
658
+ /* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/discovery-fetch.ts"],"sourcesContent":["/**\n * Hardened fetch for OAuth discovery URLs, which the remote MCP server\n * controls (SSRF mitigation). See ARCHITECTURE.md's \"SSRF mitigation\" section for the threat model.\n */\nimport dns from 'node:dns/promises';\nimport { isIP } from 'node:net';\nimport ipaddr from 'ipaddr.js';\n\nexport class DiscoveryFetchError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'DiscoveryFetchError';\n }\n}\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst MAX_REDIRECTS = 5;\nconst MAX_BODY_BYTES = 1_000_000; // 1 MB - discovery documents are small JSON\n\nfunction stripBrackets(hostname: string): string {\n return hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;\n}\n\n/** True for the literal hostname `localhost` or an IP literal in the loopback range. */\nfunction isLoopbackHost(hostname: string): boolean {\n const host = stripBrackets(hostname).toLowerCase();\n if (host === 'localhost') return true;\n if (isIP(host)) {\n try {\n return ipaddr.process(host).range() === 'loopback';\n } catch {\n return false;\n }\n }\n return false;\n}\n\n/**\n * True if `rawUrl`'s host is loopback, used to decide `allowLoopback` grants\n * from the URL the caller configured, not remote-supplied data. Fails closed on an unparseable URL.\n */\nexport function isLoopbackUrl(rawUrl: string): boolean {\n try {\n return isLoopbackHost(new URL(rawUrl).hostname);\n } catch {\n return false;\n }\n}\n\n/** True when the address is anything other than ordinary public unicast space. */\nfunction isBlockedAddress(address: string): boolean {\n try {\n // ipaddr.process() normalizes IPv4-mapped IPv6 (::ffff:a.b.c.d) to IPv4 first.\n return ipaddr.process(address).range() !== 'unicast';\n } catch {\n return true; // unparseable - fail closed\n }\n}\n\n/**\n * Validates scheme and, for literal-IP hosts, address range. `allowLoopback`\n * reflects trust in the calling server, never in `rawUrl` itself.\n */\nfunction assertSafeUrl(rawUrl: string, context: string, allowLoopback: boolean): URL {\n let url: URL;\n try {\n url = new URL(rawUrl);\n } catch {\n throw new DiscoveryFetchError(`Refusing to fetch ${context}: invalid URL`);\n }\n\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new DiscoveryFetchError(`Refusing to fetch ${context}: unsupported scheme`);\n }\n\n const host = stripBrackets(url.hostname);\n const trustedLoopback = allowLoopback && isLoopbackHost(host);\n if (trustedLoopback) return url; // this call's own grant covers it, regardless of scheme\n\n if (url.protocol === 'http:') {\n throw new DiscoveryFetchError(`Refusing to fetch ${context}: http:// is only allowed for a trusted loopback origin`);\n }\n\n if (isIP(host) && isBlockedAddress(host)) {\n throw new DiscoveryFetchError(`Refusing to fetch ${context}: address is not publicly routable`);\n }\n\n return url;\n}\n\n/**\n * Resolves the hostname and rejects it if any address is private/reserved.\n * Does not close the DNS-rebinding TOCTOU race against `fetch`'s own resolution a moment later.\n */\nasync function assertResolvesToSafeAddress(url: URL, context: string, allowLoopback: boolean): Promise<void> {\n const host = stripBrackets(url.hostname);\n if (isIP(host)) return; // literal IP already fully checked in assertSafeUrl\n if (allowLoopback && isLoopbackHost(host)) return; // literal \"localhost\" under a trusted-loopback grant\n\n let addresses: string[];\n try {\n const records = await dns.lookup(host, { all: true, verbatim: true });\n addresses = records.map((record) => record.address);\n } catch {\n throw new DiscoveryFetchError(`Refusing to fetch ${context}: host could not be resolved`);\n }\n\n if (addresses.length === 0 || addresses.some((address) => isBlockedAddress(address))) {\n throw new DiscoveryFetchError(`Refusing to fetch ${context}: host resolves to an address that is not publicly routable`);\n }\n}\n\nasync function readLimited(response: Response, context: string, maxBytes: number): Promise<string> {\n const contentLength = response.headers.get('content-length');\n if (contentLength && Number(contentLength) > maxBytes) {\n throw new DiscoveryFetchError(`Refusing to read ${context}: response too large`);\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n const text = await response.text();\n if (Buffer.byteLength(text, 'utf8') > maxBytes) {\n throw new DiscoveryFetchError(`Refusing to read ${context}: response too large`);\n }\n return text;\n }\n\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (!value) continue;\n total += value.byteLength;\n if (total > maxBytes) {\n throw new DiscoveryFetchError(`Refusing to read ${context}: response too large`);\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n\n return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))).toString('utf8');\n}\n\nasync function fetchOnce(url: URL, init: RequestInit, context: string, allowLoopback: boolean, timeoutMs: number): Promise<Response> {\n await assertResolvesToSafeAddress(url, context, allowLoopback);\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n try {\n return await fetch(url, { ...init, redirect: 'manual', signal: controller.signal });\n } catch {\n // Never surface the underlying error (ECONNREFUSED host/port, DNS detail, etc.)\n throw new DiscoveryFetchError(`Failed to fetch ${context}`);\n } finally {\n clearTimeout(timeout);\n }\n}\n\nexport interface DiscoveryFetchOptions {\n /**\n * Loopback trust grant for this fetch, computed from the server the caller\n * is actually talking to - never from the URL being fetched. Defaults to `false`.\n */\n allowLoopback?: boolean;\n /**\n * Per-request connect/read timeout in ms. Defaults to `DEFAULT_TIMEOUT_MS`;\n * overridable so tests can bound slow-failure cases.\n */\n timeoutMs?: number;\n}\n\n/**\n * Fetches an OAuth-discovery URL with SSRF mitigations applied to the\n * initial URL and every redirect hop; redirects are validated, not auto-followed.\n *\n * @param rawUrl - URL to fetch; may be remote-server-supplied (the threat this guards against).\n * @param init - Standard fetch options; `redirect` is always forced to `'manual'`.\n * @param context - Short label used only in error messages, never echoing the URL.\n * @param options - See `DiscoveryFetchOptions`.\n */\nexport async function discoveryFetch(rawUrl: string, init: RequestInit = {}, context = 'discovery URL', options: DiscoveryFetchOptions = {}): Promise<Response> {\n const { allowLoopback = false, timeoutMs = DEFAULT_TIMEOUT_MS } = options;\n\n let url = assertSafeUrl(rawUrl, context, allowLoopback);\n let response = await fetchOnce(url, init, context, allowLoopback, timeoutMs);\n\n let redirectsLeft = MAX_REDIRECTS;\n while (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n if (!location) break;\n if (redirectsLeft <= 0) {\n throw new DiscoveryFetchError(`Refusing to fetch ${context}: too many redirects`);\n }\n redirectsLeft -= 1;\n\n let nextUrl: URL;\n try {\n nextUrl = new URL(location, url);\n } catch {\n throw new DiscoveryFetchError(`Refusing to fetch ${context}: invalid redirect target`);\n }\n url = assertSafeUrl(nextUrl.toString(), context, allowLoopback);\n response = await fetchOnce(url, init, context, allowLoopback, timeoutMs);\n }\n\n return response;\n}\n\n/**\n * Parse a `discoveryFetch` response body as JSON, enforcing `MAX_BODY_BYTES`\n * while reading (not after buffering the whole thing).\n */\nexport async function readDiscoveryJson<T>(response: Response, context: string): Promise<T> {\n const text = await readLimited(response, context, MAX_BODY_BYTES);\n try {\n return JSON.parse(text) as T;\n } catch {\n throw new DiscoveryFetchError(`Refusing to parse ${context}: invalid JSON`);\n }\n}\n"],"names":["DiscoveryFetchError","discoveryFetch","isLoopbackUrl","readDiscoveryJson","message","name","Error","DEFAULT_TIMEOUT_MS","MAX_REDIRECTS","MAX_BODY_BYTES","stripBrackets","hostname","startsWith","endsWith","slice","isLoopbackHost","host","toLowerCase","isIP","ipaddr","process","range","rawUrl","URL","isBlockedAddress","address","assertSafeUrl","context","allowLoopback","url","protocol","trustedLoopback","assertResolvesToSafeAddress","addresses","records","dns","lookup","all","verbatim","map","record","length","some","readLimited","response","maxBytes","contentLength","reader","text","chunks","total","done","value","headers","get","Number","body","getReader","Buffer","byteLength","read","push","releaseLock","concat","chunk","from","toString","fetchOnce","init","timeoutMs","controller","timeout","AbortController","setTimeout","abort","fetch","redirect","signal","clearTimeout","options","redirectsLeft","location","nextUrl","status","JSON","parse"],"mappings":"AAAA;;;CAGC;;;;;;;;;;;QAKYA;eAAAA;;QAgLSC;eAAAA;;QA/INC;eAAAA;;QA+KMC;eAAAA;;;+DApNN;uBACK;6DACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEZ,IAAA,AAAMH,oCAAN;;cAAMA;aAAAA,oBACCI,OAAe;gCADhBJ;;gBAET,kBAFSA;YAEHI;;QACN,MAAKC,IAAI,GAAG;;;WAHHL;qBAA4BM;AAOzC,IAAMC,qBAAqB;AAC3B,IAAMC,gBAAgB;AACtB,IAAMC,iBAAiB,SAAW,4CAA4C;AAE9E,SAASC,cAAcC,QAAgB;IACrC,OAAOA,SAASC,UAAU,CAAC,QAAQD,SAASE,QAAQ,CAAC,OAAOF,SAASG,KAAK,CAAC,GAAG,CAAC,KAAKH;AACtF;AAEA,sFAAsF,GACtF,SAASI,eAAeJ,QAAgB;IACtC,IAAMK,OAAON,cAAcC,UAAUM,WAAW;IAChD,IAAID,SAAS,aAAa,OAAO;IACjC,IAAIE,IAAAA,aAAI,EAACF,OAAO;QACd,IAAI;YACF,OAAOG,eAAM,CAACC,OAAO,CAACJ,MAAMK,KAAK,OAAO;QAC1C,EAAE,eAAM;YACN,OAAO;QACT;IACF;IACA,OAAO;AACT;AAMO,SAASnB,cAAcoB,MAAc;IAC1C,IAAI;QACF,OAAOP,eAAe,IAAIQ,IAAID,QAAQX,QAAQ;IAChD,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA,gFAAgF,GAChF,SAASa,iBAAiBC,OAAe;IACvC,IAAI;QACF,+EAA+E;QAC/E,OAAON,eAAM,CAACC,OAAO,CAACK,SAASJ,KAAK,OAAO;IAC7C,EAAE,eAAM;QACN,OAAO,MAAM,4BAA4B;IAC3C;AACF;AAEA;;;CAGC,GACD,SAASK,cAAcJ,MAAc,EAAEK,OAAe,EAAEC,aAAsB;IAC5E,IAAIC;IACJ,IAAI;QACFA,MAAM,IAAIN,IAAID;IAChB,EAAE,eAAM;QACN,MAAM,IAAItB,oBAAoB,AAAC,qBAA4B,OAAR2B,SAAQ;IAC7D;IAEA,IAAIE,IAAIC,QAAQ,KAAK,YAAYD,IAAIC,QAAQ,KAAK,SAAS;QACzD,MAAM,IAAI9B,oBAAoB,AAAC,qBAA4B,OAAR2B,SAAQ;IAC7D;IAEA,IAAMX,OAAON,cAAcmB,IAAIlB,QAAQ;IACvC,IAAMoB,kBAAkBH,iBAAiBb,eAAeC;IACxD,IAAIe,iBAAiB,OAAOF,KAAK,wDAAwD;IAEzF,IAAIA,IAAIC,QAAQ,KAAK,SAAS;QAC5B,MAAM,IAAI9B,oBAAoB,AAAC,qBAA4B,OAAR2B,SAAQ;IAC7D;IAEA,IAAIT,IAAAA,aAAI,EAACF,SAASQ,iBAAiBR,OAAO;QACxC,MAAM,IAAIhB,oBAAoB,AAAC,qBAA4B,OAAR2B,SAAQ;IAC7D;IAEA,OAAOE;AACT;AAEA;;;CAGC,GACD,SAAeG,4BAA4BH,GAAQ,EAAEF,OAAe,EAAEC,aAAsB;;YACpFZ,MAIFiB,WAEIC;;;;oBANFlB,OAAON,cAAcmB,IAAIlB,QAAQ;oBACvC,IAAIO,IAAAA,aAAI,EAACF,OAAO;;uBAAQ,oDAAoD;oBAC5E,IAAIY,iBAAiBb,eAAeC,OAAO;;uBAAQ,qDAAqD;;;;;;;;;oBAItF;;wBAAMmB,iBAAG,CAACC,MAAM,CAACpB,MAAM;4BAAEqB,KAAK;4BAAMC,UAAU;wBAAK;;;oBAA7DJ,UAAU;oBAChBD,YAAYC,QAAQK,GAAG,CAAC,SAACC;+BAAWA,OAAOf,OAAO;;;;;;;;oBAElD,MAAM,IAAIzB,oBAAoB,AAAC,qBAA4B,OAAR2B,SAAQ;;oBAG7D,IAAIM,UAAUQ,MAAM,KAAK,KAAKR,UAAUS,IAAI,CAAC,SAACjB;+BAAYD,iBAAiBC;wBAAW;wBACpF,MAAM,IAAIzB,oBAAoB,AAAC,qBAA4B,OAAR2B,SAAQ;oBAC7D;;;;;;IACF;;AAEA,SAAegB,YAAYC,QAAkB,EAAEjB,OAAe,EAAEkB,QAAgB;;YAM/DD,gBALTE,eAKAC,QAEEC,MAOFC,QACFC,OAGwB,MAAhBC,MAAMC;;;;oBAlBZN,gBAAgBF,SAASS,OAAO,CAACC,GAAG,CAAC;oBAC3C,IAAIR,iBAAiBS,OAAOT,iBAAiBD,UAAU;wBACrD,MAAM,IAAI7C,oBAAoB,AAAC,oBAA2B,OAAR2B,SAAQ;oBAC5D;oBAEMoB,UAASH,iBAAAA,SAASY,IAAI,cAAbZ,qCAAAA,eAAea,SAAS;yBACnC,CAACV,QAAD;;;;oBACW;;wBAAMH,SAASI,IAAI;;;oBAA1BA,OAAO;oBACb,IAAIU,OAAOC,UAAU,CAACX,MAAM,UAAUH,UAAU;wBAC9C,MAAM,IAAI7C,oBAAoB,AAAC,oBAA2B,OAAR2B,SAAQ;oBAC5D;oBACA;;wBAAOqB;;;oBAGHC;oBACFC,QAAQ;;;;;;;;;;;oBAGgB;;wBAAMH,OAAOa,IAAI;;;oBAAjB,OAAA,eAAhBT,OAAgB,KAAhBA,MAAMC,QAAU,KAAVA;oBACd,IAAID,MAAM;;;;oBACV,IAAI,CAACC,OAAO;;;;oBACZF,SAASE,MAAMO,UAAU;oBACzB,IAAIT,QAAQL,UAAU;wBACpB,MAAM,IAAI7C,oBAAoB,AAAC,oBAA2B,OAAR2B,SAAQ;oBAC5D;oBACAsB,OAAOY,IAAI,CAACT;;;;;;;;;;;;;oBAGdL,OAAOe,WAAW;;;;;oBAGpB;;wBAAOJ,OAAOK,MAAM,CAACd,OAAOV,GAAG,CAAC,SAACyB;mCAAUN,OAAOO,IAAI,CAACD;4BAASE,QAAQ,CAAC;;;;IAC3E;;AAEA,SAAeC,UAAUtC,GAAQ,EAAEuC,IAAiB,EAAEzC,OAAe,EAAEC,aAAsB,EAAEyC,SAAiB;;YAGxGC,YACAC;;;;oBAHN;;wBAAMvC,4BAA4BH,KAAKF,SAASC;;;oBAAhD;oBAEM0C,aAAa,IAAIE;oBACjBD,UAAUE,WAAW;+BAAMH,WAAWI,KAAK;uBAAIL;;;;;;;;;oBAE5C;;wBAAMM,MAAM9C,KAAK,wCAAKuC;4BAAMQ,UAAU;4BAAUC,QAAQP,WAAWO,MAAM;;;;oBAAhF;;wBAAO;;;;oBAEP,gFAAgF;oBAChF,MAAM,IAAI7E,oBAAoB,AAAC,mBAA0B,OAAR2B;;oBAEjDmD,aAAaP;;;;;;;;;;IAEjB;;AAwBO,SAAetE;wCAAeqB,MAAc;YAAE8C,MAAwBzC,SAA2BoD,iCAC9FnD,mCAAuByC,WAE3BxC,KACAe,UAEAoC,eAEIC,UAOFC;;;;;oBAf6Cd,OAAAA,oEAAoB,CAAC,GAAGzC,UAAAA,oEAAU,iBAAiBoD,UAAAA,oEAAiC,CAAC;6CACtEA,QAA1DnD,eAAAA,oDAAgB,qDAA0CmD,QAAnCV,WAAAA,4CAAY9D;oBAEvCsB,MAAMH,cAAcJ,QAAQK,SAASC;oBAC1B;;wBAAMuC,UAAUtC,KAAKuC,MAAMzC,SAASC,eAAeyC;;;oBAA9DzB,WAAW;oBAEXoC,gBAAgBxE;;;yBACboC,CAAAA,SAASuC,MAAM,IAAI,OAAOvC,SAASuC,MAAM,GAAG,GAAE;;;;oBAC7CF,WAAWrC,SAASS,OAAO,CAACC,GAAG,CAAC;oBACtC,IAAI,CAAC2B,UAAU;;;;oBACf,IAAID,iBAAiB,GAAG;wBACtB,MAAM,IAAIhF,oBAAoB,AAAC,qBAA4B,OAAR2B,SAAQ;oBAC7D;oBACAqD,iBAAiB;oBAEbE,UAAAA,KAAAA;oBACJ,IAAI;wBACFA,UAAU,IAAI3D,IAAI0D,UAAUpD;oBAC9B,EAAE,eAAM;wBACN,MAAM,IAAI7B,oBAAoB,AAAC,qBAA4B,OAAR2B,SAAQ;oBAC7D;oBACAE,MAAMH,cAAcwD,QAAQhB,QAAQ,IAAIvC,SAASC;oBACtC;;wBAAMuC,UAAUtC,KAAKuC,MAAMzC,SAASC,eAAeyC;;;oBAA9DzB,WAAW;;;;;;oBAGb;;wBAAOA;;;;IACT;;AAMO,SAAezC,kBAAqByC,QAAkB,EAAEjB,OAAe;;YACtEqB;;;;oBAAO;;wBAAML,YAAYC,UAAUjB,SAASlB;;;oBAA5CuC,OAAO;oBACb,IAAI;wBACF;;4BAAOoC,KAAKC,KAAK,CAACrC;;oBACpB,EAAE,eAAM;wBACN,MAAM,IAAIhD,oBAAoB,AAAC,qBAA4B,OAAR2B,SAAQ;oBAC7D;;;;;;IACF"}