@ai-sdk/provider-utils 3.0.25 → 3.0.26
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 +21 -0
- package/dist/index.d.mts +59 -1
- package/dist/index.d.ts +59 -1
- package/dist/index.js +209 -108
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +206 -108
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @ai-sdk/provider-utils
|
|
2
2
|
|
|
3
|
+
## 3.0.26
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9f67efe: fix: only send provider credentials to same-origin response-supplied URLs
|
|
8
|
+
|
|
9
|
+
Several provider clients followed a URL taken from the provider's API response (a polling/status URL or a final media URL such as `polling_url`, `urls.get`, `result_url`, `result.sample`, or `video.uri`) and reused the authenticated headers — or appended `?key=<API_KEY>` — on that request. Because the host of the response-supplied URL was never validated, the long-lived API key was sent to whatever host the response named (a CDN in the benign case, or an attacker-chosen host if the provider response was tampered with), allowing credential exfiltration.
|
|
10
|
+
|
|
11
|
+
A new `isSameOrigin` helper is added to `@ai-sdk/provider-utils`, and the affected fetches in `@ai-sdk/black-forest-labs`, `@ai-sdk/fireworks`, `@ai-sdk/replicate`, `@ai-sdk/gladia`, `@ai-sdk/fal`, and `@ai-sdk/google` now attach credentials only when the followed URL is same-origin with the provider's configured API origin. Requests to a foreign origin are made without the credential.
|
|
12
|
+
|
|
13
|
+
- eea9166: fix: harden download URL SSRF guard against hostname and redirect bypasses
|
|
14
|
+
|
|
15
|
+
`validateDownloadUrl` and the file download helpers (`downloadBlob`, `download`) could be bypassed in several ways when handling untrusted URLs:
|
|
16
|
+
|
|
17
|
+
- A fully-qualified hostname with a trailing dot (e.g. `localhost.`, `myhost.local.`) skipped the localhost/`.local` blocklist.
|
|
18
|
+
- IPv6 addresses that embed an IPv4 address in their last 32 bits — IPv4-compatible (`::127.0.0.1`), IPv4-translated (`::ffff:0:127.0.0.1`), and NAT64 (`64:ff9b::127.0.0.1`, including the `64:ff9b:1::/48` local-use prefix) — were not decoded and checked against the private IPv4 ranges.
|
|
19
|
+
- Redirects were validated only _after_ `fetch` had already followed them, so the request to a redirect target (e.g. an internal/metadata address) had already been issued before the check ran.
|
|
20
|
+
- Several reserved/internal address ranges were not blocked: CGNAT (`100.64.0.0/10`, used by some cloud providers for internal traffic), benchmarking (`198.18.0.0/15`), IETF protocol assignments (`192.0.0.0/24`), the reserved `240.0.0.0/4` block (including the `255.255.255.255` broadcast address), and IPv6 site-local (`fec0::/10`) and multicast (`ff00::/8`).
|
|
21
|
+
|
|
22
|
+
The validator now strips trailing dots before the hostname checks and fully expands IPv6 addresses to detect embedded private IPv4 targets. The download helpers now follow redirects manually (`redirect: 'manual'`), re-validating each hop before requesting it, so an unsafe redirect target is never fetched. When a redirect cannot be inspected because the runtime returns an opaque response, the helpers fail closed (reject the redirect) on the server; only in a real browser — where SSRF is not reachable (fetch is constrained by CORS and cannot reach a server's internal network or cloud-metadata endpoints) — is the redirect followed natively so legitimate redirected downloads keep working.
|
|
23
|
+
|
|
3
24
|
## 3.0.25
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -72,6 +72,46 @@ declare class DownloadError extends AISDKError {
|
|
|
72
72
|
static isInstance(error: unknown): error is DownloadError;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Fetches a URL while enforcing the SSRF download guard on every hop.
|
|
77
|
+
*
|
|
78
|
+
* Redirects are followed manually (`redirect: 'manual'`) so each hop is
|
|
79
|
+
* validated with {@link validateDownloadUrl} *before* it is requested. Relying
|
|
80
|
+
* on the default `redirect: 'follow'` would issue the request to a redirect
|
|
81
|
+
* target (e.g. an internal address) before we ever see its URL, defeating the
|
|
82
|
+
* SSRF guard.
|
|
83
|
+
*
|
|
84
|
+
* A `redirect: 'manual'` request yields an unreadable opaque response in the
|
|
85
|
+
* browser (and in other spec-compliant fetch implementations), so the redirect
|
|
86
|
+
* target cannot be validated here. In a real browser this is safe to follow
|
|
87
|
+
* natively because SSRF is not reachable (fetch is constrained by CORS and
|
|
88
|
+
* cannot reach a server's internal network or cloud-metadata). On any other
|
|
89
|
+
* runtime we cannot validate the hop, so we fail closed rather than follow it
|
|
90
|
+
* blindly and bypass the SSRF guard.
|
|
91
|
+
*
|
|
92
|
+
* The returned response is the final (non-redirect) response. The caller is
|
|
93
|
+
* responsible for checking `response.ok` and reading the body.
|
|
94
|
+
*
|
|
95
|
+
* @throws DownloadError if a hop is unsafe, the redirect limit is exceeded, or
|
|
96
|
+
* a redirect cannot be validated on a non-browser runtime.
|
|
97
|
+
*/
|
|
98
|
+
declare function fetchWithValidatedRedirects({ url, headers, abortSignal, maxRedirects, }: {
|
|
99
|
+
url: string;
|
|
100
|
+
headers?: HeadersInit;
|
|
101
|
+
abortSignal?: AbortSignal;
|
|
102
|
+
maxRedirects?: number;
|
|
103
|
+
}): Promise<Response>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Returns `true` when running in a browser.
|
|
107
|
+
*
|
|
108
|
+
* Detection keys on the presence of a global `window`, matching the browser
|
|
109
|
+
* check used elsewhere in this package (see `getRuntimeEnvironmentUserAgent`)
|
|
110
|
+
* so the SDK has a single, consistent definition of "browser". Server runtimes
|
|
111
|
+
* (Node.js, Deno, Bun, edge/workers) do not define `window`.
|
|
112
|
+
*/
|
|
113
|
+
declare function isBrowserRuntime(globalThisAny?: any): boolean;
|
|
114
|
+
|
|
75
115
|
/**
|
|
76
116
|
* Default maximum download size: 2 GiB.
|
|
77
117
|
*
|
|
@@ -279,6 +319,20 @@ declare function injectJsonInstructionIntoMessages({ messages, schema, schemaPre
|
|
|
279
319
|
|
|
280
320
|
declare function isAbortError(error: unknown): error is Error;
|
|
281
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Returns true when `url` has the same origin (scheme + host + port) as
|
|
324
|
+
* `baseUrl`.
|
|
325
|
+
*
|
|
326
|
+
* Used to decide whether provider credentials may be attached to a request to a
|
|
327
|
+
* URL taken from a provider response (e.g. a polling or media-download URL).
|
|
328
|
+
* Credentials must only be sent to the provider's own origin; a response that
|
|
329
|
+
* names a foreign host (a CDN, or an attacker-controlled host if the response
|
|
330
|
+
* is tampered with) must not receive the API key.
|
|
331
|
+
*
|
|
332
|
+
* Returns false if either value is not a valid absolute URL (fail-closed).
|
|
333
|
+
*/
|
|
334
|
+
declare function isSameOrigin(url: string, baseUrl: string): boolean;
|
|
335
|
+
|
|
282
336
|
/**
|
|
283
337
|
* Checks if the given URL is supported natively by the model.
|
|
284
338
|
*
|
|
@@ -877,6 +931,10 @@ declare function convertToBase64(value: string | Uint8Array): string;
|
|
|
877
931
|
* Validates that a URL is safe to download from, blocking private/internal addresses
|
|
878
932
|
* to prevent SSRF attacks.
|
|
879
933
|
*
|
|
934
|
+
* Note: this performs string/literal-IP checks only. It does not resolve DNS, so a
|
|
935
|
+
* hostname that resolves to a private address is not blocked here (see callers, which
|
|
936
|
+
* should additionally constrain egress at the network layer when handling untrusted URLs).
|
|
937
|
+
*
|
|
880
938
|
* @param url - The URL string to validate.
|
|
881
939
|
* @throws DownloadError if the URL is unsafe.
|
|
882
940
|
*/
|
|
@@ -1014,4 +1072,4 @@ interface ToolResult<NAME extends string, INPUT, OUTPUT> {
|
|
|
1014
1072
|
dynamic?: boolean;
|
|
1015
1073
|
}
|
|
1016
1074
|
|
|
1017
|
-
export { type AssistantContent, type AssistantModelMessage, DEFAULT_MAX_DOWNLOAD_SIZE, type DataContent, DelayedPromise, DownloadError, type FetchFunction, type FilePart, type FlexibleSchema, type FlexibleValidator, type IdGenerator, type ImagePart, type InferSchema, type InferToolInput, type InferToolOutput, type InferValidator, type LazySchema, type LazyValidator, type ModelMessage, type ParseResult, type ProviderDefinedToolFactory, type ProviderDefinedToolFactoryWithOutputSchema, type ProviderOptions, type ReasoningPart, type Resolvable, type ResponseHandler, type Schema, type SystemModelMessage, type TextPart, type Tool, type ToolCall, type ToolCallOptions, type ToolCallPart, type ToolContent, type ToolExecuteFunction, type ToolModelMessage, type ToolResult, type ToolResultPart, type UserContent, type UserModelMessage, VERSION, type ValidationResult, type Validator, asSchema, asValidator, combineHeaders, convertAsyncIteratorToReadableStream, convertBase64ToUint8Array, convertToBase64, convertUint8ArrayToBase64, createBinaryResponseHandler, createEventSourceResponseHandler, createIdGenerator, createJsonErrorResponseHandler, createJsonResponseHandler, createJsonStreamResponseHandler, createProviderDefinedToolFactory, createProviderDefinedToolFactoryWithOutputSchema, createStatusCodeErrorResponseHandler, delay, dynamicTool, executeTool, extractResponseHeaders, generateId, getErrorMessage, getFromApi, getRuntimeEnvironmentUserAgent, injectJsonInstructionIntoMessages, isAbortError, isParsableJson, isUrlSupported, isValidator, jsonSchema, lazySchema, lazyValidator, loadApiKey, loadOptionalSetting, loadSetting, mediaTypeToExtension, normalizeHeaders, parseJSON, parseJsonEventStream, parseProviderOptions, postFormDataToApi, postJsonToApi, postToApi, readResponseWithSizeLimit, removeUndefinedEntries, resolve, safeParseJSON, safeValidateTypes, standardSchemaValidator, tool, validateDownloadUrl, validateTypes, validator, withUserAgentSuffix, withoutTrailingSlash, zodSchema };
|
|
1075
|
+
export { type AssistantContent, type AssistantModelMessage, DEFAULT_MAX_DOWNLOAD_SIZE, type DataContent, DelayedPromise, DownloadError, type FetchFunction, type FilePart, type FlexibleSchema, type FlexibleValidator, type IdGenerator, type ImagePart, type InferSchema, type InferToolInput, type InferToolOutput, type InferValidator, type LazySchema, type LazyValidator, type ModelMessage, type ParseResult, type ProviderDefinedToolFactory, type ProviderDefinedToolFactoryWithOutputSchema, type ProviderOptions, type ReasoningPart, type Resolvable, type ResponseHandler, type Schema, type SystemModelMessage, type TextPart, type Tool, type ToolCall, type ToolCallOptions, type ToolCallPart, type ToolContent, type ToolExecuteFunction, type ToolModelMessage, type ToolResult, type ToolResultPart, type UserContent, type UserModelMessage, VERSION, type ValidationResult, type Validator, asSchema, asValidator, combineHeaders, convertAsyncIteratorToReadableStream, convertBase64ToUint8Array, convertToBase64, convertUint8ArrayToBase64, createBinaryResponseHandler, createEventSourceResponseHandler, createIdGenerator, createJsonErrorResponseHandler, createJsonResponseHandler, createJsonStreamResponseHandler, createProviderDefinedToolFactory, createProviderDefinedToolFactoryWithOutputSchema, createStatusCodeErrorResponseHandler, delay, dynamicTool, executeTool, extractResponseHeaders, fetchWithValidatedRedirects, generateId, getErrorMessage, getFromApi, getRuntimeEnvironmentUserAgent, injectJsonInstructionIntoMessages, isAbortError, isBrowserRuntime, isParsableJson, isSameOrigin, isUrlSupported, isValidator, jsonSchema, lazySchema, lazyValidator, loadApiKey, loadOptionalSetting, loadSetting, mediaTypeToExtension, normalizeHeaders, parseJSON, parseJsonEventStream, parseProviderOptions, postFormDataToApi, postJsonToApi, postToApi, readResponseWithSizeLimit, removeUndefinedEntries, resolve, safeParseJSON, safeValidateTypes, standardSchemaValidator, tool, validateDownloadUrl, validateTypes, validator, withUserAgentSuffix, withoutTrailingSlash, zodSchema };
|
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,46 @@ declare class DownloadError extends AISDKError {
|
|
|
72
72
|
static isInstance(error: unknown): error is DownloadError;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Fetches a URL while enforcing the SSRF download guard on every hop.
|
|
77
|
+
*
|
|
78
|
+
* Redirects are followed manually (`redirect: 'manual'`) so each hop is
|
|
79
|
+
* validated with {@link validateDownloadUrl} *before* it is requested. Relying
|
|
80
|
+
* on the default `redirect: 'follow'` would issue the request to a redirect
|
|
81
|
+
* target (e.g. an internal address) before we ever see its URL, defeating the
|
|
82
|
+
* SSRF guard.
|
|
83
|
+
*
|
|
84
|
+
* A `redirect: 'manual'` request yields an unreadable opaque response in the
|
|
85
|
+
* browser (and in other spec-compliant fetch implementations), so the redirect
|
|
86
|
+
* target cannot be validated here. In a real browser this is safe to follow
|
|
87
|
+
* natively because SSRF is not reachable (fetch is constrained by CORS and
|
|
88
|
+
* cannot reach a server's internal network or cloud-metadata). On any other
|
|
89
|
+
* runtime we cannot validate the hop, so we fail closed rather than follow it
|
|
90
|
+
* blindly and bypass the SSRF guard.
|
|
91
|
+
*
|
|
92
|
+
* The returned response is the final (non-redirect) response. The caller is
|
|
93
|
+
* responsible for checking `response.ok` and reading the body.
|
|
94
|
+
*
|
|
95
|
+
* @throws DownloadError if a hop is unsafe, the redirect limit is exceeded, or
|
|
96
|
+
* a redirect cannot be validated on a non-browser runtime.
|
|
97
|
+
*/
|
|
98
|
+
declare function fetchWithValidatedRedirects({ url, headers, abortSignal, maxRedirects, }: {
|
|
99
|
+
url: string;
|
|
100
|
+
headers?: HeadersInit;
|
|
101
|
+
abortSignal?: AbortSignal;
|
|
102
|
+
maxRedirects?: number;
|
|
103
|
+
}): Promise<Response>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Returns `true` when running in a browser.
|
|
107
|
+
*
|
|
108
|
+
* Detection keys on the presence of a global `window`, matching the browser
|
|
109
|
+
* check used elsewhere in this package (see `getRuntimeEnvironmentUserAgent`)
|
|
110
|
+
* so the SDK has a single, consistent definition of "browser". Server runtimes
|
|
111
|
+
* (Node.js, Deno, Bun, edge/workers) do not define `window`.
|
|
112
|
+
*/
|
|
113
|
+
declare function isBrowserRuntime(globalThisAny?: any): boolean;
|
|
114
|
+
|
|
75
115
|
/**
|
|
76
116
|
* Default maximum download size: 2 GiB.
|
|
77
117
|
*
|
|
@@ -279,6 +319,20 @@ declare function injectJsonInstructionIntoMessages({ messages, schema, schemaPre
|
|
|
279
319
|
|
|
280
320
|
declare function isAbortError(error: unknown): error is Error;
|
|
281
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Returns true when `url` has the same origin (scheme + host + port) as
|
|
324
|
+
* `baseUrl`.
|
|
325
|
+
*
|
|
326
|
+
* Used to decide whether provider credentials may be attached to a request to a
|
|
327
|
+
* URL taken from a provider response (e.g. a polling or media-download URL).
|
|
328
|
+
* Credentials must only be sent to the provider's own origin; a response that
|
|
329
|
+
* names a foreign host (a CDN, or an attacker-controlled host if the response
|
|
330
|
+
* is tampered with) must not receive the API key.
|
|
331
|
+
*
|
|
332
|
+
* Returns false if either value is not a valid absolute URL (fail-closed).
|
|
333
|
+
*/
|
|
334
|
+
declare function isSameOrigin(url: string, baseUrl: string): boolean;
|
|
335
|
+
|
|
282
336
|
/**
|
|
283
337
|
* Checks if the given URL is supported natively by the model.
|
|
284
338
|
*
|
|
@@ -877,6 +931,10 @@ declare function convertToBase64(value: string | Uint8Array): string;
|
|
|
877
931
|
* Validates that a URL is safe to download from, blocking private/internal addresses
|
|
878
932
|
* to prevent SSRF attacks.
|
|
879
933
|
*
|
|
934
|
+
* Note: this performs string/literal-IP checks only. It does not resolve DNS, so a
|
|
935
|
+
* hostname that resolves to a private address is not blocked here (see callers, which
|
|
936
|
+
* should additionally constrain egress at the network layer when handling untrusted URLs).
|
|
937
|
+
*
|
|
880
938
|
* @param url - The URL string to validate.
|
|
881
939
|
* @throws DownloadError if the URL is unsafe.
|
|
882
940
|
*/
|
|
@@ -1014,4 +1072,4 @@ interface ToolResult<NAME extends string, INPUT, OUTPUT> {
|
|
|
1014
1072
|
dynamic?: boolean;
|
|
1015
1073
|
}
|
|
1016
1074
|
|
|
1017
|
-
export { type AssistantContent, type AssistantModelMessage, DEFAULT_MAX_DOWNLOAD_SIZE, type DataContent, DelayedPromise, DownloadError, type FetchFunction, type FilePart, type FlexibleSchema, type FlexibleValidator, type IdGenerator, type ImagePart, type InferSchema, type InferToolInput, type InferToolOutput, type InferValidator, type LazySchema, type LazyValidator, type ModelMessage, type ParseResult, type ProviderDefinedToolFactory, type ProviderDefinedToolFactoryWithOutputSchema, type ProviderOptions, type ReasoningPart, type Resolvable, type ResponseHandler, type Schema, type SystemModelMessage, type TextPart, type Tool, type ToolCall, type ToolCallOptions, type ToolCallPart, type ToolContent, type ToolExecuteFunction, type ToolModelMessage, type ToolResult, type ToolResultPart, type UserContent, type UserModelMessage, VERSION, type ValidationResult, type Validator, asSchema, asValidator, combineHeaders, convertAsyncIteratorToReadableStream, convertBase64ToUint8Array, convertToBase64, convertUint8ArrayToBase64, createBinaryResponseHandler, createEventSourceResponseHandler, createIdGenerator, createJsonErrorResponseHandler, createJsonResponseHandler, createJsonStreamResponseHandler, createProviderDefinedToolFactory, createProviderDefinedToolFactoryWithOutputSchema, createStatusCodeErrorResponseHandler, delay, dynamicTool, executeTool, extractResponseHeaders, generateId, getErrorMessage, getFromApi, getRuntimeEnvironmentUserAgent, injectJsonInstructionIntoMessages, isAbortError, isParsableJson, isUrlSupported, isValidator, jsonSchema, lazySchema, lazyValidator, loadApiKey, loadOptionalSetting, loadSetting, mediaTypeToExtension, normalizeHeaders, parseJSON, parseJsonEventStream, parseProviderOptions, postFormDataToApi, postJsonToApi, postToApi, readResponseWithSizeLimit, removeUndefinedEntries, resolve, safeParseJSON, safeValidateTypes, standardSchemaValidator, tool, validateDownloadUrl, validateTypes, validator, withUserAgentSuffix, withoutTrailingSlash, zodSchema };
|
|
1075
|
+
export { type AssistantContent, type AssistantModelMessage, DEFAULT_MAX_DOWNLOAD_SIZE, type DataContent, DelayedPromise, DownloadError, type FetchFunction, type FilePart, type FlexibleSchema, type FlexibleValidator, type IdGenerator, type ImagePart, type InferSchema, type InferToolInput, type InferToolOutput, type InferValidator, type LazySchema, type LazyValidator, type ModelMessage, type ParseResult, type ProviderDefinedToolFactory, type ProviderDefinedToolFactoryWithOutputSchema, type ProviderOptions, type ReasoningPart, type Resolvable, type ResponseHandler, type Schema, type SystemModelMessage, type TextPart, type Tool, type ToolCall, type ToolCallOptions, type ToolCallPart, type ToolContent, type ToolExecuteFunction, type ToolModelMessage, type ToolResult, type ToolResultPart, type UserContent, type UserModelMessage, VERSION, type ValidationResult, type Validator, asSchema, asValidator, combineHeaders, convertAsyncIteratorToReadableStream, convertBase64ToUint8Array, convertToBase64, convertUint8ArrayToBase64, createBinaryResponseHandler, createEventSourceResponseHandler, createIdGenerator, createJsonErrorResponseHandler, createJsonResponseHandler, createJsonStreamResponseHandler, createProviderDefinedToolFactory, createProviderDefinedToolFactoryWithOutputSchema, createStatusCodeErrorResponseHandler, delay, dynamicTool, executeTool, extractResponseHeaders, fetchWithValidatedRedirects, generateId, getErrorMessage, getFromApi, getRuntimeEnvironmentUserAgent, injectJsonInstructionIntoMessages, isAbortError, isBrowserRuntime, isParsableJson, isSameOrigin, isUrlSupported, isValidator, jsonSchema, lazySchema, lazyValidator, loadApiKey, loadOptionalSetting, loadSetting, mediaTypeToExtension, normalizeHeaders, parseJSON, parseJsonEventStream, parseProviderOptions, postFormDataToApi, postJsonToApi, postToApi, readResponseWithSizeLimit, removeUndefinedEntries, resolve, safeParseJSON, safeValidateTypes, standardSchemaValidator, tool, validateDownloadUrl, validateTypes, validator, withUserAgentSuffix, withoutTrailingSlash, zodSchema };
|
package/dist/index.js
CHANGED
|
@@ -56,13 +56,16 @@ __export(index_exports, {
|
|
|
56
56
|
dynamicTool: () => dynamicTool,
|
|
57
57
|
executeTool: () => executeTool,
|
|
58
58
|
extractResponseHeaders: () => extractResponseHeaders,
|
|
59
|
+
fetchWithValidatedRedirects: () => fetchWithValidatedRedirects,
|
|
59
60
|
generateId: () => generateId,
|
|
60
61
|
getErrorMessage: () => getErrorMessage,
|
|
61
62
|
getFromApi: () => getFromApi,
|
|
62
63
|
getRuntimeEnvironmentUserAgent: () => getRuntimeEnvironmentUserAgent,
|
|
63
64
|
injectJsonInstructionIntoMessages: () => injectJsonInstructionIntoMessages,
|
|
64
65
|
isAbortError: () => isAbortError,
|
|
66
|
+
isBrowserRuntime: () => isBrowserRuntime,
|
|
65
67
|
isParsableJson: () => isParsableJson,
|
|
68
|
+
isSameOrigin: () => isSameOrigin,
|
|
66
69
|
isUrlSupported: () => isUrlSupported,
|
|
67
70
|
isValidator: () => isValidator,
|
|
68
71
|
jsonSchema: () => jsonSchema,
|
|
@@ -251,6 +254,191 @@ var DownloadError = class extends (_b = import_provider.AISDKError, _a = symbol,
|
|
|
251
254
|
}
|
|
252
255
|
};
|
|
253
256
|
|
|
257
|
+
// src/is-browser-runtime.ts
|
|
258
|
+
function isBrowserRuntime(globalThisAny = globalThis) {
|
|
259
|
+
return globalThisAny.window != null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/validate-download-url.ts
|
|
263
|
+
function validateDownloadUrl(url) {
|
|
264
|
+
let parsed;
|
|
265
|
+
try {
|
|
266
|
+
parsed = new URL(url);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
throw new DownloadError({
|
|
269
|
+
url,
|
|
270
|
+
message: `Invalid URL: ${url}`
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
if (parsed.protocol === "data:") {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
277
|
+
throw new DownloadError({
|
|
278
|
+
url,
|
|
279
|
+
message: `URL scheme must be http, https, or data, got ${parsed.protocol}`
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
const hostname = parsed.hostname.toLowerCase().replace(/\.+$/, "");
|
|
283
|
+
if (!hostname) {
|
|
284
|
+
throw new DownloadError({
|
|
285
|
+
url,
|
|
286
|
+
message: `URL must have a hostname`
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (hostname === "localhost" || hostname.endsWith(".local") || hostname.endsWith(".localhost")) {
|
|
290
|
+
throw new DownloadError({
|
|
291
|
+
url,
|
|
292
|
+
message: `URL with hostname ${hostname} is not allowed`
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
if (hostname.startsWith("[") && hostname.endsWith("]")) {
|
|
296
|
+
const ipv6 = hostname.slice(1, -1);
|
|
297
|
+
if (isPrivateIPv6(ipv6)) {
|
|
298
|
+
throw new DownloadError({
|
|
299
|
+
url,
|
|
300
|
+
message: `URL with IPv6 address ${hostname} is not allowed`
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (isIPv4(hostname)) {
|
|
306
|
+
if (isPrivateIPv4(hostname)) {
|
|
307
|
+
throw new DownloadError({
|
|
308
|
+
url,
|
|
309
|
+
message: `URL with IP address ${hostname} is not allowed`
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function isIPv4(hostname) {
|
|
316
|
+
const parts = hostname.split(".");
|
|
317
|
+
if (parts.length !== 4) return false;
|
|
318
|
+
return parts.every((part) => {
|
|
319
|
+
const num = Number(part);
|
|
320
|
+
return Number.isInteger(num) && num >= 0 && num <= 255 && String(num) === part;
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function isPrivateIPv4(ip) {
|
|
324
|
+
const parts = ip.split(".").map(Number);
|
|
325
|
+
const [a, b, c] = parts;
|
|
326
|
+
if (a === 0) return true;
|
|
327
|
+
if (a === 10) return true;
|
|
328
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
329
|
+
if (a === 127) return true;
|
|
330
|
+
if (a === 169 && b === 254) return true;
|
|
331
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
332
|
+
if (a === 192 && b === 0 && c === 0) return true;
|
|
333
|
+
if (a === 192 && b === 168) return true;
|
|
334
|
+
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
335
|
+
if (a >= 240) return true;
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
function parseIPv6(ip) {
|
|
339
|
+
let address = ip.toLowerCase();
|
|
340
|
+
const zoneIndex = address.indexOf("%");
|
|
341
|
+
if (zoneIndex !== -1) {
|
|
342
|
+
address = address.slice(0, zoneIndex);
|
|
343
|
+
}
|
|
344
|
+
const halves = address.split("::");
|
|
345
|
+
if (halves.length > 2) return null;
|
|
346
|
+
const toGroups = (segment) => {
|
|
347
|
+
if (segment === "") return [];
|
|
348
|
+
const groups = [];
|
|
349
|
+
const parts = segment.split(":");
|
|
350
|
+
for (let i = 0; i < parts.length; i++) {
|
|
351
|
+
const part = parts[i];
|
|
352
|
+
if (part.includes(".")) {
|
|
353
|
+
if (i !== parts.length - 1 || !isIPv4(part)) return null;
|
|
354
|
+
const [a, b, c, d] = part.split(".").map(Number);
|
|
355
|
+
groups.push(a << 8 | b, c << 8 | d);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (!/^[0-9a-f]{1,4}$/.test(part)) return null;
|
|
359
|
+
groups.push(parseInt(part, 16));
|
|
360
|
+
}
|
|
361
|
+
return groups;
|
|
362
|
+
};
|
|
363
|
+
const head = toGroups(halves[0]);
|
|
364
|
+
if (head === null) return null;
|
|
365
|
+
if (halves.length === 2) {
|
|
366
|
+
const tail = toGroups(halves[1]);
|
|
367
|
+
if (tail === null) return null;
|
|
368
|
+
const fill = 8 - head.length - tail.length;
|
|
369
|
+
if (fill < 0) return null;
|
|
370
|
+
return [...head, ...new Array(fill).fill(0), ...tail];
|
|
371
|
+
}
|
|
372
|
+
return head.length === 8 ? head : null;
|
|
373
|
+
}
|
|
374
|
+
function isPrivateIPv6(ip) {
|
|
375
|
+
const groups = parseIPv6(ip);
|
|
376
|
+
if (groups === null) return true;
|
|
377
|
+
const topZero = (count) => groups.slice(0, count).every((group) => group === 0);
|
|
378
|
+
if (topZero(7) && (groups[7] === 0 || groups[7] === 1)) return true;
|
|
379
|
+
if ((groups[0] & 65024) === 64512) return true;
|
|
380
|
+
if ((groups[0] & 65472) === 65152) return true;
|
|
381
|
+
if ((groups[0] & 65472) === 65216) return true;
|
|
382
|
+
if ((groups[0] & 65280) === 65280) return true;
|
|
383
|
+
const embedsIPv4 = (
|
|
384
|
+
// ::/96 — IPv4-compatible (deprecated)
|
|
385
|
+
topZero(6) || // ::ffff:0:0/96 — IPv4-mapped (ffff in group 5)
|
|
386
|
+
topZero(5) && groups[5] === 65535 || // ::ffff:0:0/96 — IPv4-translated form (ffff in group 4, group 5 zero)
|
|
387
|
+
topZero(4) && groups[4] === 65535 && groups[5] === 0 || // 64:ff9b::/96 — NAT64 well-known prefix
|
|
388
|
+
groups[0] === 100 && groups[1] === 65435 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 0 || // 64:ff9b:1::/48 — NAT64 local-use prefix
|
|
389
|
+
groups[0] === 100 && groups[1] === 65435 && groups[2] === 1
|
|
390
|
+
);
|
|
391
|
+
if (embedsIPv4) {
|
|
392
|
+
const a = groups[6] >> 8 & 255;
|
|
393
|
+
const b = groups[6] & 255;
|
|
394
|
+
const c = groups[7] >> 8 & 255;
|
|
395
|
+
const d = groups[7] & 255;
|
|
396
|
+
return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
|
|
397
|
+
}
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/fetch-with-validated-redirects.ts
|
|
402
|
+
var MAX_DOWNLOAD_REDIRECTS = 10;
|
|
403
|
+
async function fetchWithValidatedRedirects({
|
|
404
|
+
url,
|
|
405
|
+
headers,
|
|
406
|
+
abortSignal,
|
|
407
|
+
maxRedirects = MAX_DOWNLOAD_REDIRECTS
|
|
408
|
+
}) {
|
|
409
|
+
const baseInit = { signal: abortSignal };
|
|
410
|
+
if (headers !== void 0) {
|
|
411
|
+
baseInit.headers = headers;
|
|
412
|
+
}
|
|
413
|
+
let currentUrl = url;
|
|
414
|
+
for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount++) {
|
|
415
|
+
validateDownloadUrl(currentUrl);
|
|
416
|
+
const response = await fetch(currentUrl, {
|
|
417
|
+
...baseInit,
|
|
418
|
+
redirect: "manual"
|
|
419
|
+
});
|
|
420
|
+
if (response.type === "opaqueredirect") {
|
|
421
|
+
if (!isBrowserRuntime()) {
|
|
422
|
+
throw new DownloadError({
|
|
423
|
+
url,
|
|
424
|
+
message: `Redirect from ${currentUrl} could not be validated and was blocked`
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return await fetch(currentUrl, { ...baseInit, redirect: "follow" });
|
|
428
|
+
}
|
|
429
|
+
const location = response.headers.get("location");
|
|
430
|
+
if (response.status >= 300 && response.status < 400 && location) {
|
|
431
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
return response;
|
|
435
|
+
}
|
|
436
|
+
throw new DownloadError({
|
|
437
|
+
url,
|
|
438
|
+
message: `Too many redirects (max ${maxRedirects})`
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
254
442
|
// src/read-response-with-size-limit.ts
|
|
255
443
|
var DEFAULT_MAX_DOWNLOAD_SIZE = 2 * 1024 * 1024 * 1024;
|
|
256
444
|
async function readResponseWithSizeLimit({
|
|
@@ -440,7 +628,7 @@ function withUserAgentSuffix(headers, ...userAgentSuffixParts) {
|
|
|
440
628
|
}
|
|
441
629
|
|
|
442
630
|
// src/version.ts
|
|
443
|
-
var VERSION = true ? "3.0.
|
|
631
|
+
var VERSION = true ? "3.0.26" : "0.0.0-test";
|
|
444
632
|
|
|
445
633
|
// src/get-from-api.ts
|
|
446
634
|
var getOriginalFetch = () => globalThis.fetch;
|
|
@@ -450,10 +638,10 @@ var getFromApi = async ({
|
|
|
450
638
|
successfulResponseHandler,
|
|
451
639
|
failedResponseHandler,
|
|
452
640
|
abortSignal,
|
|
453
|
-
fetch = getOriginalFetch()
|
|
641
|
+
fetch: fetch2 = getOriginalFetch()
|
|
454
642
|
}) => {
|
|
455
643
|
try {
|
|
456
|
-
const response = await
|
|
644
|
+
const response = await fetch2(url, {
|
|
457
645
|
method: "GET",
|
|
458
646
|
headers: withUserAgentSuffix(
|
|
459
647
|
headers,
|
|
@@ -551,6 +739,15 @@ function injectJsonInstructionIntoMessages({
|
|
|
551
739
|
];
|
|
552
740
|
}
|
|
553
741
|
|
|
742
|
+
// src/is-same-origin.ts
|
|
743
|
+
function isSameOrigin(url, baseUrl) {
|
|
744
|
+
try {
|
|
745
|
+
return new URL(url).origin === new URL(baseUrl).origin;
|
|
746
|
+
} catch (e) {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
554
751
|
// src/is-url-supported.ts
|
|
555
752
|
function isUrlSupported({
|
|
556
753
|
mediaType,
|
|
@@ -890,7 +1087,7 @@ var postJsonToApi = async ({
|
|
|
890
1087
|
failedResponseHandler,
|
|
891
1088
|
successfulResponseHandler,
|
|
892
1089
|
abortSignal,
|
|
893
|
-
fetch
|
|
1090
|
+
fetch: fetch2
|
|
894
1091
|
}) => postToApi({
|
|
895
1092
|
url,
|
|
896
1093
|
headers: {
|
|
@@ -904,7 +1101,7 @@ var postJsonToApi = async ({
|
|
|
904
1101
|
failedResponseHandler,
|
|
905
1102
|
successfulResponseHandler,
|
|
906
1103
|
abortSignal,
|
|
907
|
-
fetch
|
|
1104
|
+
fetch: fetch2
|
|
908
1105
|
});
|
|
909
1106
|
var postFormDataToApi = async ({
|
|
910
1107
|
url,
|
|
@@ -913,7 +1110,7 @@ var postFormDataToApi = async ({
|
|
|
913
1110
|
failedResponseHandler,
|
|
914
1111
|
successfulResponseHandler,
|
|
915
1112
|
abortSignal,
|
|
916
|
-
fetch
|
|
1113
|
+
fetch: fetch2
|
|
917
1114
|
}) => postToApi({
|
|
918
1115
|
url,
|
|
919
1116
|
headers,
|
|
@@ -924,7 +1121,7 @@ var postFormDataToApi = async ({
|
|
|
924
1121
|
failedResponseHandler,
|
|
925
1122
|
successfulResponseHandler,
|
|
926
1123
|
abortSignal,
|
|
927
|
-
fetch
|
|
1124
|
+
fetch: fetch2
|
|
928
1125
|
});
|
|
929
1126
|
var postToApi = async ({
|
|
930
1127
|
url,
|
|
@@ -933,10 +1130,10 @@ var postToApi = async ({
|
|
|
933
1130
|
successfulResponseHandler,
|
|
934
1131
|
failedResponseHandler,
|
|
935
1132
|
abortSignal,
|
|
936
|
-
fetch = getOriginalFetch2()
|
|
1133
|
+
fetch: fetch2 = getOriginalFetch2()
|
|
937
1134
|
}) => {
|
|
938
1135
|
try {
|
|
939
|
-
const response = await
|
|
1136
|
+
const response = await fetch2(url, {
|
|
940
1137
|
method: "POST",
|
|
941
1138
|
headers: withUserAgentSuffix(
|
|
942
1139
|
headers,
|
|
@@ -2558,105 +2755,6 @@ function convertToBase64(value) {
|
|
|
2558
2755
|
return value instanceof Uint8Array ? convertUint8ArrayToBase64(value) : value;
|
|
2559
2756
|
}
|
|
2560
2757
|
|
|
2561
|
-
// src/validate-download-url.ts
|
|
2562
|
-
function validateDownloadUrl(url) {
|
|
2563
|
-
let parsed;
|
|
2564
|
-
try {
|
|
2565
|
-
parsed = new URL(url);
|
|
2566
|
-
} catch (e) {
|
|
2567
|
-
throw new DownloadError({
|
|
2568
|
-
url,
|
|
2569
|
-
message: `Invalid URL: ${url}`
|
|
2570
|
-
});
|
|
2571
|
-
}
|
|
2572
|
-
if (parsed.protocol === "data:") {
|
|
2573
|
-
return;
|
|
2574
|
-
}
|
|
2575
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2576
|
-
throw new DownloadError({
|
|
2577
|
-
url,
|
|
2578
|
-
message: `URL scheme must be http, https, or data, got ${parsed.protocol}`
|
|
2579
|
-
});
|
|
2580
|
-
}
|
|
2581
|
-
const hostname = parsed.hostname;
|
|
2582
|
-
if (!hostname) {
|
|
2583
|
-
throw new DownloadError({
|
|
2584
|
-
url,
|
|
2585
|
-
message: `URL must have a hostname`
|
|
2586
|
-
});
|
|
2587
|
-
}
|
|
2588
|
-
if (hostname === "localhost" || hostname.endsWith(".local") || hostname.endsWith(".localhost")) {
|
|
2589
|
-
throw new DownloadError({
|
|
2590
|
-
url,
|
|
2591
|
-
message: `URL with hostname ${hostname} is not allowed`
|
|
2592
|
-
});
|
|
2593
|
-
}
|
|
2594
|
-
if (hostname.startsWith("[") && hostname.endsWith("]")) {
|
|
2595
|
-
const ipv6 = hostname.slice(1, -1);
|
|
2596
|
-
if (isPrivateIPv6(ipv6)) {
|
|
2597
|
-
throw new DownloadError({
|
|
2598
|
-
url,
|
|
2599
|
-
message: `URL with IPv6 address ${hostname} is not allowed`
|
|
2600
|
-
});
|
|
2601
|
-
}
|
|
2602
|
-
return;
|
|
2603
|
-
}
|
|
2604
|
-
if (isIPv4(hostname)) {
|
|
2605
|
-
if (isPrivateIPv4(hostname)) {
|
|
2606
|
-
throw new DownloadError({
|
|
2607
|
-
url,
|
|
2608
|
-
message: `URL with IP address ${hostname} is not allowed`
|
|
2609
|
-
});
|
|
2610
|
-
}
|
|
2611
|
-
return;
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
function isIPv4(hostname) {
|
|
2615
|
-
const parts = hostname.split(".");
|
|
2616
|
-
if (parts.length !== 4) return false;
|
|
2617
|
-
return parts.every((part) => {
|
|
2618
|
-
const num = Number(part);
|
|
2619
|
-
return Number.isInteger(num) && num >= 0 && num <= 255 && String(num) === part;
|
|
2620
|
-
});
|
|
2621
|
-
}
|
|
2622
|
-
function isPrivateIPv4(ip) {
|
|
2623
|
-
const parts = ip.split(".").map(Number);
|
|
2624
|
-
const [a, b] = parts;
|
|
2625
|
-
if (a === 0) return true;
|
|
2626
|
-
if (a === 10) return true;
|
|
2627
|
-
if (a === 127) return true;
|
|
2628
|
-
if (a === 169 && b === 254) return true;
|
|
2629
|
-
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
2630
|
-
if (a === 192 && b === 168) return true;
|
|
2631
|
-
return false;
|
|
2632
|
-
}
|
|
2633
|
-
function isPrivateIPv6(ip) {
|
|
2634
|
-
const normalized = ip.toLowerCase();
|
|
2635
|
-
if (normalized === "::1") return true;
|
|
2636
|
-
if (normalized === "::") return true;
|
|
2637
|
-
if (normalized.startsWith("::ffff:")) {
|
|
2638
|
-
const mappedPart = normalized.slice(7);
|
|
2639
|
-
if (isIPv4(mappedPart)) {
|
|
2640
|
-
return isPrivateIPv4(mappedPart);
|
|
2641
|
-
}
|
|
2642
|
-
const hexParts = mappedPart.split(":");
|
|
2643
|
-
if (hexParts.length === 2) {
|
|
2644
|
-
const high = parseInt(hexParts[0], 16);
|
|
2645
|
-
const low = parseInt(hexParts[1], 16);
|
|
2646
|
-
if (!isNaN(high) && !isNaN(low)) {
|
|
2647
|
-
const a = high >> 8 & 255;
|
|
2648
|
-
const b = high & 255;
|
|
2649
|
-
const c = low >> 8 & 255;
|
|
2650
|
-
const d = low & 255;
|
|
2651
|
-
return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
}
|
|
2655
|
-
if (normalized.startsWith("fc") || normalized.startsWith("fd")) return true;
|
|
2656
|
-
if (normalized.startsWith("fe80")) return true;
|
|
2657
|
-
return false;
|
|
2658
|
-
}
|
|
2659
|
-
|
|
2660
2758
|
// src/without-trailing-slash.ts
|
|
2661
2759
|
function withoutTrailingSlash(url) {
|
|
2662
2760
|
return url == null ? void 0 : url.replace(/\/$/, "");
|
|
@@ -2716,13 +2814,16 @@ var import_stream2 = require("eventsource-parser/stream");
|
|
|
2716
2814
|
dynamicTool,
|
|
2717
2815
|
executeTool,
|
|
2718
2816
|
extractResponseHeaders,
|
|
2817
|
+
fetchWithValidatedRedirects,
|
|
2719
2818
|
generateId,
|
|
2720
2819
|
getErrorMessage,
|
|
2721
2820
|
getFromApi,
|
|
2722
2821
|
getRuntimeEnvironmentUserAgent,
|
|
2723
2822
|
injectJsonInstructionIntoMessages,
|
|
2724
2823
|
isAbortError,
|
|
2824
|
+
isBrowserRuntime,
|
|
2725
2825
|
isParsableJson,
|
|
2826
|
+
isSameOrigin,
|
|
2726
2827
|
isUrlSupported,
|
|
2727
2828
|
isValidator,
|
|
2728
2829
|
jsonSchema,
|