@ai-sdk/provider-utils 3.0.29 → 3.0.31
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 +9 -5
- package/dist/index.d.ts +9 -5
- package/dist/index.js +121 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +121 -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.31
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
9
|
+
## 3.0.30
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 2fd6076: fix(ai): call `onInputStart` before `onInputAvailable` during non-streaming tool calls
|
|
14
|
+
|
|
3
15
|
## 3.0.29
|
|
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
|
*/
|
|
@@ -799,8 +804,8 @@ type Tool<INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONVa
|
|
|
799
804
|
*/
|
|
800
805
|
inputSchema: FlexibleSchema<INPUT>;
|
|
801
806
|
/**
|
|
802
|
-
* Optional function that is called when the
|
|
803
|
-
*
|
|
807
|
+
* Optional function that is called when the model starts generating the tool input.
|
|
808
|
+
* In non-streaming contexts, it is called immediately before `onInputAvailable`.
|
|
804
809
|
*/
|
|
805
810
|
onInputStart?: (options: ToolCallOptions) => void | PromiseLike<void>;
|
|
806
811
|
/**
|
|
@@ -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
|
*/
|
|
@@ -799,8 +804,8 @@ type Tool<INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONVa
|
|
|
799
804
|
*/
|
|
800
805
|
inputSchema: FlexibleSchema<INPUT>;
|
|
801
806
|
/**
|
|
802
|
-
* Optional function that is called when the
|
|
803
|
-
*
|
|
807
|
+
* Optional function that is called when the model starts generating the tool input.
|
|
808
|
+
* In non-streaming contexts, it is called immediately before `onInputAvailable`.
|
|
804
809
|
*/
|
|
805
810
|
onInputStart?: (options: ToolCallOptions) => void | PromiseLike<void>;
|
|
806
811
|
/**
|
|
@@ -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"
|
|
@@ -640,7 +752,7 @@ function withUserAgentSuffix(headers, ...userAgentSuffixParts) {
|
|
|
640
752
|
}
|
|
641
753
|
|
|
642
754
|
// src/version.ts
|
|
643
|
-
var VERSION = true ? "3.0.
|
|
755
|
+
var VERSION = true ? "3.0.31" : "0.0.0-test";
|
|
644
756
|
|
|
645
757
|
// src/get-from-api.ts
|
|
646
758
|
var getOriginalFetch = () => globalThis.fetch;
|
|
@@ -650,10 +762,10 @@ var getFromApi = async ({
|
|
|
650
762
|
successfulResponseHandler,
|
|
651
763
|
failedResponseHandler,
|
|
652
764
|
abortSignal,
|
|
653
|
-
fetch
|
|
765
|
+
fetch = getOriginalFetch()
|
|
654
766
|
}) => {
|
|
655
767
|
try {
|
|
656
|
-
const response = await
|
|
768
|
+
const response = await fetch(url, {
|
|
657
769
|
method: "GET",
|
|
658
770
|
headers: withUserAgentSuffix(
|
|
659
771
|
headers,
|
|
@@ -1099,7 +1211,7 @@ var postJsonToApi = async ({
|
|
|
1099
1211
|
failedResponseHandler,
|
|
1100
1212
|
successfulResponseHandler,
|
|
1101
1213
|
abortSignal,
|
|
1102
|
-
fetch
|
|
1214
|
+
fetch
|
|
1103
1215
|
}) => postToApi({
|
|
1104
1216
|
url,
|
|
1105
1217
|
headers: {
|
|
@@ -1113,7 +1225,7 @@ var postJsonToApi = async ({
|
|
|
1113
1225
|
failedResponseHandler,
|
|
1114
1226
|
successfulResponseHandler,
|
|
1115
1227
|
abortSignal,
|
|
1116
|
-
fetch
|
|
1228
|
+
fetch
|
|
1117
1229
|
});
|
|
1118
1230
|
var postFormDataToApi = async ({
|
|
1119
1231
|
url,
|
|
@@ -1122,7 +1234,7 @@ var postFormDataToApi = async ({
|
|
|
1122
1234
|
failedResponseHandler,
|
|
1123
1235
|
successfulResponseHandler,
|
|
1124
1236
|
abortSignal,
|
|
1125
|
-
fetch
|
|
1237
|
+
fetch
|
|
1126
1238
|
}) => postToApi({
|
|
1127
1239
|
url,
|
|
1128
1240
|
headers,
|
|
@@ -1133,7 +1245,7 @@ var postFormDataToApi = async ({
|
|
|
1133
1245
|
failedResponseHandler,
|
|
1134
1246
|
successfulResponseHandler,
|
|
1135
1247
|
abortSignal,
|
|
1136
|
-
fetch
|
|
1248
|
+
fetch
|
|
1137
1249
|
});
|
|
1138
1250
|
var postToApi = async ({
|
|
1139
1251
|
url,
|
|
@@ -1142,10 +1254,10 @@ var postToApi = async ({
|
|
|
1142
1254
|
successfulResponseHandler,
|
|
1143
1255
|
failedResponseHandler,
|
|
1144
1256
|
abortSignal,
|
|
1145
|
-
fetch
|
|
1257
|
+
fetch = getOriginalFetch2()
|
|
1146
1258
|
}) => {
|
|
1147
1259
|
try {
|
|
1148
|
-
const response = await
|
|
1260
|
+
const response = await fetch(url, {
|
|
1149
1261
|
method: "POST",
|
|
1150
1262
|
headers: withUserAgentSuffix(
|
|
1151
1263
|
headers,
|