/** * Shared request guard for OpenLore's local HTTP surfaces (the `serve` daemon and * the `view` graph server). * * OpenLore binds more than one local HTTP listener; both must present the same * door to a browser. This module is the single, dependency-light home for the * security-critical primitives so the two surfaces cannot drift: * * - a Host-header allowlist restricted to loopback forms (DNS-rebinding guard), * - an Origin check rejecting foreign browser origins, * - a constant-time token comparison, * * plus {@link checkLocalHttpRequest}, the composed policy a surface applies per * request: reject a cross-origin/rebinding request (403), then require the * `x-openlore-token` header when the binding is non-loopback OR the route is a * money/agent endpoint (401). See the `mcp-security` spec requirement * `AllLocalHttpSurfacesShareTheGuard`. */ import type { IncomingMessage, ServerResponse } from 'node:http'; /** Header a client presents to authenticate to a local OpenLore HTTP surface. */ export declare const OPENLORE_TOKEN_HEADER = "x-openlore-token"; /** Hostnames that denote the loopback interface (no DNS resolution involved). */ export declare const LOOPBACK_HOSTNAMES: Set; /** True if `host` is a loopback literal/name (127.0.0.0/8, ::1, localhost). */ export declare function isLoopbackHost(host: string): boolean; /** Extract the hostname (sans port, sans brackets) from a Host/Origin authority. */ export declare function hostnameOf(authority: string): string; /** * Constant-time string equality. Returns false for length mismatch, but still * runs a same-length compare first so timing does not leak the secret's length. */ export declare function constantTimeEqual(a: string, b: string): boolean; /** * DNS-rebinding / cross-origin defense for a loopback listener. A browser tricked * into resolving an attacker domain to 127.0.0.1 still sends the attacker's name in * the `Host` header (and an attacker page sends a cross-site `Origin`). We accept a * request only when both the Host and any Origin name the loopback interface or the * exact bound host. Returns an error string to reject with, or null to allow. */ export declare function originDefenseError(req: IncomingMessage, boundHost: string): string | null; /** Per-request guard configuration. */ export interface LocalHttpGuardConfig { /** The host the server is bound to (from the surface's --host). */ boundHost: string; /** The instance token, if one is configured. */ token?: string; /** * Force the token even on a loopback binding — for money/agent endpoints * (e.g. the viewer's chat route) that must not be driven by another local * process or a header-less rebinding page. On a non-loopback binding the token * is always required regardless of this flag. */ requireToken?: boolean; /** Header carrying the token. Defaults to {@link OPENLORE_TOKEN_HEADER}. */ tokenHeader?: string; } /** A rejection to send. `null` from {@link checkLocalHttpRequest} means "allow". */ export interface LocalHttpGuardRejection { status: number; error: string; } /** * Apply the shared local-HTTP guard to a request. Returns a rejection to send * (403 for a rebinding/cross-origin request, 401 for a missing/invalid token) or * `null` to allow the request through. * * Token policy: a token is required when a token is configured AND either the * binding is non-loopback (anyone on the network can reach the port) or the * caller marked the route `requireToken` (a money/agent endpoint). */ export declare function checkLocalHttpRequest(req: IncomingMessage, cfg: LocalHttpGuardConfig): LocalHttpGuardRejection | null; /** Minimal connect-style middleware signature (a subset of what vite/connect pass). */ export type ConnectMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void; /** * Build a connect/vite middleware that enforces {@link checkLocalHttpRequest} on * every request that reaches it. Mount it at the `/api` prefix BEFORE any * `/api/*` route so no route can be reached without passing the guard. * * `requireTokenFor(pathname)` receives the request pathname RELATIVE to the mount * point (e.g. `/chat` for a request to `/api/chat`) and returns true for routes * that must present the token even on a loopback binding. */ export declare function createApiGuardMiddleware(opts: { boundHost: string; token?: string; requireTokenFor?: (relativePathname: string) => boolean; tokenHeader?: string; }): ConnectMiddleware; //# sourceMappingURL=local-http-guard.d.ts.map