@terraza/safe-fetch
v0.1.0
Published
SSRF-safe HTTP(S) fetch for Terraza: manual redirect handling, per-hop host validation, and DNS-pinned connections that reject private/loopback/link-local targets
Readme
@terraza/safe-fetch
SSRF-safe HTTP(S) fetch for Terraza. Zero runtime dependencies (Node core only).
The platform kept hitting the same bug: validate the initial URL's host, then
fetch(..., { redirect: "follow" }) — which chases 3xx redirects and independently
re-resolves DNS, so a redirect to an internal host or a low-TTL DNS rebind (TOCTOU)
reaches a target the check was meant to block. The 2026-07-03 fleet audit found it in
four places. This package is the single fix; route every "fetch a URL that could be
attacker-influenced" call through it.
Guarantees
- Manual redirects, re-validated per hop — no
redirect: "follow". - DNS-pinned connections — each request resolves once, validates every returned address, and pins the vetted IP into the socket, so the connection lands on exactly the address checked. Closes the rebind TOCTOU.
- Blocks internal targets — loopback, RFC1918 private, link-local (incl. cloud
metadata
169.254.169.254), CGNAT, IPv6 ULA/link-local, multicast, and v4-mapped v6. Unparseable addresses fail closed. - TLS unchanged — SNI and cert validation stay bound to the real hostname; only the connect-time IP is pinned.
- Bounded — body-size cap and an overall timeout across all hops.
Usage
import { safeFetch, SsrfError } from "@terraza/safe-fetch";
const res = await safeFetch(url, {
maxBytes: 64 * 1024,
timeoutMs: 5_000,
maxRedirects: 0, // reject any redirect (e.g. static docs that must be same-origin)
});
if (!res.ok) throw new Error(`fetch failed: ${res.status}`);
const doc = res.json();safeFetch throws SsrfError when a target resolves to a private/internal address or a
non-http(s) scheme, and SafeFetchError on timeout, size-cap, or redirect-limit.
Also exported: assertPublicHost(hostname) (resolve + validate without fetching) and the
isPrivateAddress(ip) / isPrivateIpv4(ip) classifiers.
Consumers
Adopted by (or slated for): @terraza/terraza-mcp CIMD fetch, terraza-shelf
url-ingest, terraza-post attachment fetch. See
terraza/platform/references/security-audit-2026-07-03.md.
The
isAddressBlocked/resolveHostoptions are dependency-injection seams for tests and advanced callers; overridingisAddressBlockeddisables the SSRF guard, so never wire it to untrusted input.
