GHSA-5mj8-gf6m-fhw8

Suggest an improvement
Source
https://github.com/advisories/GHSA-5mj8-gf6m-fhw8
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-5mj8-gf6m-fhw8/GHSA-5mj8-gf6m-fhw8.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-5mj8-gf6m-fhw8
Aliases
Published
2026-09-22T16:34:06Z
Modified
2026-09-22T17:15:10Z
Severity
  • 7.3 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L CVSS Calculator
Summary
9Router has an Authentication Bypass in Public LLM API via Spoofable X-9r-Real-Ip Header
Details

Summary

9router determines whether an incoming request originates from localhost by trusting the X-9r-Real-Ip HTTP request header. This header is intended to be produced and sanitized exclusively by the bundled custom-server.js layer from the TCP socket address. In deployment modes where requests reach Next.js directly (the header is never stripped/regenerated), a remote, unauthenticated attacker can simply send X-9r-Real-Ip: 127.0.0.1 and be treated as a local client. This bypasses the API-key requirement on the public LLM API (/api/v1/*), granting unauthenticated access to the instance owner's configured provider resources.

Affected Component

  • src/dashboardGuard.js
  • isLocalRequest() — trusts the client-supplied X-9r-Real-Ip header to decide loopback origin
  • canAccessPublicLlmApi() — grants access to /api/v1/* when isLocalRequest() returns true, skipping API-key validation
  • Verified affected route: GET /api/v1/models
  • Product version tested: 9router-app 0.5.4 (Next.js 16.2.9)

Root Cause

The authorization layer makes a security decision based on a client-controllable HTTP header. isLocalRequest() reads X-9r-Real-Ip and, if its value is a loopback address (127.0.0.1), classifies the request as local. The design assumes this header can only be set by the trusted custom-server.js wrapper (which derives it from the unspoofable socket address and strips any inbound copy). When the application is served without that wrapper, Next.js passes the attacker-supplied header through unchanged, so the trust assumption is violated:

Untrusted Client Input
        ↓
X-9r-Real-Ip: 127.0.0.1
        ↓
isLocalRequest()  → true
        ↓
canAccessPublicLlmApi()  → allowed (API key not required)
        ↓
200 OK

Attack Scenario

  1. The instance is deployed in a mode that does not use custom-server.js, and the LLM API is reachable by the attacker (the default bind is 0.0.0.0).
  2. The attacker sends a normal request to /api/v1/models and receives 401 Unauthorized (API key required for remote access).
  3. The attacker re-sends the identical request with the single added header X-9r-Real-Ip: 127.0.0.1.
  4. The request is classified as local, the API-key check is skipped, and the attacker receives 200 OK with the owner's model catalog and ongoing access to the LLM API.

Proof of Concept

Baseline Request

Screenshot 2026-06-19 174727

Exploit Request

Screenshot 2026-06-19 174844

The only difference between the two requests is the addition of X-9r-Real-Ip: 127.0.0.1.

Impact

An unauthenticated remote attacker who can reach the service can bypass API-key enforcement on the public LLM API and act as a trusted local client. Consequences include:

  • Unauthorized use of the owner's configured LLM provider connections
  • Consumption of paid API credits / financial loss to the instance owner
  • Abuse of upstream provider accounts via the proxy
  • Enumeration of configured providers and available models

Remediation

  • Do not trust X-9r-Real-Ip (or any X-9r-* header) when received directly from clients.
  • Derive the client address for authorization from a trusted transport-level source, e.g. req.socket.remoteAddress, rather than a request header.
  • If custom-server.js is required for the security model, fail closed when its trusted marker is absent, and explicitly strip/reject any inbound client-supplied X-9r-* headers at the edge.
  • Document supported, secure startup modes so the application is not run in a configuration where the header is attacker-controllable.
Database specific
{
    "cwe_ids":  [
        "CWE-807"
    ],
    "github_reviewed":  true,
    "github_reviewed_at":  "2026-09-22T16:34:06Z",
    "nvd_published_at":  null,
    "severity":  "HIGH"
}
References

Affected packages

npm / 9router

Package

Affected ranges

Type
SEMVER
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
0.5.8

Database specific

last_known_affected_version_range
"<= 0.5.4"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-5mj8-gf6m-fhw8/GHSA-5mj8-gf6m-fhw8.json"