Secure URL Fetching

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.

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, plus localhost and .local. 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.

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. Any redirect off that origin is still validated.

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.

If you inject or globally replace 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 inject or globally replace 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.