Files
OmniRoute/bin/cli/utils/serverHost.mjs
Diego Rodrigues de Sa e Souza 9cb91dee74 fix(security): close round-3 advisories (v7g9, x7vm, j7j4, jcm5) + exposure warning (wmgv) (#11261)
Validated on a 2-PR combined board: routeGuard 36/36 (within the 68/68 focused-file total), a2a-task-owner-idor 7/7, a2a-tasks-auth, search-baseurl-ssrf-guard, cli-serve-hostname, spawn-capable-prefixes-client-safe all green, typecheck:core + dashboard-typecheck clean, gates within baseline. Five real High-severity advisories fixed with TDD (each failing-then-passing): settings export/import-json ALWAYS_PROTECTED completion, MITM route LOCAL_ONLY+SPAWN_CAPABLE gating, search baseUrl SSRF/IMDS guard, A2A REST task auth+ownership (previously none at all), and the loud boot exposure warning. GHSA-cjv9 confirmed already closed on this base (verified). Round 3 of the advisory sweep.
2026-08-23 20:32:42 -03:00

58 lines
2.3 KiB
JavaScript

import { hostname, platform } from "node:os";
/**
* Resolve the bind host passed to the standalone Next.js server.
*
* HOSTNAME is a standard shell variable on Unix-like systems, so only the
* dedicated OmniRoute variable is treated as configuration there. Windows
* keeps the legacy HOSTNAME fallback for compatibility with existing .env
* files, while still ignoring the OS-reported machine name.
*
* @param {NodeJS.ProcessEnv} [env]
* @param {NodeJS.Platform} [runtimePlatform]
* @param {string} [machineHostname]
* @returns {string}
*/
export function resolveServerHost(
env = process.env,
runtimePlatform = platform(),
machineHostname = hostname()
) {
if (env.OMNIROUTE_SERVER_HOST) return env.OMNIROUTE_SERVER_HOST;
if (runtimePlatform === "win32" && env.HOSTNAME && env.HOSTNAME !== machineHostname) {
return env.HOSTNAME;
}
return "0.0.0.0";
}
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
/**
* Boot-time exposure warning (GHSA-wmgv-ph3p-rv57): the shipped default binds
* all interfaces while the inference plane requires no credentials, so any
* LAN peer can spend the operator's quota. That local-first posture is a
* deliberate, documented default — but it must be LOUD at startup so an
* operator who never read the docs still learns the two escape hatches.
*
* Returns the warning text when the server will listen on a non-loopback
* interface with no API-key requirement, or null when the exposure is closed.
*
* @param {NodeJS.ProcessEnv} [env]
* @param {string} [host]
* @returns {string | null}
*/
export function resolveExposureWarning(env = process.env, host = resolveServerHost(env)) {
if (LOOPBACK_HOSTS.has(host)) return null;
const requireKey = String(env.REQUIRE_API_KEY || "")
.trim()
.toLowerCase();
if (requireKey === "true" || requireKey === "1" || requireKey === "yes") return null;
return (
`SECURITY: listening on ${host} with NO API-key requirement — the inference ` +
`plane (/v1/*) is reachable by ANY device that can route to this host, and ` +
`requests are billed to your configured providers. This local-first default ` +
`is intentional, but on an untrusted network either set REQUIRE_API_KEY=true ` +
`or bind loopback with OMNIROUTE_SERVER_HOST=127.0.0.1.`
);
}