@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
77 lines (76 loc) • 3.59 kB
TypeScript
/**
* SSRF Guard — Safe URL Validation Utility
*
* Prevents Server-Side Request Forgery by:
* 1. Enforcing HTTPS-only (no plain HTTP).
* 2. Normalising encoded IPv4 forms (octal, hex, decimal integer, IPv4-mapped IPv6)
* to canonical dotted-decimal before rangechecking.
* 3. Resolving the hostname for **both** A and AAAA families and rejecting
* requests to RFC 1918 private ranges, loopback, link-local, CGNAT,
* IPv6 link-local/ULA, and cloud metadata endpoints
* (AWS / GCP / Azure / Alibaba).
* 4. Re-throwing on DNS failure rather than silently allowing the request.
*
* **DNS rebinding residual race:** `assertSafeUrl` validates the IP at the
* moment of the lookup. If the resolver returns a public IP here and a private
* IP at the actual `fetch()` call, the guard is bypassed. To eliminate the
* race, use the companion `safeDownload` helper in `safeFetch.ts` which pins
* the resolved IP onto the request via an undici Agent dispatcher.
*
* Usage:
* await assertSafeUrl(url);
* // ... or, for actual downloads: ...
* await safeDownload(url, { maxBytes, label });
*
* @module utils/ssrfGuard
*/
import type { PinnedAddress } from "../types/index.js";
/**
* Assert that `url` is safe to fetch server-side.
*
* @throws {Error} when the URL is non-HTTPS, parses as a blocked IP literal,
* or resolves (A or AAAA) to a blocked IP. **Also throws on DNS lookup
* failure** (the previous behaviour of silently allowing was a bypass —
* an attacker-controlled resolver could force NXDOMAIN here and a private
* IP at the actual fetch).
*/
export declare function assertSafeUrl(url: string): Promise<void>;
/**
* Validate `url` for a download that goes through an HTTP proxy.
*
* The proxy, not this process, resolves the host name and opens the
* connection, so there is no local DNS answer to check or to pin. What can be
* judged from the URL alone is judged: HTTPS only, blocked IP literals in any
* encoding, and host names that can only mean something local or private (a
* single-label name such as `intranet`, and the internal suffixes above).
* What the proxy resolves any other name to is the proxy's egress policy to
* enforce; that is the trade for using a proxy at all, and why this is only
* used when one applies. Without a proxy {@link validateAndResolveUrl} runs
* instead and the connection is pinned to the addresses it cleared.
*
* Same throw semantics as {@link assertSafeUrl}.
*/
export declare function assertSafeUrlForProxy(url: string): void;
/**
* Validate `url` and return the resolved address set that should be used for
* the actual fetch (companion to `safeFetch.ts:safeDownload`).
*
* For IP-literal hosts, `addresses` is the normalised IP as a singleton. For
* hostnames it is **every** validated address, IPv4 first, so the connect
* layer can race families (Happy Eyeballs). Pinning a single OS-preferred
* address breaks every download on IPv4-only networks whenever the resolver
* prefers AAAA: the pinned connection has no second address to fall back to,
* so it times out where plain `curl` (which races both families) succeeds.
* `ip`/`family` mirror the first entry for callers that need a single pin.
*
* Same throw semantics as {@link assertSafeUrl}.
*
* This is the canonical entry point for binary downloads where DNS-rebinding
* pinning matters — see `safeFetch.ts`.
*/
export declare function validateAndResolveUrl(url: string): Promise<{
url: string;
ip: string;
family: 4 | 6;
addresses: readonly PinnedAddress[];
}>;