@ai-sdk/provider-utils 4.0.40 → 4.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @ai-sdk/provider-utils
2
2
 
3
+ ## 4.0.42
4
+
5
+ ### Patch Changes
6
+
7
+ - ee2bf30: fix(provider-utils): prevent Metro from parsing the Node 18 dynamic import fallback
8
+
9
+ ## 4.0.41
10
+
11
+ ### Patch Changes
12
+
13
+ - 9ecdefe: Prevent validated downloads on Node.js from reaching private or internal services through DNS aliases or DNS rebinding by validating and pinning every resolved address at connection time.
14
+
3
15
  ## 4.0.40
4
16
 
5
17
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -223,6 +223,11 @@ declare function readResponseWithSizeLimit({ response, url, maxBytes, }: {
223
223
  * The returned response is the final (non-redirect) response. The caller is
224
224
  * responsible for checking `response.ok` and reading the body.
225
225
  *
226
+ * On Node.js, the default fetch resolves every hostname through a validating
227
+ * lookup hook and passes those exact addresses to the connector, preventing
228
+ * hostname-to-private-IP and DNS-rebinding bypasses. Other runtimes should
229
+ * constrain egress at the network layer when handling untrusted URLs.
230
+ *
226
231
  * @throws DownloadError if a hop is unsafe, the redirect limit is exceeded, or
227
232
  * a redirect cannot be validated on a non-browser runtime.
228
233
  */
@@ -1411,9 +1416,8 @@ declare function convertToBase64(value: string | Uint8Array): string;
1411
1416
  * Validates that a URL is safe to download from, blocking private/internal addresses
1412
1417
  * to prevent SSRF attacks.
1413
1418
  *
1414
- * Note: this performs string/literal-IP checks only. It does not resolve DNS, so a
1415
- * hostname that resolves to a private address is not blocked here (see callers, which
1416
- * should additionally constrain egress at the network layer when handling untrusted URLs).
1419
+ * Note: this function performs string/literal-IP checks only. The Node.js
1420
+ * download fetch additionally validates and pins DNS results at connect time.
1417
1421
  *
1418
1422
  * @param url - The URL string to validate.
1419
1423
  * @throws DownloadError if the URL is unsafe.
package/dist/index.d.ts CHANGED
@@ -223,6 +223,11 @@ declare function readResponseWithSizeLimit({ response, url, maxBytes, }: {
223
223
  * The returned response is the final (non-redirect) response. The caller is
224
224
  * responsible for checking `response.ok` and reading the body.
225
225
  *
226
+ * On Node.js, the default fetch resolves every hostname through a validating
227
+ * lookup hook and passes those exact addresses to the connector, preventing
228
+ * hostname-to-private-IP and DNS-rebinding bypasses. Other runtimes should
229
+ * constrain egress at the network layer when handling untrusted URLs.
230
+ *
226
231
  * @throws DownloadError if a hop is unsafe, the redirect limit is exceeded, or
227
232
  * a redirect cannot be validated on a non-browser runtime.
228
233
  */
@@ -1411,9 +1416,8 @@ declare function convertToBase64(value: string | Uint8Array): string;
1411
1416
  * Validates that a URL is safe to download from, blocking private/internal addresses
1412
1417
  * to prevent SSRF attacks.
1413
1418
  *
1414
- * Note: this performs string/literal-IP checks only. It does not resolve DNS, so a
1415
- * hostname that resolves to a private address is not blocked here (see callers, which
1416
- * should additionally constrain egress at the network layer when handling untrusted URLs).
1419
+ * Note: this function performs string/literal-IP checks only. The Node.js
1420
+ * download fetch additionally validates and pins DNS results at connect time.
1417
1421
  *
1418
1422
  * @param url - The URL string to validate.
1419
1423
  * @throws DownloadError if the URL is unsafe.
package/dist/index.js CHANGED
@@ -402,6 +402,19 @@ function validateDownloadUrl(url) {
402
402
  return;
403
403
  }
404
404
  }
405
+ function validateDownloadAddress({
406
+ address,
407
+ family,
408
+ hostname
409
+ }) {
410
+ const isUnsafe = family === 4 ? !isIPv4(address) || isPrivateIPv4(address) : family === 6 ? isPrivateIPv6(address) : true;
411
+ if (isUnsafe) {
412
+ throw new DownloadError({
413
+ url: hostname,
414
+ message: `Hostname ${hostname} resolved to disallowed IP address ${address}`
415
+ });
416
+ }
417
+ }
405
418
  function isIPv4(hostname) {
406
419
  const parts = hostname.split(".");
407
420
  if (parts.length !== 4) return false;
@@ -488,6 +501,106 @@ function isPrivateIPv6(ip) {
488
501
  return false;
489
502
  }
490
503
 
504
+ // src/safe-node-fetch.ts
505
+ function createSafeLookup(lookup) {
506
+ return ((hostname, options, callback) => {
507
+ lookup(hostname, { ...options, all: true }, (error, addresses) => {
508
+ if (error) {
509
+ callback(error);
510
+ return;
511
+ }
512
+ try {
513
+ const [firstAddress] = addresses;
514
+ if (firstAddress == null) {
515
+ throw new Error(`Hostname ${hostname} did not resolve to an address`);
516
+ }
517
+ for (const { address, family } of addresses) {
518
+ validateDownloadAddress({ address, family, hostname });
519
+ }
520
+ if (options.all === true) {
521
+ callback(null, addresses);
522
+ } else {
523
+ callback(
524
+ null,
525
+ firstAddress.address,
526
+ firstAddress.family
527
+ );
528
+ }
529
+ } catch (error2) {
530
+ callback(
531
+ error2 instanceof Error ? error2 : new Error(String(error2))
532
+ );
533
+ }
534
+ });
535
+ });
536
+ }
537
+ var safeNodeFetchPromise;
538
+ var initialGlobalFetch = globalThis.fetch;
539
+ var initialGlobalFetchIsNodeDefault = isNodeDefaultFetch(initialGlobalFetch);
540
+ function isNodeRuntime() {
541
+ var _a2, _b2;
542
+ const runtimeProcess = globalThis.process;
543
+ return ((_a2 = runtimeProcess == null ? void 0 : runtimeProcess.release) == null ? void 0 : _a2.name) === "node" && ((_b2 = runtimeProcess.versions) == null ? void 0 : _b2.bun) == null;
544
+ }
545
+ async function getDefaultDownloadFetch() {
546
+ if (!isNodeRuntime() || !initialGlobalFetchIsNodeDefault || globalThis.fetch !== initialGlobalFetch) {
547
+ return globalThis.fetch;
548
+ }
549
+ return safeNodeFetchPromise != null ? safeNodeFetchPromise : safeNodeFetchPromise = createSafeNodeFetch();
550
+ }
551
+ function isNodeDefaultFetch(fetch) {
552
+ const source = Function.prototype.toString.call(fetch);
553
+ return source.includes("internal/deps/undici") || source.includes("lazy loading of undici");
554
+ }
555
+ async function createSafeNodeFetch() {
556
+ const [{ createRequire }, { lookup }] = await Promise.all([
557
+ loadNodeModule("node:module"),
558
+ loadNodeModule("node:dns")
559
+ ]);
560
+ const { Agent, fetch } = createRequire(getCurrentModulePath())(
561
+ "undici"
562
+ );
563
+ const dispatcher = new Agent({
564
+ connect: {
565
+ lookup: createSafeLookup(lookup)
566
+ }
567
+ });
568
+ return ((input, init) => fetch(
569
+ input,
570
+ {
571
+ ...init,
572
+ dispatcher
573
+ }
574
+ ));
575
+ }
576
+ async function loadNodeModule(id) {
577
+ var _a2;
578
+ const processWithBuiltins = globalThis.process;
579
+ const builtinModule = (_a2 = processWithBuiltins == null ? void 0 : processWithBuiltins.getBuiltinModule) == null ? void 0 : _a2.call(processWithBuiltins, id);
580
+ return builtinModule == null ? await importNodeModule(id) : builtinModule;
581
+ }
582
+ var dynamicImport;
583
+ function importNodeModule(id) {
584
+ dynamicImport != null ? dynamicImport : dynamicImport = Function("specifier", "return import(specifier)");
585
+ return dynamicImport(id);
586
+ }
587
+ function getCurrentModulePath() {
588
+ const originalPrepareStackTrace = Error.prepareStackTrace;
589
+ try {
590
+ Error.prepareStackTrace = (_error, callSites) => callSites;
591
+ const error = new Error("Capture current module path");
592
+ Error.captureStackTrace(error, getCurrentModulePath);
593
+ const [caller] = error.stack;
594
+ const fileName = caller == null ? void 0 : caller.getFileName();
595
+ if (fileName == null) {
596
+ throw new Error("Unable to determine the current module path");
597
+ }
598
+ return fileName;
599
+ } finally {
600
+ Error.prepareStackTrace = originalPrepareStackTrace;
601
+ }
602
+ }
603
+
491
604
  // src/fetch-with-validated-redirects.ts
492
605
  var MAX_DOWNLOAD_REDIRECTS = 10;
493
606
  async function fetchWithValidatedRedirects({
@@ -503,6 +616,7 @@ async function fetchWithValidatedRedirects({
503
616
  let currentUrl = url;
504
617
  for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount++) {
505
618
  validateDownloadUrl(currentUrl);
619
+ const fetch = await getDefaultDownloadFetch();
506
620
  const response = await fetch(currentUrl, {
507
621
  ...baseInit,
508
622
  redirect: "manual"
@@ -779,7 +893,7 @@ function withUserAgentSuffix(headers, ...userAgentSuffixParts) {
779
893
  }
780
894
 
781
895
  // src/version.ts
782
- var VERSION = true ? "4.0.40" : "0.0.0-test";
896
+ var VERSION = true ? "4.0.42" : "0.0.0-test";
783
897
 
784
898
  // src/get-from-api.ts
785
899
  var getOriginalFetch = () => globalThis.fetch;
@@ -789,10 +903,10 @@ var getFromApi = async ({
789
903
  successfulResponseHandler,
790
904
  failedResponseHandler,
791
905
  abortSignal,
792
- fetch: fetch2 = getOriginalFetch()
906
+ fetch = getOriginalFetch()
793
907
  }) => {
794
908
  try {
795
- const response = await fetch2(url, {
909
+ const response = await fetch(url, {
796
910
  method: "GET",
797
911
  headers: withUserAgentSuffix(
798
912
  headers,
@@ -2544,7 +2658,7 @@ var postJsonToApi = async ({
2544
2658
  failedResponseHandler,
2545
2659
  successfulResponseHandler,
2546
2660
  abortSignal,
2547
- fetch: fetch2
2661
+ fetch
2548
2662
  }) => postToApi({
2549
2663
  url,
2550
2664
  headers: {
@@ -2558,7 +2672,7 @@ var postJsonToApi = async ({
2558
2672
  failedResponseHandler,
2559
2673
  successfulResponseHandler,
2560
2674
  abortSignal,
2561
- fetch: fetch2
2675
+ fetch
2562
2676
  });
2563
2677
  var postFormDataToApi = async ({
2564
2678
  url,
@@ -2567,7 +2681,7 @@ var postFormDataToApi = async ({
2567
2681
  failedResponseHandler,
2568
2682
  successfulResponseHandler,
2569
2683
  abortSignal,
2570
- fetch: fetch2
2684
+ fetch
2571
2685
  }) => postToApi({
2572
2686
  url,
2573
2687
  headers,
@@ -2578,7 +2692,7 @@ var postFormDataToApi = async ({
2578
2692
  failedResponseHandler,
2579
2693
  successfulResponseHandler,
2580
2694
  abortSignal,
2581
- fetch: fetch2
2695
+ fetch
2582
2696
  });
2583
2697
  var postToApi = async ({
2584
2698
  url,
@@ -2587,10 +2701,10 @@ var postToApi = async ({
2587
2701
  successfulResponseHandler,
2588
2702
  failedResponseHandler,
2589
2703
  abortSignal,
2590
- fetch: fetch2 = getOriginalFetch2()
2704
+ fetch = getOriginalFetch2()
2591
2705
  }) => {
2592
2706
  try {
2593
- const response = await fetch2(url, {
2707
+ const response = await fetch(url, {
2594
2708
  method: "POST",
2595
2709
  headers: withUserAgentSuffix(
2596
2710
  headers,