@advance-labs/net-guard
v0.2.2
Published
SSRF-guarded HTTP fetch seam — DNS-resolves and rejects private/loopback/link-local/CGNAT/cloud-metadata addresses, re-validates every redirect hop, caps body size and time, and host-pins to defeat DNS rebinding.
Maintainers
Readme
@advance-labs/net-guard
SSRF-guarded HTTP fetch seam. Closes finding C1 of the Autopilot security review: the existing
crawler/backlinks HTTP layer follows redirects with no private-IP guard, so a user-supplied URL can
reach 169.254.169.254 (cloud metadata), localhost, or RFC-1918 hosts.
Use it for any untrusted URL
import { safeFetch, createLiveSafeFetchDeps } from '@advance-labs/net-guard';
const deps = createLiveSafeFetchDeps();
const res = await safeFetch('https://prospect.example/', {}, deps);
if (!res.ok) {
// res.blockedReason: 'scheme-not-allowed' | 'private-address' | 'dns-resolution-failed'
// | 'too-many-redirects' | 'body-too-large' | 'timeout'
}What it guards (in order)
- Scheme allowlist —
http:/https:only (rejectsfile:,gopher:, …). - DNS resolve + private-address rejection — refuses if any resolved IP is loopback, RFC-1918, link-local (incl. the metadata IP), CGNAT, IPv6 ULA/link-local, or an IPv4-mapped private address. Malformed addresses fail closed.
- Per-hop redirect re-validation — redirects are manual; the full check re-runs on every target, so a public host can't 302 you to an internal one.
- Body + time caps —
maxBodyBytes(default 2 MB) andtimeoutMs(default 10 s, aborts the request).
All logic is pure and injected (resolve, fetchImpl) — unit-tested with zero network. See
docs/CONVENTIONS.md security invariant #1.
