// Generated by scripts/sync-shared.mjs from shared/host/loopback.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs". /** * Loopback trust fence shared by the host route families: socket address, * Host header, and browser same-origin markers. Packages receive this file as * a generated copy via scripts/sync-shared.mjs; edit the shared source and * re-run the sync instead of editing a copy. * * Semantics: RFC 5735 IPv4 127/8, ::1, IPv4-mapped ::ffff:127/8 (matching the * remote-web-ui gate), localhost hostnames, plus the browser same-origin * markers (sec-fetch-site and Origin) for the request-level fence. * @module dsh-web-shared/host/loopback */ import type { IncomingMessage } from 'node:http' /** IPv4 127/8 predicate (four decimal octets, first == 127). */ export function isIPv4Loopback(v4: string): boolean { const parts = v4.split('.') return parts.length === 4 && parts[0] === '127' && parts.every(part => /^\d{1,3}$/.test(part) && Number(part) <= 255) } /** Whether a socket remote address names the loopback range (127/8, ::1, IPv4-mapped). */ export function isLoopbackAddress(address: string | undefined): boolean { if (address === undefined) return false const normalized = address.toLowerCase() if (normalized === '::1') return true if (normalized.startsWith('::ffff:')) return isIPv4Loopback(normalized.slice('::ffff:'.length)) return isIPv4Loopback(normalized) } /** Whether a normalized URL hostname names the loopback authority (localhost, [::1], 127/8). */ export function isLoopbackHostname(hostname: string): boolean { if (hostname === 'localhost' || hostname === '[::1]') return true return isIPv4Loopback(hostname) } /** * Request-level trust fence: a loopback socket address AND a loopback Host * header, plus browser same-origin markers. The socket address is * authoritative; X-Forwarded-For is never trusted. */ export function isLoopbackRequest(request: IncomingMessage): boolean { if (!isLoopbackAddress(request.socket.remoteAddress)) return false const host = request.headers.host if (typeof host !== 'string') return false let hostUrl: URL try { hostUrl = new URL('http://' + host) } catch { return false } if (!isLoopbackHostname(hostUrl.hostname)) return false if (request.headers['sec-fetch-site'] === 'cross-site') return false const origin = request.headers.origin if (origin === undefined) return true try { return new URL(origin).host === hostUrl.host } catch { return false } }