@ai-sdk/provider-utils 3.0.24 → 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 CHANGED
@@ -1,5 +1,34 @@
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
+
24
+ ## 3.0.25
25
+
26
+ ### Patch Changes
27
+
28
+ - 783fa6c: chore: ensure consistent import handling and avoid import duplicates or cycles
29
+ - Updated dependencies [783fa6c]
30
+ - @ai-sdk/provider@2.0.3
31
+
3
32
  ## 3.0.24
4
33
 
5
34
  ### 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
  *
@@ -447,7 +501,6 @@ type InferSchema<SCHEMA> = SCHEMA extends z3.Schema ? z3.infer<SCHEMA> : SCHEMA
447
501
  declare function jsonSchema<OBJECT = unknown>(jsonSchema: JSONSchema7 | (() => JSONSchema7), { validate, }?: {
448
502
  validate?: (value: unknown) => ValidationResult<OBJECT> | PromiseLike<ValidationResult<OBJECT>>;
449
503
  }): Schema<OBJECT>;
450
- declare function asSchema<OBJECT>(schema: FlexibleSchema<OBJECT> | undefined): Schema<OBJECT>;
451
504
 
452
505
  /**
453
506
  Additional provider-specific options.
@@ -868,6 +921,8 @@ type Resolvable<T> = T | Promise<T> | (() => T) | (() => Promise<T>);
868
921
  */
869
922
  declare function resolve<T>(value: Resolvable<T>): Promise<T>;
870
923
 
924
+ declare function asSchema<OBJECT>(schema: FlexibleSchema<OBJECT> | undefined): Schema<OBJECT>;
925
+
871
926
  declare function convertBase64ToUint8Array(base64String: string): Uint8Array<ArrayBuffer>;
872
927
  declare function convertUint8ArrayToBase64(array: Uint8Array): string;
873
928
  declare function convertToBase64(value: string | Uint8Array): string;
@@ -876,6 +931,10 @@ declare function convertToBase64(value: string | Uint8Array): string;
876
931
  * Validates that a URL is safe to download from, blocking private/internal addresses
877
932
  * to prevent SSRF attacks.
878
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
+ *
879
938
  * @param url - The URL string to validate.
880
939
  * @throws DownloadError if the URL is unsafe.
881
940
  */
@@ -1013,4 +1072,4 @@ interface ToolResult<NAME extends string, INPUT, OUTPUT> {
1013
1072
  dynamic?: boolean;
1014
1073
  }
1015
1074
 
1016
- 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
  *
@@ -447,7 +501,6 @@ type InferSchema<SCHEMA> = SCHEMA extends z3.Schema ? z3.infer<SCHEMA> : SCHEMA
447
501
  declare function jsonSchema<OBJECT = unknown>(jsonSchema: JSONSchema7 | (() => JSONSchema7), { validate, }?: {
448
502
  validate?: (value: unknown) => ValidationResult<OBJECT> | PromiseLike<ValidationResult<OBJECT>>;
449
503
  }): Schema<OBJECT>;
450
- declare function asSchema<OBJECT>(schema: FlexibleSchema<OBJECT> | undefined): Schema<OBJECT>;
451
504
 
452
505
  /**
453
506
  Additional provider-specific options.
@@ -868,6 +921,8 @@ type Resolvable<T> = T | Promise<T> | (() => T) | (() => Promise<T>);
868
921
  */
869
922
  declare function resolve<T>(value: Resolvable<T>): Promise<T>;
870
923
 
924
+ declare function asSchema<OBJECT>(schema: FlexibleSchema<OBJECT> | undefined): Schema<OBJECT>;
925
+
871
926
  declare function convertBase64ToUint8Array(base64String: string): Uint8Array<ArrayBuffer>;
872
927
  declare function convertUint8ArrayToBase64(array: Uint8Array): string;
873
928
  declare function convertToBase64(value: string | Uint8Array): string;
@@ -876,6 +931,10 @@ declare function convertToBase64(value: string | Uint8Array): string;
876
931
  * Validates that a URL is safe to download from, blocking private/internal addresses
877
932
  * to prevent SSRF attacks.
878
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
+ *
879
938
  * @param url - The URL string to validate.
880
939
  * @throws DownloadError if the URL is unsafe.
881
940
  */
@@ -1013,4 +1072,4 @@ interface ToolResult<NAME extends string, INPUT, OUTPUT> {
1013
1072
  dynamic?: boolean;
1014
1073
  }
1015
1074
 
1016
- 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 };