9router CVE-2026-49353
HIGHSeverity by source
AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N
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.
Primary rating from Vendor (https://github.com/decolua/9router).
CVSS VectorVendor: https://github.com/decolua/9router
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N
Lifecycle Timeline
1DescriptionCVE.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:
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:
- Attacker sends request to
https://<tunnel-domain>/api/mcp/<plugin>/sse - Sets
Host: localhost:3000andOrigin: http://localhost:3000 isLocalRequest()returnstruecanAccessLocalOnlyRoute()then requires CLI token or (local + JWT)- CLI token is
getConsistentMachineId("9r-cli-auth")- a deterministic HMAC of the machine's hardware/OS identifiers
Scenario 2: DNS Rebinding
- Attacker controls
evil.comDNS, initially resolving to attacker IP - Victim's browser navigates to
evil.com(or via iframe/redirect) - DNS rebinding switches
evil.com→127.0.0.1 - Subsequent fetch to
evil.com:3000/api/mcp/<plugin>/messagereaches 9router Hostheader isevil.com:3000- this is blocked by the current check (not in LOOPBACK_HOSTS)- However, if the attacker uses
localhost:3000as the request host via CORS or service worker tricks, and the browser sendsHost: localhost:3000, the gate opens
Exploitation (when CLI token is obtained)
Once past the gate, the attacker can:
GET /api/mcp/<plugin>/sse- establish SSE session, getsessionIdPOST /api/mcp/<plugin>/message- send arbitrary JSON-RPC to the child process stdin- The child process is one of:
npx,node,python,python3,uvx,bunx,bun - Depending on the MCP plugin implementation, this can achieve arbitrary code execution on the host
Steps to Reproduce
- Deploy 9router behind a reverse proxy or tunnel
- From a remote host, send:
GET /api/mcp/browser/sse HTTP/1.1
Host: localhost:3000
Origin: http://localhost:3000
x-9r-cli-token: <machine-id-derived-token>- Observe: SSE connection established,
endpointevent received with message URL - 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
- Check actual source IP, not headers. Use
request.ip,request.socket.remoteAddress, or a trustedX-Forwarded-Forheader with known proxy configuration instead ofHost/Originfor the local-only gate. - 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.
- 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.1only, 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. …
Unlock full vulnerability intelligence
- Risk assessment & exploitation conditions
- Attack chain visualization
- Remediation with exact patch versions
- Threat intelligence from 22 sources
- Personal watchlist & email alerts
Free forever · No credit card required
Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata
Vulnerability AssessmentAI
| Exploitation | Exploitation requires that 9router be deployed behind a reverse proxy, Cloudflare Tunnel, or Tailscale (i.e., reachable beyond loopback) - the default localhost-only deployment is NOT vulnerable to remote abuse. … Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | The vendor CVSS 3.1 score of 7.5 (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N) captures the tension well: the vector is network-reachable with no privileges, but AC:H reflects two real hurdles that materially limit exploitation - the instance must be deployed behind a reverse proxy or tunnel (not the default localhost-only posture), and the attacker must obtain or guess the CLI token. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in. |
| Exploit Scenario | An operator exposes 9router through a Cloudflare Tunnel for remote access. A remote attacker who has separately learned or guessed the target's machine ID sends GET https://<tunnel-domain>/api/mcp/browser/sse with forged headers Host: localhost:3000 and Origin: http://localhost:3000 plus the derived x-9r-cli-token, causing isLocalRequest() to return true and passing the second-factor check. … |
| Remediation | 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. … Detailed patch versions, workarounds, and compensating controls in full report. |
Recommended ActionAI
Within 24 hours: Identify all production 9router deployments and classify by network configuration (direct, proxied, or tunneled). …
Sign in for detailed remediation steps and compensating controls.
Threat intelligence, references, and detailed analysis are available after sign-in.
Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t
BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser
pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi
The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica
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
pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.
In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne
Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301
Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing
Same weakness CWE-290 – Authentication Bypass by Spoofing
View allSame technique Authentication Bypass
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-6g2f-w7g3-77vf