zidane/ tools/ fetch-url
The opt-in fetch_url tool, with an SSRF guard on every redirect hop.
import { … } from 'zidane/tools/fetch-url'src/tools/fetch-url.ts
Use fetchUrl when the agent reads a web page or calls an HTTP API. It accepts only http and https URLs and turns HTML into plain text. On every redirect hop, it refuses loopback, private, link-local and cloud-metadata addresses. Limit it to certain hosts with behavior.fetchUrlAllowHosts. The entry also exports the guard helpers (fetchWithSsrfGuard, isBlockedAddress, isHostAllowed) for your own network code.
import { createAgent } from 'zidane'
import { anthropic } from 'zidane/providers/anthropic'
import { fetchUrl } from 'zidane/tools/fetch-url'
const agent = createAgent({
provider: anthropic(),
tools: { fetch_url: fetchUrl },
behavior: { fetchUrlAllowHosts: ['example.com'] },
})
await agent.run({ prompt: 'Fetch https://example.com and summarize it.' })Functions#
clearFetchUrlCacheFunction#
Test seam — drop all cached entries.
function clearFetchUrlCache(): voidfetchWithSsrfGuardFunction#
Fetches a URL, following redirects and checking every target host against the SSRF blocklist before connecting. Throws on a blocked host or too many redirects.
Walks up to MAX_REDIRECTS hops and re-validates the target host of
each 3xx response before following it.
function fetchWithSsrfGuard(
startUrl: URL,
headers: Record<string, string>,
signal: AbortSignal,
maxBufferBytes: number,
deps?: FetchSsrfDeps,
allowHosts?: readonly string[],
): Promise<PinnedResponse>Parameters
| Name | Type | Default | Description |
|---|---|---|---|
| startUrl | URL | URL to fetch. | |
| headers | Record<string, string> | Request headers. | |
| signal | AbortSignal | Aborts the request. | |
| maxBufferBytes | number | Body bytes to keep per response. | |
| deps? | FetchSsrfDeps | {} | Test seam for the resolver and the request function. |
| allowHosts? | readonly string[] | Allowed host names. Empty or |
Returns
Promise<PinnedResponse>isBlockedAddressFunction#
Tells whether an IP address is in a range the SSRF guard refuses: loopback, private, link-local, multicast and similar. Anything that is not an IP literal is refused too.
function isBlockedAddress(ip: string): booleanParameters
| Name | Type | Description |
|---|---|---|
| ip | string | IPv4 or IPv6 address literal. |
Returns
booleanisHostAllowedFunction#
Host-suffix allowlist check. An empty / undefined list means "no allowlist"
→ every host passes (the SSRF blocklist is the only gate). Otherwise the
host must equal, or be a subdomain of, one of the entries. Subdomain match
is on a dot boundary so example.com matches docs.example.com but not
notexample.com.
function isHostAllowed(
hostname: string,
allowHosts: readonly string[] | undefined,
): booleanParameters
| Name | Type | Description |
|---|---|---|
| hostname | string | Host to check. |
| allowHosts | readonly string[] | undefined | Allowed host names. Empty or |
Returns
booleanpinnedRequestFunction#
Sends one GET request to a fixed IP address while keeping the original host name for the Host header and TLS server name. Resolves with the status, body and any redirect location.
Connects to pinnedIp directly (bypassing Node/Bun's own DNS resolution).
This is the concrete defense against DNS rebinding — see the file-level
docstring. Reads up to maxBufferBytes of body and then forcibly closes the
connection.
function pinnedRequest(
url: URL,
pinnedIp: string,
family: 4 | 6,
headers: Record<string, string>,
signal: AbortSignal,
maxBufferBytes: number,
): Promise<PinnedResponse>Parameters
| Name | Type | Description |
|---|---|---|
| url | URL | URL to request. |
| pinnedIp | string | IP address to connect to. |
| family | 4 | 6 | IP version of |
| headers | Record<string, string> | Request headers. |
| signal | AbortSignal | Aborts the request. |
| maxBufferBytes | number | Body bytes to keep. The rest is dropped and |
Returns
Promise<PinnedResponse>Constants#
__fetchCacheTestApiConstant#
Test seam — exercise the cache get/set/eviction in isolation without
standing up the full SSRF + HTTP path. Not part of the public tool surface;
execute uses the module-private functions directly.
const __fetchCacheTestApi: { bytes: () => number; get: (key: string) => string | null; key: (url: string, cap: number, allowHosts?: readonly string[]) => string; set: (key: string, output: string, ttlMs: number) => void; size: () => number }fetchUrlConstant#
The fetch_url tool. Fetches an HTTP or HTTPS URL and returns its body as text, with an SSRF guard on every hop.
const fetchUrl: ToolDefInterfaces#
FetchSsrfDepsInterface#
Test seams for fetchWithSsrfGuard — production callers use the
defaults. Hosts that want to restrict egress should set
behavior.fetchUrlAllowHosts rather than reaching into these.
interface FetchSsrfDepsProperties
| Name | Type | Description |
|---|---|---|
| requestImpl? | (url: URL, pinnedIp: string, family: 4 | 6, headers: Record<string, string>, signal: AbortSignal, maxBufferBytes: number) => Promise<PinnedResponse> | Execute a single pinned HTTP request. Defaults to |
| resolver? | (hostname: string) => Promise<ResolvedAddress | ResolvedAddress[]> | Resolve + validate a hostname, in connect-preference order. Defaults to |
ResolvedAddressInterface#
One resolved address of a host.
interface ResolvedAddressProperties
| Name | Type | Description |
|---|---|---|
| address | string | IP address literal. |
| family | 4 | 6 | IP version of |