本页还没有译文
本页 27 段还没译成한국어,下面显示的是英文原文。Secure URL Fetching
还没翻译译自英文(ai-sdk.dev)本页 27 段都还没译
Many providers return a URL in their response body — a generated image,
audio, or video to download, or a polling URL to check job status. The AI SDK
fetches these server-side and returns the result to your code. Because that URL
comes from an external service, a malicious or compromised provider (or anyone
able to tamper with the response) could point it at an internal address such as
a cloud-metadata endpoint (http://169.254.169.254/…), a private host
(http://10.0.0.5/…), or localhost.
To prevent that, the SDK validates every response-supplied URL before fetching it. This happens automatically inside the provider packages — you don't need to configure anything.
For authenticated task-status polling, providers can construct the first URL from the configured API endpoint. The SDK trusts that configured origin for the initial request, but manually follows and validates every redirect away from it. MiniMax, Kling AI, and ByteDance video polling use this protected path.
What the SDK protects against
When the SDK fetches a URL taken from a provider response, it:
- Rejects private, loopback, and link-local targets — IPv4 (
10/8,172.16/12,192.168/16,127/8,169.254/16, CGNAT, multicast, …) and the equivalent IPv6 ranges, pluslocalhost,.local,.localhost, and.internalhostnames. Non-http(s)schemes are rejected too. - Re-validates every redirect hop — a URL that passes but then redirects to an internal address is blocked; the redirect is never followed blindly.
- Validates DNS at connection time on Node.js — every resolved address is checked, and the socket is pinned to the validated DNS result so DNS rebinding cannot introduce a different address between validation and connection.
- Strips risky request headers — proxy-forwarding, cloud-metadata, and cookie headers are removed before the request.
- Drops credentials across origins — caller headers (
Authorization,Cookie, and provider-specific API-key headers alike) are not sent to a host on a different origin than the provider's; a redirect that crosses origin drops all of them except the user-agent.
A blocked URL surfaces as a DownloadError.
Fetching untrusted URLs directly
For custom integrations using @ai-sdk/provider-utils, use fetchUntrustedUrl
when the URL comes from a response or other untrusted input. It validates URLs
and redirects and withholds credentials and unknown custom headers on the first
request unless the URL matches a configured origin:
import { fetchUntrustedUrl } from '@ai-sdk/provider-utils';
const response = await fetchUntrustedUrl({
url: result.downloadUrl,
headers: {
authorization: `Bearer ${apiKey}`,
accept: 'image/*',
},
credentialedOrigin: providerBaseURL,
});Here, Authorization is sent only when the initial URL has the same origin as
providerBaseURL. Otherwise, only allowlisted metadata such as Accept,
Range, and User-Agent is sent. A cross-origin redirect drops all caller
headers except User-Agent, and credentials are never reattached on later hops.
credentialedOrigin must come from your configuration, never from the response
URL. It does not disable URL validation. For a configured private endpoint, pass
trustedOrigin as well; if credentialedOrigin is omitted, fetchUntrustedUrl
uses trustedOrigin as the origin allowed to receive credentials. Extra protocol
metadata can be allowed with untrustedFirstHopHeaders: ['x-protocol-version'].
Only list headers that are safe to disclose to any destination; use a configured
origin for credentials.
The existing fetchWithValidatedRedirects helper preserves its first-request
behavior: it sanitizes risky headers but still forwards authentication and
custom headers. Switch to fetchUntrustedUrl to opt into first-request
credential isolation. Existing calls to getFromApi also retain their behavior;
pass credentialedOrigin when fetching response-supplied URLs with credentials.
Self-hosted and local endpoints
URLs that are same-origin with the provider endpoint you configured (e.g. a
custom baseURL pointing at a self-hosted or localhost deployment) are
exempt from these checks — they target exactly the host you told the SDK to
talk to. This also applies to task-status polling. Any redirect off that origin
is still validated before the redirected request is sent.
DNS validation across runtimes
On Node.js, the default validated download fetch uses node:dns and an
undici connector hook to validate every resolved address at connection time.
The connector uses those exact results, closing both hostname-to-private-IP and
DNS-rebinding bypasses.
Wrapping or replacing global fetch does not disable this protection: the
default Node.js download transport is independent of global fetch.
Bun, Deno, Cloudflare Workers, and framework edge runtimes use their platform
fetch, even when they expose a Node-compatible process object.
If you explicitly inject a custom fetch, it is responsible for equivalent DNS
validation and connection pinning. Other runtimes do not expose Node's
DNS/socket hooks, so server deployments on those runtimes should restrict
network egress to private, loopback, link-local, and cloud-metadata ranges.
Hardening your deployment
If your server fetches provider-supplied URLs and you want to close the DNS gaps, use one (ideally both) of these:
1. Restrict outbound egress at the network layer
Deny your server's network egress to 169.254.0.0/16, RFC-1918 ranges, and
loopback. This is the most robust control and is independent of application
code.
2. Harden an injected fetch
The Node.js default is already pinned. If you explicitly inject a custom
fetch, back it with an undici
Agent whose connect.lookup validates the resolved IP and lets the socket
connect only to a safe address — closing both the hostname-to-private and the
DNS-rebinding windows:
import { Agent, fetch as undiciFetch } from 'undici';
import { lookup } from 'node:dns';
// Your own check that returns true for private/loopback/link-local addresses.
declare function isUnsafeAddress(ip: string): boolean;
const safeLookup: typeof lookup = (hostname, options, callback) => {
lookup(hostname, options as any, (err, address, family) => {
if (!err && typeof address === 'string' && isUnsafeAddress(address)) {
callback(new Error(`Refusing to connect to ${address}`), '', 0);
return;
}
(callback as any)(err, address, family);
});
};
const safeDispatcher = new Agent({ connect: { lookup: safeLookup } });
const safeFetch: typeof fetch = (input, init) =>
undiciFetch(input, { ...init, dispatcher: safeDispatcher }) as any;import { createFal } from '@ai-sdk/fal';
const fal = createFal({ fetch: safeFetch });The SDK's URL validation and your custom fetch's connect-time pinning are complementary — keep both.