/**
 * @module @arcis/node/validation/url-async
 *
 * Async SSRF guard that closes the DNS-rebinding TOCTOU gap left open
 * by the synchronous `validateUrl` (sdk-vectors.md #31, issue #50).
 *
 * The synchronous `validateUrl` only checks the *string form* of the
 * hostname. It catches obvious cases — `127.0.0.1`, `10.0.0.1`,
 * `169.254.169.254` — but a hostname like `evil.com` passes through
 * even when its DNS A-record points to `10.0.0.1`, because resolution
 * happens later inside `fetch`. An attacker controlling the
 * `evil.com` zone can also rebind the answer between Arcis's check
 * and the actual TCP connect, which is the classic DNS TOCTOU.
 *
 * Two layers of fix shipped here:
 *
 * 1. **`validateUrlAsync(url, options)`** — runs the existing sync
 *    `validateUrl` first, then `dns.lookup(hostname, { all: true })`,
 *    then re-runs the same private-range check on every resolved
 *    address. Returns the pinned IP list so callers can reuse it for
 *    the actual connection (closing the TOCTOU window).
 *
 * 2. **`pinnedDnsLookup(ip)`** — returns a Node `lookup` callback
 *    that resolves any hostname to the pre-validated IP. Wire this
 *    into `https.request({ lookup })` / `http.request({ lookup })`
 *    so the connection uses the IP Arcis already validated, not
 *    whatever DNS returns at connect time. Pure stdlib — no undici,
 *    no extra dep.
 *
 * 3. **`safeFollowRedirect(prev, location, options)`** — when the
 *    server replies 30x, run the same async guard against the new
 *    Location URL. Resolves the absolute URL using the previous
 *    response URL as base. Caller decides whether to follow.
 *
 * The function signatures keep `lookup` injectable so tests can
 * substitute a fake resolver without monkey-patching `node:dns`.
 *
 * ```ts
 * import https from 'node:https';
 * import { validateUrlAsync, pinnedDnsLookup } from '@arcis/node';
 *
 * const result = await validateUrlAsync(url);
 * if (!result.safe) throw new Error(result.reason);
 *
 * https.get(url, { lookup: pinnedDnsLookup(result.resolvedIp!) }, (res) => {
 *   // The TCP connect now goes to result.resolvedIp regardless of
 *   // what DNS would say at this exact moment.
 * });
 * ```
 */
import { type ValidateUrlOptions, type ValidateUrlResult } from './url';
/**
 * Subset of `dns.lookup`'s `{ all: true }` callback signature. Kept
 * narrow so a test fake can satisfy it without depending on Node's
 * full `LookupAddress` type.
 */
export type LookupAddress = {
    address: string;
    family: number;
};
/**
 * Function shape compatible with `dns.lookup(hostname, { all: true })`.
 * Returns a list of resolved addresses. Tests inject a fake.
 */
export type DnsLookup = (hostname: string) => Promise<LookupAddress[]>;
export interface ValidateUrlAsyncOptions extends ValidateUrlOptions {
    /**
     * DNS lookup function. Defaults to a Promise wrapper around
     * `dns.lookup(hostname, { all: true })`. Tests inject a stub.
     */
    lookup?: DnsLookup;
    /**
     * If true, accept the first non-private IP and ignore the rest.
     * Default false: every resolved IP must pass the private-range
     * check. Hosts with mixed-public/private answers (round-robin DNS
     * with one internal record) still fail-closed.
     */
    acceptFirstPublic?: boolean;
}
export interface ValidateUrlAsyncResult extends ValidateUrlResult {
    /**
     * Single pinned IP (the first public address if all checks passed,
     * or undefined when the string-only synchronous validator already
     * decided — e.g., the hostname *was* a literal IP). Use this with
     * `pinnedDnsLookup()` to wire the actual fetch.
     */
    resolvedIp?: string;
    /** Every IP returned by DNS, in resolver order. */
    resolvedIps?: string[];
}
/**
 * Async SSRF guard with DNS resolution. Runs the sync validator
 * first, then resolves DNS and validates every returned IP against
 * the same private-range rules. Returns a pinned IP for the caller
 * to reuse.
 *
 * Failure modes (any returns `{ safe: false, reason }`):
 * - Sync validator already rejects (string-pattern fail).
 * - DNS lookup throws (NXDOMAIN, network error). Reason carries the
 *   underlying error message.
 * - DNS returns no addresses.
 * - Any resolved address fails the private-range check (default) or
 *   *all* fail it when `acceptFirstPublic` is true.
 */
export declare function validateUrlAsync(url: string, options?: ValidateUrlAsyncOptions): Promise<ValidateUrlAsyncResult>;
/**
 * Build a `lookup` callback that pins the resolution to a single
 * pre-validated IP, regardless of what DNS would say at connect time.
 * Drop into `https.request({ lookup })` / `http.request({ lookup })`.
 *
 * Closes the TOCTOU window between `validateUrlAsync` and the actual
 * TCP connect. The pinned IP must be one that already passed the
 * async validator — wiring it without that check defeats the purpose.
 *
 * The returned function matches Node's `dns.lookup` callback shape
 * for the `{ all: false, family: 0 }` case (single address). The
 * default Node http/https stack uses that shape.
 */
export declare function pinnedDnsLookup(ip: string): (hostname: string, options: {
    family?: number;
    hints?: number;
    verbatim?: boolean;
}, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void) => void;
/**
 * Validate a redirect target with the same TOCTOU-aware pipeline.
 *
 * `prev` is the URL of the response that returned the 30x; `location`
 * is the raw `Location:` header value (which may be relative). The
 * function resolves `location` against `prev` per RFC 3986 then runs
 * `validateUrlAsync` on the absolute result.
 *
 * Use this on every hop of a redirect chain. Without it, a server
 * that you trust today can redirect tomorrow's request to
 * `http://169.254.169.254/`.
 */
export declare function safeFollowRedirect(prev: string, location: string, options?: ValidateUrlAsyncOptions): Promise<ValidateUrlAsyncResult>;
//# sourceMappingURL=url-async.d.ts.map