@ai-sdk/provider-utils 3.0.30 → 3.0.32
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 +12 -0
- package/dist/index.d.mts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +122 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +122 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @ai-sdk/provider-utils
|
|
2
2
|
|
|
3
|
+
## 3.0.32
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0e51b7b: Preserve streamed download size-limit errors when response cancellation fails.
|
|
8
|
+
|
|
9
|
+
## 3.0.31
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 7a6bdbc: 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
|
## 3.0.30
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -92,6 +92,11 @@ declare class DownloadError extends AISDKError {
|
|
|
92
92
|
* The returned response is the final (non-redirect) response. The caller is
|
|
93
93
|
* responsible for checking `response.ok` and reading the body.
|
|
94
94
|
*
|
|
95
|
+
* On Node.js, the default fetch resolves every hostname through a validating
|
|
96
|
+
* lookup hook and passes those exact addresses to the connector, preventing
|
|
97
|
+
* hostname-to-private-IP and DNS-rebinding bypasses. Other runtimes should
|
|
98
|
+
* constrain egress at the network layer when handling untrusted URLs.
|
|
99
|
+
*
|
|
95
100
|
* @throws DownloadError if a hop is unsafe, the redirect limit is exceeded, or
|
|
96
101
|
* a redirect cannot be validated on a non-browser runtime.
|
|
97
102
|
*/
|
|
@@ -945,9 +950,8 @@ declare function convertToBase64(value: string | Uint8Array): string;
|
|
|
945
950
|
* Validates that a URL is safe to download from, blocking private/internal addresses
|
|
946
951
|
* to prevent SSRF attacks.
|
|
947
952
|
*
|
|
948
|
-
* Note: this performs string/literal-IP checks only.
|
|
949
|
-
*
|
|
950
|
-
* should additionally constrain egress at the network layer when handling untrusted URLs).
|
|
953
|
+
* Note: this function performs string/literal-IP checks only. The Node.js
|
|
954
|
+
* download fetch additionally validates and pins DNS results at connect time.
|
|
951
955
|
*
|
|
952
956
|
* @param url - The URL string to validate.
|
|
953
957
|
* @throws DownloadError if the URL is unsafe.
|
package/dist/index.d.ts
CHANGED
|
@@ -92,6 +92,11 @@ declare class DownloadError extends AISDKError {
|
|
|
92
92
|
* The returned response is the final (non-redirect) response. The caller is
|
|
93
93
|
* responsible for checking `response.ok` and reading the body.
|
|
94
94
|
*
|
|
95
|
+
* On Node.js, the default fetch resolves every hostname through a validating
|
|
96
|
+
* lookup hook and passes those exact addresses to the connector, preventing
|
|
97
|
+
* hostname-to-private-IP and DNS-rebinding bypasses. Other runtimes should
|
|
98
|
+
* constrain egress at the network layer when handling untrusted URLs.
|
|
99
|
+
*
|
|
95
100
|
* @throws DownloadError if a hop is unsafe, the redirect limit is exceeded, or
|
|
96
101
|
* a redirect cannot be validated on a non-browser runtime.
|
|
97
102
|
*/
|
|
@@ -945,9 +950,8 @@ declare function convertToBase64(value: string | Uint8Array): string;
|
|
|
945
950
|
* Validates that a URL is safe to download from, blocking private/internal addresses
|
|
946
951
|
* to prevent SSRF attacks.
|
|
947
952
|
*
|
|
948
|
-
* Note: this performs string/literal-IP checks only.
|
|
949
|
-
*
|
|
950
|
-
* should additionally constrain egress at the network layer when handling untrusted URLs).
|
|
953
|
+
* Note: this function performs string/literal-IP checks only. The Node.js
|
|
954
|
+
* download fetch additionally validates and pins DNS results at connect time.
|
|
951
955
|
*
|
|
952
956
|
* @param url - The URL string to validate.
|
|
953
957
|
* @throws DownloadError if the URL is unsafe.
|
package/dist/index.js
CHANGED
|
@@ -322,6 +322,19 @@ function validateDownloadUrl(url) {
|
|
|
322
322
|
return;
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
|
+
function validateDownloadAddress({
|
|
326
|
+
address,
|
|
327
|
+
family,
|
|
328
|
+
hostname
|
|
329
|
+
}) {
|
|
330
|
+
const isUnsafe = family === 4 ? !isIPv4(address) || isPrivateIPv4(address) : family === 6 ? isPrivateIPv6(address) : true;
|
|
331
|
+
if (isUnsafe) {
|
|
332
|
+
throw new DownloadError({
|
|
333
|
+
url: hostname,
|
|
334
|
+
message: `Hostname ${hostname} resolved to disallowed IP address ${address}`
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
325
338
|
function isIPv4(hostname) {
|
|
326
339
|
const parts = hostname.split(".");
|
|
327
340
|
if (parts.length !== 4) return false;
|
|
@@ -408,6 +421,104 @@ function isPrivateIPv6(ip) {
|
|
|
408
421
|
return false;
|
|
409
422
|
}
|
|
410
423
|
|
|
424
|
+
// src/safe-node-fetch.ts
|
|
425
|
+
function createSafeLookup(lookup) {
|
|
426
|
+
return ((hostname, options, callback) => {
|
|
427
|
+
lookup(hostname, { ...options, all: true }, (error, addresses) => {
|
|
428
|
+
if (error) {
|
|
429
|
+
callback(error);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
try {
|
|
433
|
+
const [firstAddress] = addresses;
|
|
434
|
+
if (firstAddress == null) {
|
|
435
|
+
throw new Error(`Hostname ${hostname} did not resolve to an address`);
|
|
436
|
+
}
|
|
437
|
+
for (const { address, family } of addresses) {
|
|
438
|
+
validateDownloadAddress({ address, family, hostname });
|
|
439
|
+
}
|
|
440
|
+
if (options.all === true) {
|
|
441
|
+
callback(null, addresses);
|
|
442
|
+
} else {
|
|
443
|
+
callback(
|
|
444
|
+
null,
|
|
445
|
+
firstAddress.address,
|
|
446
|
+
firstAddress.family
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
} catch (error2) {
|
|
450
|
+
callback(
|
|
451
|
+
error2 instanceof Error ? error2 : new Error(String(error2))
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
var safeNodeFetchPromise;
|
|
458
|
+
var initialGlobalFetch = globalThis.fetch;
|
|
459
|
+
var initialGlobalFetchIsNodeDefault = isNodeDefaultFetch(initialGlobalFetch);
|
|
460
|
+
function isNodeRuntime() {
|
|
461
|
+
var _a2, _b2;
|
|
462
|
+
const runtimeProcess = globalThis.process;
|
|
463
|
+
return ((_a2 = runtimeProcess == null ? void 0 : runtimeProcess.release) == null ? void 0 : _a2.name) === "node" && ((_b2 = runtimeProcess.versions) == null ? void 0 : _b2.bun) == null;
|
|
464
|
+
}
|
|
465
|
+
async function getDefaultDownloadFetch() {
|
|
466
|
+
if (!isNodeRuntime() || !initialGlobalFetchIsNodeDefault || globalThis.fetch !== initialGlobalFetch) {
|
|
467
|
+
return globalThis.fetch;
|
|
468
|
+
}
|
|
469
|
+
return safeNodeFetchPromise != null ? safeNodeFetchPromise : safeNodeFetchPromise = createSafeNodeFetch();
|
|
470
|
+
}
|
|
471
|
+
function isNodeDefaultFetch(fetch) {
|
|
472
|
+
const source = Function.prototype.toString.call(fetch);
|
|
473
|
+
return source.includes("internal/deps/undici") || source.includes("lazy loading of undici");
|
|
474
|
+
}
|
|
475
|
+
async function createSafeNodeFetch() {
|
|
476
|
+
const [{ createRequire }, { lookup }] = await Promise.all([
|
|
477
|
+
loadNodeModule("node:module"),
|
|
478
|
+
loadNodeModule("node:dns")
|
|
479
|
+
]);
|
|
480
|
+
const { Agent, fetch } = createRequire(getCurrentModulePath())(
|
|
481
|
+
"undici"
|
|
482
|
+
);
|
|
483
|
+
const dispatcher = new Agent({
|
|
484
|
+
connect: {
|
|
485
|
+
lookup: createSafeLookup(lookup)
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
return ((input, init) => fetch(
|
|
489
|
+
input,
|
|
490
|
+
{
|
|
491
|
+
...init,
|
|
492
|
+
dispatcher
|
|
493
|
+
}
|
|
494
|
+
));
|
|
495
|
+
}
|
|
496
|
+
async function loadNodeModule(id) {
|
|
497
|
+
var _a2;
|
|
498
|
+
const processWithBuiltins = globalThis.process;
|
|
499
|
+
const builtinModule = (_a2 = processWithBuiltins == null ? void 0 : processWithBuiltins.getBuiltinModule) == null ? void 0 : _a2.call(processWithBuiltins, id);
|
|
500
|
+
return builtinModule == null ? await importNodeModule(id) : builtinModule;
|
|
501
|
+
}
|
|
502
|
+
function importNodeModule(id) {
|
|
503
|
+
return import(id);
|
|
504
|
+
}
|
|
505
|
+
function getCurrentModulePath() {
|
|
506
|
+
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
507
|
+
try {
|
|
508
|
+
Error.prepareStackTrace = (_error, callSites) => callSites;
|
|
509
|
+
const error = new Error("Capture current module path");
|
|
510
|
+
Error.captureStackTrace(error, getCurrentModulePath);
|
|
511
|
+
const [caller] = error.stack;
|
|
512
|
+
const fileName = caller == null ? void 0 : caller.getFileName();
|
|
513
|
+
if (fileName == null) {
|
|
514
|
+
throw new Error("Unable to determine the current module path");
|
|
515
|
+
}
|
|
516
|
+
return fileName;
|
|
517
|
+
} finally {
|
|
518
|
+
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
411
522
|
// src/fetch-with-validated-redirects.ts
|
|
412
523
|
var MAX_DOWNLOAD_REDIRECTS = 10;
|
|
413
524
|
async function fetchWithValidatedRedirects({
|
|
@@ -423,6 +534,7 @@ async function fetchWithValidatedRedirects({
|
|
|
423
534
|
let currentUrl = url;
|
|
424
535
|
for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount++) {
|
|
425
536
|
validateDownloadUrl(currentUrl);
|
|
537
|
+
const fetch = await getDefaultDownloadFetch();
|
|
426
538
|
const response = await fetch(currentUrl, {
|
|
427
539
|
...baseInit,
|
|
428
540
|
redirect: "manual"
|
|
@@ -493,6 +605,7 @@ async function readResponseWithSizeLimit({
|
|
|
493
605
|
} finally {
|
|
494
606
|
try {
|
|
495
607
|
await reader.cancel();
|
|
608
|
+
} catch (e) {
|
|
496
609
|
} finally {
|
|
497
610
|
reader.releaseLock();
|
|
498
611
|
}
|
|
@@ -640,7 +753,7 @@ function withUserAgentSuffix(headers, ...userAgentSuffixParts) {
|
|
|
640
753
|
}
|
|
641
754
|
|
|
642
755
|
// src/version.ts
|
|
643
|
-
var VERSION = true ? "3.0.
|
|
756
|
+
var VERSION = true ? "3.0.32" : "0.0.0-test";
|
|
644
757
|
|
|
645
758
|
// src/get-from-api.ts
|
|
646
759
|
var getOriginalFetch = () => globalThis.fetch;
|
|
@@ -650,10 +763,10 @@ var getFromApi = async ({
|
|
|
650
763
|
successfulResponseHandler,
|
|
651
764
|
failedResponseHandler,
|
|
652
765
|
abortSignal,
|
|
653
|
-
fetch
|
|
766
|
+
fetch = getOriginalFetch()
|
|
654
767
|
}) => {
|
|
655
768
|
try {
|
|
656
|
-
const response = await
|
|
769
|
+
const response = await fetch(url, {
|
|
657
770
|
method: "GET",
|
|
658
771
|
headers: withUserAgentSuffix(
|
|
659
772
|
headers,
|
|
@@ -1099,7 +1212,7 @@ var postJsonToApi = async ({
|
|
|
1099
1212
|
failedResponseHandler,
|
|
1100
1213
|
successfulResponseHandler,
|
|
1101
1214
|
abortSignal,
|
|
1102
|
-
fetch
|
|
1215
|
+
fetch
|
|
1103
1216
|
}) => postToApi({
|
|
1104
1217
|
url,
|
|
1105
1218
|
headers: {
|
|
@@ -1113,7 +1226,7 @@ var postJsonToApi = async ({
|
|
|
1113
1226
|
failedResponseHandler,
|
|
1114
1227
|
successfulResponseHandler,
|
|
1115
1228
|
abortSignal,
|
|
1116
|
-
fetch
|
|
1229
|
+
fetch
|
|
1117
1230
|
});
|
|
1118
1231
|
var postFormDataToApi = async ({
|
|
1119
1232
|
url,
|
|
@@ -1122,7 +1235,7 @@ var postFormDataToApi = async ({
|
|
|
1122
1235
|
failedResponseHandler,
|
|
1123
1236
|
successfulResponseHandler,
|
|
1124
1237
|
abortSignal,
|
|
1125
|
-
fetch
|
|
1238
|
+
fetch
|
|
1126
1239
|
}) => postToApi({
|
|
1127
1240
|
url,
|
|
1128
1241
|
headers,
|
|
@@ -1133,7 +1246,7 @@ var postFormDataToApi = async ({
|
|
|
1133
1246
|
failedResponseHandler,
|
|
1134
1247
|
successfulResponseHandler,
|
|
1135
1248
|
abortSignal,
|
|
1136
|
-
fetch
|
|
1249
|
+
fetch
|
|
1137
1250
|
});
|
|
1138
1251
|
var postToApi = async ({
|
|
1139
1252
|
url,
|
|
@@ -1142,10 +1255,10 @@ var postToApi = async ({
|
|
|
1142
1255
|
successfulResponseHandler,
|
|
1143
1256
|
failedResponseHandler,
|
|
1144
1257
|
abortSignal,
|
|
1145
|
-
fetch
|
|
1258
|
+
fetch = getOriginalFetch2()
|
|
1146
1259
|
}) => {
|
|
1147
1260
|
try {
|
|
1148
|
-
const response = await
|
|
1261
|
+
const response = await fetch(url, {
|
|
1149
1262
|
method: "POST",
|
|
1150
1263
|
headers: withUserAgentSuffix(
|
|
1151
1264
|
headers,
|