GitHub Advisoryadvisory · npm

GHSA-x5c9-v98j-722r 9router /v1 APIs has unauthenticated access via reverse proxy locality collapse

HIGH Published 2026-09-23 Modified 2026-09-23 Official page ↗
Aliases
CVE-2026-56675
Ecosystem
npm
Packages
9router
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L
Weaknesses
CWE-287, CWE-290, CWE-306, CWE-441
Reviewed
2026-09-23

Affected packages

• npm 9router: < 0.5.2 → fixed in 0.5.2

Description

Summary 9router treats local loopback requests as trusted and allows access to `/v1/*` without an API key. In a documented/common reverse-proxy deployment where nginx forwards public traffic to the backend via `127.0.0.1`, external non-`Origin` requests are misclassified as local. This allows unauthenticated access to `/v1` APIs such as `/v1/models`, and may allow abuse of configured upstream provider credentials depending on the enabled providers. Details • Affected version / commit: 9router `v0.4.80` @ `b282f05`. • Deployment precondition: a same-host reverse proxy (e.g. nginx) forwarding public traffic to the backend on `127.0.0.1` / `localhost`. This mirrors the documented cloud deployment (`proxy_pass http://localhost:20128` with `X-Real-IP` / `X-Forwarded-For`). • Observed behaviour: • The direct backend (`direct-backend`, port `18081`) returns `401` for `/v1/models` without an API key. • A direct request that spoofs `X-9r-Real-IP: 127.0.0.1` still returns `401`: the custom server deletes the client-supplied header and overwrites it with the real socket address, so naive header spoofing does not work against the direct backend. • The proxied path (`reverse-proxy`, port `18080`) returns `200` with the full model catalog for the same `/v1/models` request without any API key. • A proxied request that carries an `Origin` header returns `401`. The bypass therefore primarily affects curl / SDK / server-side / non-browser clients, which do not send `Origin`. • Root cause: the backend's local/remote decision relies on perceived socket/loopback locality after reverse proxying. Because nginx connects to the backend from `127.0.0.1`, the backend stamps a loopback client address for every internet client and treats the request as local, skipping the `/v1` API-key requirement. The forwarded `X-Real-IP` / `X-Forwarded-For` headers that carry the true client IP are ignored for this decision. • This is not a simple client header-spoofing issue (the direct-spoof control above proves header spoofing is rejected); it is a property of how loopback proxy traffic is trusted. Proof of Concept This repository is a self-contained Docker Compose reproduction. No real provider is called and no real API key is required. 1. Build and start the stack: ```bash docker compose up --build ``` 2. Direct baseline (no API key): ```bash curl -i http://127.0.0.1:18081/v1/models ``` 3. Direct spoof control: ```bash curl -i -H "X-9r-Real-IP: 127.0.0.1" http://127.0.0.1:18081/v1/models ``` 4. Reverse-proxy bypass (no API key): ```bash curl -i http://127.0.0.1:18080/v1/models ``` 5. Reverse-proxy `Origin` control: ```bash curl -i -H "Origin: http://evil.example" http://127.0.0.1:18080/v1/models ``` Expected evidence | Request | Result | |---------|--------| | Direct `18081`, no key | `401 Unauthorized` (`{"error":"API key required for remote API access"}`) | | Direct `18081`, `X-9r-Real-IP: 127.0.0.1` spoof | `401 Unauthorized` | | Proxied `18080`, no key | `200 OK` with the full model catalog | | Proxied `18080`, with `Origin` | `401 Unauthorized` | Impact • Unauthenticated access to the `/v1` API surface in the affected reverse-proxy deployment. • Model enumeration via `/v1/models`. • Possible abuse of the operator's configured upstream provider credentials through `/v1/chat/completions` and other `/v1` proxy endpoints (the attacker spends the operator's provider quota/keys without holding any key of their own). • Actual impact depends on which providers are configured and how the instance is exposed to the public internet. • The attacker requires no API key. Suggested Fix • Do not use client/proxy/socket IP locality as an authentication bypass. • Require an API key by default for `/v1/*` on public listeners. • If local trust is genuinely needed, bind it to an unguessable server-generated secret or to a Unix domain socket that is only accessible locally — not to "the connection looks like loopback". • When running behind reverse proxies, use an explicit trusted-proxy configuration and a real client-IP derivation (e.g. a vetted `X-Forwarded-For` chain), and never treat all loopback proxy traffic as end-user-local. • Document a secure reverse-proxy configuration for operators.

Related

Alias · CVE (1)

Weakness · CWE (4)

  • CWE-287 Improper Authentication weakness
  • CWE-290 Authentication Bypass by Spoofing weakness
  • CWE-306 Missing Authentication for Critical Functionality weakness
  • CWE-441 Unintended Proxy or Intermediary weakness

References