Skip to content

CVE-2026-59800: Verified Reproduction

CVE-2026-59800: 9router before 0.4.44 allows unauthenticated remote OS command execution via the /api/tunnel/tailscale-install endpoint by injecting shell commands in the sudoPassword field when sudo does not prompt for a password.

CVE-2026-59800 is verified against 9router (npm) · npm. Affected versions: < 0.4.44 (advisory states <= v0.4.39). Fixed in 0.4.44 (GitHub advisory page); OSV lists 0.4.45 as fixed range. Vulnerability class: Command Injection. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00270.

REPRO-2026-00270 9router (npm) · npm Command Injection Jul 8, 2026 CVE entry .txt
Severity
CRITICAL
CVSS
9.2
Confidence
MEDIUM
Reproduced in
45m 53s
Tool calls
382
Spend
$12.03
01 · Overview

What Is CVE-2026-59800?

CVE-2026-59800 is a critical unauthenticated OS command injection (CWE-78) in the npm package 9router, reachable through the /api/tunnel/tailscale-install endpoint. Pruva reproduced it (reproduction REPRO-2026-00270).

02 · Severity & CVSS

CVE-2026-59800 Severity & CVSS Score

CVE-2026-59800 is rated critical severity, with a CVSS base score of 9.2 out of 10.

CRITICAL threat level
9.2 / 10 CVSS base
Weakness CWE-78 — Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.

03 · Affected Versions

Affected 9router (npm) Versions

9router (npm) · npm versions < 0.4.44 (advisory states <= v0.4.39) are affected.

How to Reproduce CVE-2026-59800

$ pruva-verify REPRO-2026-00270
or curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00270/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-59800

Remote code execution — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • medium confidence
  • the upstream fix blocks the same trigger
Trigger

unauthenticated JSON POST body field sudoPassword containing shell commands

Attack chain
  1. POST /api/tunnel/tailscale-install
  2. install helper
  3. spawn('sudo', ['-S','sh'])
  4. sudoPassword reaches sh stdin when sudo does not prompt
Runnable proof: reproduction_steps.sh
Captured evidence: fixed 1 server
How the agent worked 837 events · 382 tool calls · 46 min
46 minDuration
382Tool calls
195Reasoning steps
837Events
Agent activity over 46 min
Support
17
Hypothesis
2
Repro
587
Judge
105
Variant
120
0:0045:53

Root Cause and Exploit Chain for CVE-2026-59800

Versions: < 0.4.44; reproduced against 9router@0.4.39.Fixed: version used for negative control: 9router@0.4.45 (patched behavior: unauthenticated request receives HTTP 401 and the install helper no longer feeds password bytes as shell script input).

9router versions before 0.4.44 expose unauthenticated command execution through POST /api/tunnel/tailscale-install. The endpoint is not covered by the vulnerable dashboard authorization middleware matcher, so a remote client can invoke it without credentials. The handler passes the request-body sudoPassword field to stdin of sudo -S sh before writing the intended Tailscale install script. When sudo does not consume stdin for a password prompt (root process, NOPASSWD sudo, or cached sudo timestamp), sh reads the attacker-controlled sudoPassword bytes as shell commands. This run demonstrates the command boundary through the real 9router HTTP service and records marker files created by the attacker-supplied command, plus a fixed-version negative control.

  • Package / component: npm package 9router; Next.js standalone server route /api/tunnel/tailscale-install; Linux Tailscale install helper.
  • Affected versions: < 0.4.44; reproduced against 9router@0.4.39.
  • Fixed version used for negative control: 9router@0.4.45 (patched behavior: unauthenticated request receives HTTP 401 and the install helper no longer feeds password bytes as shell script input).
  • Risk level and consequences: Critical. Under the documented no-prompt sudo precondition, an unauthenticated HTTP request can execute attacker-selected shell commands as the 9router process user. In root/container deployments this is root command execution.

Impact Parity

  • Disclosed / claimed maximum impact: unauthenticated remote OS command execution (code_execution).
  • Reproduced impact from this run: attacker-controlled commands were executed through the real unauthenticated POST /api/tunnel/tailscale-install endpoint on the vulnerable build. The reproduction script ran the actual 9router npm package and actual Next.js server, passed /api/health, sent JSON containing sudoPassword, reached the vulnerable spawn("sudo", ["-S", "sh"]) path, and wrote marker artifacts containing PWNED_BY_9ROUTER_* plus id output. Two vulnerable attempts succeeded. Two fixed attempts returned HTTP 401 and produced no marker.
  • Parity: full for the demonstrated command-execution boundary under the no-prompt sudo precondition. The current sandbox has no Docker daemon and no real root/NOPASSWD sudo, so the script used a PATH-local no-prompt sudo precondition shim for the successful marker run; if Docker or real root/NOPASSWD is available, the same script first attempts those stronger production-like modes.
  • Not demonstrated: actual uid=0 output in this sandbox, because the sandbox user is unprivileged (vscode, uid 1000), Docker daemon access is denied, and real sudo requires a password. The marker nevertheless proves attacker-controlled command execution through the vulnerable product route when sudo does not consume stdin.

Root Cause

Two defects combine:

  1. Authorization matcher omission. In 9router@0.4.39, the middleware matcher is an explicit allow-list:

    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*"]
    

    /api/tunnel/tailscale-install is absent, so the dashboard guard is not invoked and the route accepts unauthenticated requests.

  2. Shell stdin injection. The vulnerable install helper spawns sudo -S sh, writes sudoPassword to stdin first, then writes the legitimate install script. If sudo does not prompt, stdin remains available to sh; therefore a value such as echo PWNED > /path/marker; id >> /path/marker; exit 0 is interpreted as the first shell command.

The fixed build uses a catch-all middleware matcher that protects /api/tunnel/*, validates the sudo password, writes the install script to a temporary file, and invokes sh <tempfile> so password bytes are no longer interpreted as shell script input.

Reproduction Steps

  1. Run bundle/repro/reproduction_steps.sh.
  2. The script:
    • fetches 9router@0.4.39 and 9router@0.4.45;
    • starts the real 9router server with node cli.js --log --skip-update;
    • waits for /api/health;
    • sends an unauthenticated POST /api/tunnel/tailscale-install request with JSON body containing an attacker command in sudoPassword;
    • records HTTP status, SSE response body, server log, sudo/no-prompt log, and marker files;
    • repeats two vulnerable attempts and two fixed attempts.
  3. Expected evidence:
    • Vulnerable attempts: HTTP 200, response includes Running install script..., sudo/no-prompt log shows invocation as sudo -S sh, and marker files contain PWNED_BY_9ROUTER_* plus uid=... output.
    • Fixed attempts: HTTP 401 {"error":"Unauthorized"} and no marker files.

Evidence

The script was run twice consecutively and succeeded both times. Key artifacts from the final run:

Artifact Evidence
bundle/logs/reproduction_steps.log Complete successful transcript; final result confirmed.
bundle/repro/runtime_manifest.json service_started=true, healthcheck_passed=true, target_path_reached=true; lists all proof artifacts.
bundle/artifacts/vuln_1_http_code.txt, bundle/artifacts/vuln_2_http_code.txt 200 for vulnerable unauthenticated POSTs.
bundle/logs/vuln_1_response.txt, bundle/logs/vuln_2_response.txt SSE progress includes Running install script..., proving route handler reached the install/spawn path.
bundle/logs/vuln_1_sudo_shim.log, bundle/logs/vuln_2_sudo_shim.log No-prompt sudo precondition invoked as argv=-S sh and did not consume stdin.
bundle/artifacts/vuln_1_marker.txt, bundle/artifacts/vuln_2_marker.txt Attacker command markers, e.g. PWNED_BY_9ROUTER_vuln_1 and uid=1000(vscode) ....
bundle/artifacts/fixed_1_http_code.txt, bundle/artifacts/fixed_2_http_code.txt 401 for fixed unauthenticated POSTs.
bundle/logs/fixed_1_response.txt, bundle/logs/fixed_2_response.txt {"error":"Unauthorized"}.
bundle/artifacts/fixed_1_marker.txt, bundle/artifacts/fixed_2_marker.txt [marker absent], proving the fixed build did not execute the command.
bundle/artifacts/vuln_middleware_matcher.txt, bundle/artifacts/fixed_middleware_matcher.txt Vulnerable matcher omission vs fixed catch-all matcher.
bundle/artifacts/vuln_install_code.txt, bundle/artifacts/fixed_install_code.txt Vulnerable stdin-to-sh implementation vs fixed temp-file/validation implementation.

Key excerpt from bundle/logs/reproduction_steps.log:

[vuln_1] HTTP 200; marker: PWNED_BY_9ROUTER_vuln_1 uid=1000(vscode) gid=1000(vscode) groups=1000(vscode)
[vuln_2] HTTP 200; marker: PWNED_BY_9ROUTER_vuln_2 uid=1000(vscode) gid=1000(vscode) groups=1000(vscode)
[fixed_1] HTTP 401; marker: [marker absent]
[fixed_2] HTTP 401; marker: [marker absent]
RESULT: CONFIRMED - attacker-controlled command executed through unauthenticated POST /api/tunnel/tailscale-install; fixed build blocked the same request.

Recommendations / Next Steps

  • Upgrade 9router to >= 0.4.44 (or the latest patched release).
  • Protect routes with a deny-by-default or catch-all authorization middleware matcher so newly added API endpoints cannot bypass authentication by omission.
  • Never feed attacker-influenced data into a shell that reads its script from stdin. Pass scripts by immutable file path or avoid shell interpreters entirely.
  • Validate privileged-operation inputs, including rejecting newlines/metacharacters in password fields.
  • Add integration tests that enumerate /api/* routes and assert unauthenticated requests receive 401/403 unless deliberately public.

Additional Notes

  • The reproduction script is idempotent and was verified twice consecutively.
  • The script attempts Docker-root and direct root/NOPASSWD modes before the controlled no-prompt sudo mode. Those stronger modes were unavailable in this sandbox (docker socket permission denied; no passwordless real sudo), but remain implemented for environments that can run the ticket's root/NOPASSWD precondition directly.
  • The successful run still uses the real product HTTP boundary and real vulnerable route; only the sudo precondition was supplied by a local no-prompt executable so the constrained sandbox could demonstrate the final command-execution marker.

CVE-2026-59800 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:001:04
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-59800 · REPRO-20
0:02
0:03
web search
0:05
web search
0:06
0:07
0:09
web search
0:11
0:12
web search
0:20
0:20
extract_facts
no facts extracted
0:20
0:20
0:20
supportrepro
0:34
0:36
0:36
0:36
0:37
0:37
0:37
0:37
0:37
0:38
0:38
0:39
web search
0:41
0:55
0:56
web search
1:04
08 · How to Fix

How to Fix CVE-2026-59800

Upgrade 9router (npm) · npm to 0.4.44 (GitHub advisory page); OSV lists 0.4.45 as fixed range or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-59800 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-59800

What conditions are needed for the CVE-2026-59800 attack to work?

Sudo must not prompt for a password when the handler invokes it — for example because the process already runs as root, NOPASSWD is configured, or a recent sudo timestamp is cached. Under any of those conditions, an unauthenticated POST request can execute attacker-chosen shell commands as the 9router process user.

Which 9router versions are affected by CVE-2026-59800, and where is it fixed?

Versions < 0.4.44 (the advisory states <= v0.4.39) are affected. It is listed as fixed in 0.4.44 per the GitHub advisory page, though OSV lists 0.4.45 as the fixed range.

How severe is CVE-2026-59800?

It is rated critical with a CVSS score of 9.2 — an unauthenticated attacker can execute arbitrary commands as the 9router process user, and root command execution in root/container deployments.

How can I reproduce CVE-2026-59800?

Download the verified script from this page and run it in an isolated environment against 9router@0.4.39 with sudo configured not to prompt for a password. Send an unauthenticated POST to /api/tunnel/tailscale-install with a sudoPassword field containing shell metacharacters, and confirm the injected command executes; the fixed 9router@0.4.45 returns HTTP 401 instead.
11 · References

References for CVE-2026-59800

Authoritative sources for CVE-2026-59800 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.