ai
Version:
AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.
112 lines (85 loc) • 4.67 kB
text/mdx
---
title: Secure URL Fetching
description: How the AI SDK protects server-side fetches of URLs returned by model providers, and how to harden your deployment further.
---
# 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:
```ts
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;
```
```ts
import { createFal } from '-sdk/fal';
const fal = createFal({ fetch: safeFetch });
```
The SDK's URL validation and your custom fetch's connect-time pinning are
complementary — keep both.