CVE-2026-49353
9Router: Local-Only Access Gate Bypass in 9router via Host Header SpoofING
9Router is an AI router & token saver. In 0.4.45 and earlier, 9Router's src/dashboardGuard.js local-only access gate used Host and Origin headers in isLocalRequest() to protect /api/mcp/, /api/tunnel/, and /api/cli-tools/*, allowing header spoofing in reverse proxy or tunnel deployments to reach MCP child process stdin paths.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- Low
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
7.5
9Router: Local-Only Access Gate Bypass in 9router via Host Header SpoofING
9Router is an AI router & token saver. In 0.4.45 and earlier, 9Router's src/dashboardGuard.js local-only access gate used Host and Origin headers in isLocalRequest() to protect /api/mcp/, /api/tunnel/, and /api/cli-tools/*, allowing header spoofing in reverse proxy or tunnel deployments to reach MCP child process stdin paths.
- Record
CVE-2026-49353- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N - Weakness
- CWE-290
- Updated
- 2026-11-02
EUVDEUVD-2026-44811
7.5
9Router is an AI router & token saver. In 0.4.45 and earlier, 9Router's src/dashboardGuard.js local-only access gate used Host and Origin headers in isLocalRequest() to protect /api/mcp/, /api/tunnel/, and /api/cli-tools/*, allowing header spoofing in reverse proxy or tunnel deployments to reach MCP child process stdin paths.
- Record
EUVD-2026-44811- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-6g2f-w7g3-77vf
7.5
9router has an Incomplete Fix: Local-Only Access Gate Bypass in 9router via Host Header SpoofING
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…
- Record
GHSA-6g2f-w7g3-77vf- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N - Weakness
- CWE-290
- Updated
- 2026-11-02
NVD
7.5
9Router is an AI router & token saver. In 0.4.45 and earlier, 9Router's src/dashboardGuard.js local-only access gate used Host and Origin headers in isLocalRequest() to protect /api/mcp/, /api/tunnel/, and /api/cli-tools/*, allowing header spoofing in reverse proxy or tunnel deployments to reach MCP child process stdin paths.
- Record
CVE-2026-49353- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N - Weakness
- CWE-290
- Updated
- 2026-11-02
OSV
7.5
9Router: Local-Only Access Gate Bypass in 9router via Host Header SpoofING
9Router is an AI router & token saver. In 0.4.45 and earlier, 9Router's src/dashboardGuard.js local-only access gate used Host and Origin headers in isLocalRequest() to protect /api/mcp/, /api/tunnel/, and /api/cli-tools/*, allowing header spoofing in reverse proxy or tunnel deployments to reach MCP child process stdin paths.
- Record
CVE-2026-49353- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N - Weakness
- CWE-290
- Updated
- 2026-11-02
OSVGHSA-6g2f-w7g3-77vf
7.5
9router has an Incomplete Fix: Local-Only Access Gate Bypass in 9router via Host Header SpoofING
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…
- Record
GHSA-6g2f-w7g3-77vf- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:H/A:N - Weakness
- CWE-290
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
9routernpm |
through 0.4.55 | None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| aliases | EUVD | |
| first indexed | CVE List, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-49353 is a high vulnerability (CVSS 7.5) in Decolua 9router, published July 2, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.34%, the 27th percentile. No source names a fixed version.
References
github.com/decolua/9router/security/advisories/GHSA-6g2f-w7g3-77vf
github.com/decolua/9router/commit/5e1c1261368e06dced1cbc650684561b2c8844db
github.com/decolua/9router/commit/bb86808582067e4fc6f004508a919efb9970d1d5
github.com/decolua/9router/releases/tag/v0.4.46
github.com/advisories/GHSA-6g2f-w7g3-77vf
github.com/CVEProject/cvelistV5/tree/main/cves/2026/49xxx/CVE-2026-49353.json
nvd.nist.gov/vuln/detail/CVE-2026-49353
github.com/decolua/9router
Left out here: the rest of 2 source descriptions. The JSON record has them.