/** * Outbound-fetch guard for the authorization server (#340). * * Anything the AS fetches because a client told it to (today nothing; the * Client ID Metadata Document behind a CIMD client_id when that lands) is a * server-side request forgery surface: a hostile client_id can point at the * Docker network, the cloud metadata service, or this very container. The * guard is three checks that all have to pass: * * 1. the URL itself: https only, no credentials, no literal private address; * 2. every address the hostname resolves to is public; * 3. the socket is pinned to the addresses that passed check 2, so a DNS * answer that changes between resolve and connect (rebinding) cannot * steer the connection somewhere private. * * The Coolify base URL is deliberately NOT routed through here. It is operator * configuration, and on a Coolify deployment it is an internal address by * design. */ import { type LookupFunction } from 'node:net'; export declare class UnsafeUrlError extends Error { constructor(message: string); } export interface ResolvedAddress { address: string; family: 4 | 6; } /** Resolves a hostname to every address it answers with. Injectable for tests. */ export type Resolver = (hostname: string) => Promise; /** True for anything that is not a public unicast address (unparseable counts). */ export declare function isForbiddenAddress(address: string): boolean; /** * Check 1: the URL as written. Throws UnsafeUrlError on anything that is not a * credential-free https URL to a non-local hostname or public literal address. */ export declare function assertPublicUrl(raw: string, options?: { allowInsecureHttp?: boolean; }): URL; /** Check 2: every address the name resolves to is public; returns them. */ export declare function resolvePublicAddresses(hostname: string, resolver?: Resolver): Promise; /** * Check 3: a Node `lookup` that answers from the vetted list and never * consults DNS again, so the connection cannot be rebound after the check. */ export declare function pinnedLookup(addresses: ResolvedAddress[]): LookupFunction; export interface FetchPublicJsonOptions { /** Hard deadline for the whole exchange. Claude allows 10 s for discovery. */ timeoutMs?: number; /** Response size cap; a metadata document is a few hundred bytes. */ maxBytes?: number; /** Development only (mirrors MCP_ALLOW_INSECURE_HTTP). */ allowInsecureHttp?: boolean; resolver?: Resolver; } /** * GET a JSON document from a public URL with all three checks applied. * Redirects are not followed: a 3xx is a failure, because following one would * re-open every question the checks just answered. */ export declare function fetchPublicJson(raw: string, options?: FetchPublicJsonOptions): Promise;