A AegiFlow
HIGHCVSS 8.2EPSS 0.3%

CVE-2026-55641

9router: Unauthenticated `/v1` proxy access via `Host`-header spoofing → open AI relay + SSRF

Published
2026-08-28
Modified
2026-08-28
EPSS percentile
25%
Aliases
GHSA-86m2-fcxq-5q7c
Sources
github-advisory

Summary

## Summary 9router's request guard decides a request is "local" (and therefore exempt from API-key auth on the `/v1` LLM proxy) by reading the **client-controlled `Host` header**. Because 9router binds `0.0.0.0` by default (and the CLI misleadingly prints "localhost"), a remote, unauthenticated attacker who can reach the port can send `Host: localhost` to be treated as local and obtain `/v1` proxy access with **no API key, no CLI token, and no dashboard login**. In the default configuration (`requireApiKey` is absent from `DEFAULT_SETTINGS`, so the handler-side key check is skipped), this yields: - **Open AI relay** — the proxy forwards the attacker's requests to AI providers using the **victim's stored paid API keys** (cost/quota theft, prompt-based data exfiltration through the victim's accounts). - **Unauthenticated SSRF** — `/v1/search` with the built-in `noAuth` `searxng` provider takes its outbound fetch URL from the request body (`provider_options.baseUrl`), so the attacker drives a server-side fetch to any internal/cloud-metadata host and gets the JSON response reflected back. - **Affected:** `9router no key required if (await hasValidCliToken(request)) return true; return await hasValidApiKey(request); } ``` `isLocalRequest` never consults the **socket peer address** — only the spoofable `Host` header (and an absent/loopback `Origin`). The `/v1`,`/v1beta`,`/api/v1`,`/api/v1beta` prefixes are gated solely by `canAccessPublicLlmApi`. ### Default exposure - `cli/cli.js:63` `const DEFAULT_HOST = "0.0.0.0";` and `Dockerfile` `ENV HOSTNAME=0.0.0.0` / `EXPOSE 20128` → reachable from the network by default. - `cli/cli.js:500,541` display `"localhost"` even when bound to `0.0.0.0` — operators believe it's local-only. - `src/lib/db/repos/settingsRepo.js` `DEFAULT_SETTINGS` has **no `requireApiKey`** → the handler key checks (`chat.js` `if (settings.requireApiKey)`, `search.js` same) are skipped by default. ### Relay chain (verbatim trace, 0.4.71) middleware (`src/proxy.js`, matcher covers all paths) → `canAccessPublicLlmApi` true via spoofed Host → `next.config.mjs` rewrites `/v1/:path*`→`/api/v1/:path*` → `src/app/api/v1/messages/route.js` POST → `handleChat` (no independent auth) → only gate falsy `requireApiKey` → `getProviderCredentials()` loads the victim's stored credentials → `handleChatCore` outbound fetch → response returned. **No downstream key gate.** ### SSRF chain `search.js` (only gate falsy `requireApiKey`) → `searxng` `noAuth:true` ⇒ `handleSearchCore({credentials:null})` → `coreBody.provider_options = body.provider_options` → `callers.js`: ```js export function resolveBaseUrl(config, params){ const override = getProviderSetting(params, "baseUrl"); // reads params.providerOptions.baseUrl FIRST return (override || config.baseUrl).replace(/\/+$/, ""); } ``` → `buildSearxngRequest` appends `/search?q=...&format=json&categories=general` → `fetch(url)` (server-side) → JSON reflected to caller. ## PoC Ground-truth, no network egress: `harness/hostspoof.mjs` (verbatim gua

Affected packages

EcosystemPackageAffected versionsFixed versions
npm9router0.5.2

Remediation: Upgrade to 0.5.2 or later.

References

Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.

CVE® is a registered trademark of The MITRE Corporation. CVE content reproduced under the CVE Terms of Use; copyright designation © MITRE.

EPSS scores provided by the FIRST.org Exploit Prediction Scoring System.