/** * @license * Copyright 2026 Steven Roussey * SPDX-License-Identifier: Apache-2.0 */ /** * Strict, structured allow-list for "local-only" HTTP(S) base URLs used by * provider clients that talk to on-host backends (e.g. llama-server, * stable-diffusion.cpp HTTP server). * * Design goals: * - No runtime DNS resolution — judgement is by hostname literal only. * - No substring/prefix heuristics that can be DNS-rebound or spoofed * (`attacker.localhost`, IPv6 strings that merely start with "fc"/"fd", * `localhost.attacker.com`, percent-encoded forms, IDN, underscores …). * - No WHATWG URL canonicalization of the host before validation — the * URL parser silently rewrites non-standard IPv4 spellings * (`0x7f.0.0.1`, `2130706433`, `010.0.0.1`) to canonical dotted-quads, * which would defeat the strict-literal grammar below. We extract the * host from the raw URL string and validate THAT literal. * - Hostname grammar is shrunk to forms that cannot be rebound: * * literal `localhost` * * IPv4 literals in loopback / RFC 1918 / link-local ranges * * IPv6 literals in `::1`, `fc00::/7`, `fe80::/10`, plus IPv4-mapped * forms that decode to an allowed IPv4 * - `*.localhost` is intentionally REJECTED (it is the bypass vector that * motivated this module). */ /** * Parse an IPv6 literal (without surrounding brackets) into its 16-byte * binary representation. Returns `null` on any malformed input, on * zone-identifier suffixes (`%eth0` etc.), or on IPv4-suffix forms that * are not cleanly recognisable. * * The `::` compression must actually compress one or more zero groups — * inputs like `fc00:0:0:0:0:0:0::1` (8 explicit groups *and* a `::`) are * structurally invalid and are rejected even though the byte representation * they’d round to is otherwise local. Strictness here is defence in depth * against any downstream parser that takes a different view of `::`. */ export declare function parseIpv6(host: string): Uint8Array | null; /** * Returns true if `host` is a dotted-quad IPv4 literal in a strictly local * range: loopback `127.0.0.0/8`, RFC 1918 (`10/8`, `172.16/12`, `192.168/16`), * or link-local `169.254/16`. Rejects `0.0.0.0` and `255.255.255.255`, * rejects leading zeros (no `010.0.0.1`), and rejects malformed input. */ export declare function isLocalIpv4(host: string): boolean; /** * Returns true if `host` is an IPv6 literal (without surrounding brackets) * that lies in `::1`, `fc00::/7` (ULA), `fe80::/10` (link-local), or is an * IPv4-mapped IPv6 (`::ffff:0:0/96`) whose embedded IPv4 satisfies * {@link isLocalIpv4}. Parses structurally — no string-prefix matching. */ export declare function isLocalIpv6(host: string): boolean; /** * Returns true if `host` is a recognised local hostname literal: exactly * `localhost`, or an IPv4/IPv6 literal in an allowed local range. * * IPv6 literals must be passed WITHOUT surrounding brackets (see * {@link normalizeLocalHttpUrl}, which strips them before calling this). * * Rejects everything else, including `*.localhost`, IDN, percent-encoded * forms, underscores, and any name that would require DNS resolution. */ export declare function isLocalHostname(host: string): boolean; /** * Returns true if `host` is a STRICTLY loopback hostname literal — the policy * for AI server clients that, by product decision, only ever talk to a server * on the same host: * * `localhost` (case-insensitive, optional single trailing dot) * * IPv4 in `127.0.0.0/8` * * IPv6 `::1` and IPv4-mapped loopback (`::ffff:127.x.x.x`) * * Everything else — RFC 1918, link-local (incl. the `169.254.169.254` * cloud-metadata IP), ULA, public, `*.localhost`, IDN, percent-encoded, * and unsigned-integer IPv4 spellings — returns false. This is intentionally * narrower than {@link isLocalHostname}: it is the initial-URL and * redirect-hop gate used by `localOnlyFetch` to close the link-local * metadata SSRF vector. * * IPv6 literals must be passed WITHOUT surrounding brackets. */ export declare function isLoopbackHostname(host: string): boolean; /** * Extract the literal host substring from `rawUrl` BEFORE the WHATWG URL * parser canonicalises it. Returns the host as it appeared in the source * (case preserved, brackets stripped for IPv6), or `null` if the URL does * not match the basic `scheme://[user[:pw]@]host[:port][/...]` grammar. * * This is deliberately separate from `new URL().hostname` because the * WHATWG parser rewrites: * - `0x7f.0.0.1` → `127.0.0.1` * - `2130706433` → `127.0.0.1` * - `010.0.0.1` → `10.0.0.1` (in lenient runtimes) * and any of those rewrites would silently bypass * {@link isLocalHostname}'s strict-literal grammar. */ export declare function extractRawHost(rawUrl: string): string | null; /** * Parse `rawUrl`, validate that it targets a strictly-local HTTP(S) endpoint, * and return a canonical form with the query string and fragment stripped * and any trailing slashes removed from the path. * * `label` is the human-readable provider tag prepended to thrown errors * (e.g. `"LlamaCppServer"`) so callers don't need to wrap. * * @throws Error if the URL is malformed, not http(s), carries credentials, * or targets a non-local hostname (including non-literal IPv4 * spellings that WHATWG would canonicalise to a local literal). */ export declare function normalizeLocalHttpUrl(rawUrl: string, label: string): string; //# sourceMappingURL=localUrl.d.ts.map