Skip to main content

9router CVE-2026-49353

HIGH
Authentication Bypass by Spoofing (CWE-290)
2026-07-02 https://github.com/decolua/9router GHSA-6g2f-w7g3-77vf
7.5
CVSS 3.1 · Vendor: https://github.com/decolua/9router
Share

Severity by source

Vendor (https://github.com/decolua/9router) PRIMARY
7.5 HIGH
AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N
vuln.today AI
9.0 CRITICAL

Network-reachable but AC:H for the required tunnel/proxy exposure plus deterministic-token guessing; PR:N since no legitimate account is needed; S:C and C/I/A:H reflect code execution escaping into a spawned child process.

3.1 AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H
4.0 AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

Primary rating from Vendor (https://github.com/decolua/9router).

CVSS VectorVendor: https://github.com/decolua/9router

Attack Vector
Network
Attack Complexity
High
Privileges Required
None
User Interaction
None
Scope
Changed
Confidentiality
Low
Integrity
High
Availability
None

Lifecycle Timeline

1
Analysis Generated
Jul 02, 2026 - 21:51 vuln.today

DescriptionCVE.org

Summary

The fix for CVE-2026-46339 (unauthenticated RCE via unprotected MCP plugin routes) introduced a local-only access gate in src/dashboardGuard.js that restricts spawn-capable routes (/api/mcp/*, /api/tunnel/*, /api/cli-tools/*) to loopback requests. The gate determines "local" by inspecting the Host and Origin HTTP headers rather than the TCP source address. When 9router is deployed behind a reverse proxy, tunnel (Cloudflare Tunnel, Tailscale - both natively supported), or is subject to DNS rebinding, these headers are attacker-controlled, allowing the local-only gate to be bypassed.

A second factor (CLI token or JWT cookie) is required by canAccessLocalOnlyRoute(), but the CLI token is a deterministic HMAC of the machine ID (getConsistentMachineId), which is stable and predictable on cloud VMs. If the attacker can obtain or guess the machine ID (e.g., via another information disclosure, or on shared-tenant infrastructure), the full chain to MCP child process stdin injection is reachable.

This is a variant / incomplete fix of CVE-2026-46339 - the same attack surface (remote → MCP child process stdin) remains reachable under specific but realistic deployment configurations.

Root Cause

isLocalRequest() at src/dashboardGuard.js:93-101:

javascript
function isLocalRequest(request) {
  if (!isLoopbackHostname(request.headers.get("host"))) return false;
  const origin = request.headers.get("origin");
  if (origin) {
    try {
      if (!isLoopbackHostname(new URL(origin).hostname)) return false;
    } catch { return false; }
  }
  return true;
}

This function trusts Host and Origin headers as proof of local origin. Both are attacker-controlled in any proxied deployment. The LOOPBACK_HOSTS set (localhost, 127.0.0.1, ::1) is checked against these headers, not against the actual connection source IP.

Attack Scenario

Scenario 1: Cloudflare Tunnel / Tailscale Funnel

9router natively supports Cloudflare Tunnel and Tailscale (see LOCAL_ONLY_PATHS entries for /api/tunnel/*). When exposed via tunnel:

  1. Attacker sends request to https://<tunnel-domain>/api/mcp/<plugin>/sse
  2. Sets Host: localhost:3000 and Origin: http://localhost:3000
  3. isLocalRequest() returns true
  4. canAccessLocalOnlyRoute() then requires CLI token or (local + JWT)
  5. CLI token is getConsistentMachineId("9r-cli-auth") - a deterministic HMAC of the machine's hardware/OS identifiers

Scenario 2: DNS Rebinding

  1. Attacker controls evil.com DNS, initially resolving to attacker IP
  2. Victim's browser navigates to evil.com (or via iframe/redirect)
  3. DNS rebinding switches evil.com127.0.0.1
  4. Subsequent fetch to evil.com:3000/api/mcp/<plugin>/message reaches 9router
  5. Host header is evil.com:3000 - this is blocked by the current check (not in LOOPBACK_HOSTS)
  6. However, if the attacker uses localhost:3000 as the request host via CORS or service worker tricks, and the browser sends Host: localhost:3000, the gate opens

Exploitation (when CLI token is obtained)

Once past the gate, the attacker can:

  1. GET /api/mcp/<plugin>/sse - establish SSE session, get sessionId
  2. POST /api/mcp/<plugin>/message - send arbitrary JSON-RPC to the child process stdin
  3. The child process is one of: npx, node, python, python3, uvx, bunx, bun
  4. Depending on the MCP plugin implementation, this can achieve arbitrary code execution on the host

Steps to Reproduce

  1. Deploy 9router behind a reverse proxy or tunnel
  2. From a remote host, send:
http
GET /api/mcp/browser/sse HTTP/1.1
Host: localhost:3000
Origin: http://localhost:3000
x-9r-cli-token: <machine-id-derived-token>
  1. Observe: SSE connection established, endpoint event received with message URL
  2. POST arbitrary JSON-RPC to the message endpoint

Impact

An attacker who can reach a proxied/tunneled 9router instance and obtain the deterministic CLI token can bypass the local-only restriction and interact with MCP child processes (node, python, npx, etc.) via stdin. This achieves the same impact as CVE-2026-46339: remote code execution on the host.

The severity is reduced from CVE-2026-46339's CVSS 10.0 because:

  • Requires proxied/tunneled deployment (not default localhost-only)
  • Requires obtaining the CLI token (deterministic but not trivially guessable without another primitive)

Remediation

  1. Check actual source IP, not headers. Use request.ip, request.socket.remoteAddress, or a trusted X-Forwarded-For header with known proxy configuration instead of Host/Origin for the local-only gate.
  2. Make CLI token non-deterministic. Generate a random token on first run and persist it, rather than deriving from machine ID. Machine IDs are often predictable or discoverable on cloud infrastructure.
  3. Bind MCP routes to loopback at the network layer. If MCP is local-only by design, the server should bind those routes to 127.0.0.1 only, not rely on middleware header checks.

Credit: @snailsploit

AnalysisAI

Authentication-bypass leading to remote code execution in 9router (npm package 9router) lets attackers reach spawn-capable MCP routes that were meant to be loopback-only. This is an incomplete fix for CVE-2026-46339: the local-only gate in src/dashboardGuard.js decides 'local' from attacker-controllable Host and Origin headers instead of the TCP source address, so any proxied or tunneled (Cloudflare Tunnel / Tailscale) deployment can be tricked into treating remote requests as local. Combined with the deterministic, machine-ID-derived CLI token, a remote attacker can inject JSON-RPC into MCP child processes (node, python, npx, etc.) and execute code on the host; no public exploit identified at time of analysis, though detailed reproduction steps are published in the vendor advisory.

Technical ContextAI

9router is a Node.js router/dashboard that proxies AI tooling and natively supports exposing itself via reverse proxies and tunnels (Cloudflare Tunnel, Tailscale) through /api/tunnel/* routes. It brokers Model Context Protocol (MCP) plugins by spawning child interpreters (npx, node, python, python3, uvx, bunx, bun) and forwarding JSON-RPC to their stdin over an SSE session established at /api/mcp/<plugin>/sse and driven via /api/mcp/<plugin>/message. To contain the earlier unauthenticated RCE, a middleware gate (isLocalRequest() at src/dashboardGuard.js:93-101) restricts these spawn-capable paths to loopback. The root cause maps to CWE-290 (Authentication Bypass by Spoofing): the gate compares the Host and Origin HTTP headers against a LOOPBACK_HOSTS set (localhost, 127.0.0.1, ::1) rather than inspecting request.socket.remoteAddress or a trusted X-Forwarded-For, so client-supplied headers become the sole proof of locality. The affected package is identified by CPE pkg:npm/9router.

RemediationAI

Upstream fix available (PR/commit); released patched version not independently confirmed - apply the fixes in commits 5e1c1261368e06dced1cbc650684561b2c8844db and bb86808582067e4fc6f004508a919efb9970d1d5 referenced by advisory GHSA-6g2f-w7g3-77vf, or update to the vendor release incorporating them once identified. The durable fix is to gate local-only routes on the actual connection source (request.socket.remoteAddress, or a trusted X-Forwarded-For only behind a known proxy) instead of the Host and Origin headers, and to bind the MCP/tunnel/cli-tools routes to 127.0.0.1 at the network layer so middleware header checks are not the only barrier. As a compensating control until patched, do not expose 9router through Cloudflare Tunnel, Tailscale Funnel, or a reverse proxy - keep it loopback-only - since exposure is the primary precondition; if a tunnel is operationally required, terminate it behind an authenticating proxy that strips client-supplied Host/Origin and enforces its own auth in front of /api/mcp/*, /api/tunnel/*, and /api/cli-tools/* (trade-off: adds proxy complexity and can break tunnel-native features). Additionally, replace the deterministic machine-ID-derived CLI token with a random per-install token persisted on first run, because the current token is predictable on cloud/shared infrastructure (trade-off: requires re-provisioning existing CLI clients).

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-49869 CRITICAL POC
10.0 Jun 26

Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

Share

CVE-2026-49353 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy