Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
HIGH severity

CVE-2026-49353 — 9router

HIGHFix: decolua/9router@5e1c126

CVE-2026-49353 is a high-severity (CVSS 7.5) CWE-290 vulnerability in 9router. No vendor fix is recorded yet; mitigation options are listed below.

9Router: Local-Only Access Gate Bypass in 9router via Host Header SpoofING

Also known asGHSA-6g2f-w7g3-77vf
Published
Updated
Affected
1 pkg
Patched
See advisory
Exploits
None indexed
Exploitation data as of Oct 14, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-49353.

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs28th percentile — riskier than 28% of all scored CVEsHighest risk
0.00%0.29%0.57%0.86%0.2%0.4%0.4%0.4%Aug 26Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

CVE-2026-49353 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 388,997 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

1other npm packages depend on this — each one inherits the vulnerability until it's patched upstream
9routernpm
50Kdownloads / week

Description

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:

  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.com → 127.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:
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

Affected Packages

1 total
EcosystemPackageVulnerable rangeFix
📦npm9routerall versionsNo fix

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for 9router, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Remediation status

    No patched version of 9router has shipped for CVE-2026-49353 yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.

  3. Mitigate without a patch

    Assume what was exposed is already known: rotate any credential, token or key that the affected component could return, restrict the endpoint to callers that genuinely need it, and strip sensitive fields from responses and error output at the boundary rather than relying on the client not to read them.

Frequently Asked Questions

## 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
O3 Security · Impact-Aware SCA

Is CVE-2026-49353 in your dependencies?

Find it across npm, including transitive dependencies.

CVE-2026-49353: 9router RCE (High 7.5)