Skip to content

CVE-2026-59800: Verified Repro With Script Download

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 Variant found 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://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 serverfixed server
Variants tested

No distinct bypass or alternate unauthenticated command-injection trigger was confirmed. The vulnerable control reached the original /api/tunnel/tailscale-install sudoPassword-to-sh sink, but path-normalization, adjacent tunnel daemon, and adjacent CLI-tools candidates did not create markers on the patched/latest targ…

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.

Variant Analysis & Alternative Triggers for CVE-2026-59800

Versions: parent version tested: 9router@0.4.39.

No distinct bypass or alternate unauthenticated command-injection trigger was confirmed. The variant run reproduced the parent vulnerable control on 9router@0.4.39 through the real HTTP service and the same sudoPassword -> sudo -S sh sink, then tested path-normalization and adjacent privileged endpoints against the patched/latest target (9router@0.5.20, npm latest at runtime). The patched/latest build blocked unauthenticated requests before the privileged helpers or used code paths that do not interpret attacker-controlled password bytes as shell script input.

Fix Coverage / Assumptions

The fix relies on two invariants:

  1. Authorization invariant: privileged dashboard/API routes must be covered by middleware. The vulnerable matcher was an explicit allow-list that omitted /api/tunnel/*. The fixed/latest matcher is catch-all except static assets, so /api/tunnel/* and /api/cli-tools/* requests are checked before reaching privileged handlers.
  2. Shell-stdin invariant: attacker-influenced password bytes must not be fed to a shell process that is reading its script from stdin. The vulnerable Linux installer spawned sudo -S sh and wrote sudoPassword before the downloaded install script. The fixed/latest helper writes the install script to a temporary file and invokes sh with that file path, separating password stdin from script content.

Reviewed code paths include:

  • app/.next/server/middleware.js in 9router@0.4.39: matcher omits /api/tunnel/*.
  • app/.next-cli-build/server/middleware.js in 9router@0.5.20: catch-all matcher.
  • app/.next/server/chunks/3774.js in 9router@0.4.39: vulnerable spawn("sudo", ["-S", "sh"]) install path.
  • app/.next-cli-build/server/chunks/6457.js in 9router@0.5.20: install-script temp-file behavior.
  • Adjacent route bundles for /api/tunnel/tailscale-start-daemon and /api/cli-tools/antigravity-mitm.

The npm package did not contain a SECURITY.md or equivalent threat-model document. The service exposes dashboard/API/tunnel functionality, so unauthenticated remote HTTP access to privileged API routes was treated as in scope.

Variant / Alternate Trigger

Tested candidates:

  1. Parent baseline control: POST /api/tunnel/tailscale-install

    • Vulnerable: HTTP 200; marker created; original shell sink reached.
    • Fixed/latest: HTTP 403 Local only: CLI token required; marker absent.
    • Status: parent control, not a variant.
  2. Path-normalization candidate: POST /api/tunnel/tailscale-install/

    • Vulnerable: HTTP 308 redirect to canonical path; marker absent.
    • Fixed/latest: HTTP 308 redirect to canonical path; marker absent.
    • Status: not a bypass; following the redirect would only exercise the canonical route, which fixed/latest blocks.
  3. Adjacent tunnel daemon candidate: POST /api/tunnel/tailscale-start-daemon

    • Vulnerable: HTTP 200 under the no-prompt sudo precondition, but marker absent.
    • Fixed/latest: HTTP 401 Unauthorized; marker absent.
    • Status: not the same sink. This route writes a password token to sudo -S tailscaled ...; it does not cause sh to interpret sudoPassword bytes as script text.
  4. Adjacent CLI-tools candidate: POST /api/cli-tools/antigravity-mitm

    • Vulnerable: HTTP 401 Unauthorized; marker absent.
    • Fixed/latest: HTTP 403 Local only: CLI token required; marker absent.
    • Status: already protected in the vulnerable matcher and not a bypass of the tunnel-route fix.
  • Package/component: npm package 9router; Next.js standalone server; privileged tunnel/Tailscale API handlers.
  • Affected parent version tested: 9router@0.4.39.
  • Patched/latest version tested: 9router@0.5.20 (npm latest during this run). The ticket references 0.4.44, but that version was not available from npm during this run; repro-stage evidence also tested 0.4.45 as a patched negative control.
  • Risk level and consequences for the parent: critical unauthenticated remote OS command execution under the no-prompt sudo precondition.
  • Risk level for variants tested here: none confirmed.

Impact Parity

  • Disclosed/claimed maximum parent impact: unauthenticated remote OS command execution.
  • Reproduced impact from this variant run: the parent vulnerable control on 9router@0.4.39 created an attacker marker via the original route. No candidate created a marker on the fixed/latest target.
  • Parity: none for variants/bypasses. The run demonstrated the parent control only to prove the test environment reached the sink.
  • Not demonstrated: no distinct endpoint, path encoding, or adjacent route produced command execution on the patched/latest target.

Root Cause

The parent root cause is a combination of missing middleware coverage for /api/tunnel/tailscale-install and unsafe stdin composition for sudo -S sh. A remote unauthenticated body field (sudoPassword) is written into a stream later interpreted by sh when sudo does not prompt.

The tested candidates did not reach the same underlying bug on the fixed/latest build:

  • The catch-all matcher prevented unauthenticated tunnel/CLI requests from reaching privileged handlers.
  • The trailing-slash path normalized to the canonical route rather than bypassing middleware.
  • The daemon-start path uses sudo for tailscaled, not sh reading a script from stdin.
  • The CLI-tools route is protected and is not part of the missing /api/tunnel/* matcher gap.

No fix commit URL was available from the ticket/package metadata in this run.

Reproduction Steps

  1. Run bundle/vuln_variant/reproduction_steps.sh.
  2. The script:
    • resolves npm latest (0.5.20) and records it in bundle/logs/vuln_variant/latest_version.txt;
    • fetches/unpacks 9router@0.4.39 and 9router@0.5.20;
    • starts the real 9router server for each target;
    • supplies a PATH-local no-prompt sudo shim and a local install-script curl shim to deterministically exercise the original sink without external Tailscale dependency;
    • tests the parent baseline and three candidate variants against both vulnerable and fixed/latest builds;
    • exits 0 only if a patched/latest bypass is confirmed, otherwise exits 1 after completing all attempts.
  3. Expected evidence for this negative result:
    • Vulnerable parent baseline has HTTP 200 and a marker file.
    • Fixed/latest parent baseline and all variant candidates have no marker files.
    • The script completes without crashing; exit code 1 is expected for no confirmed variant.

Evidence

Primary evidence:

  • bundle/logs/vuln_variant/reproduction_steps.log — final runtime transcript.
  • bundle/vuln_variant/runtime_manifest.json — runtime manifest with tested versions and proof artifacts.
  • bundle/vuln_variant/source_scan_summary.json — matcher and sink observations.
  • bundle/vuln_variant/patch_analysis.md — detailed fix analysis.
  • bundle/vuln_variant/validation_verdict.json — structured negative verdict.

Key runtime excerpts from the final run:

[vuln][c0_parent_install_baseline] HTTP=200 marker=present ... Running install script...
[vuln][c0_parent_install_baseline] vulnerable control reached original install shell sink
[fixed][c0_parent_install_baseline] HTTP=403 marker=absent response={"error":"Local only: CLI token required"}
[fixed][c1_install_trailing_slash] HTTP=308 marker=absent response=/api/tunnel/tailscale-install
[fixed][c2_start_daemon] HTTP=401 marker=absent response={"error":"Unauthorized"}
[fixed][c3_cli_tools_mitm] HTTP=403 marker=absent response={"error":"Local only: CLI token required"}
RESULT: NO DISTINCT VARIANT/BYPASS ...

Environment/version details:

  • bundle/logs/vuln_variant/latest_version.txt: npm_latest_version=0.5.20.
  • bundle/logs/vuln_variant/fixed_version.txt: ticket patched version 0.4.44 was not present in npm registry; default patched control is 0.4.45.

Recommendations / Next Steps

  • Keep the catch-all middleware matcher and add automated route enumeration tests that assert all privileged /api/* endpoints reject unauthenticated remote requests unless explicitly public.
  • Add regression tests for /api/tunnel/tailscale-install, including no-prompt sudo behavior, to ensure sudoPassword is never interpreted as shell script content.
  • Avoid shell interpreters for privileged operations where possible. If a shell is required, pass immutable script file paths and validate any password/credential fields to reject newlines and shell metacharacters.
  • Treat newly added tunnel, CLI-tool, and update endpoints as high-risk: require explicit authorization tests and review for spawn, exec, and execSync usage.

Additional Notes

  • The variant script was executed multiple times and completed deterministically. The last two required verification runs both completed and exited 1, which is the documented no-variant outcome.
  • Runtime proof uses the same controlled no-prompt sudo precondition strategy as the repro stage to model root/NOPASSWD/cached-sudo deployments in this sandbox.
  • No source_identity.json is required because no distinct variant/bypass was confirmed.

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

Artifacts and Evidence for CVE-2026-59800

Scripts, logs, diffs, and output captured during the reproduction.

bundle/artifact_promotion_manifest.json26.9 KB
bundle/artifact_promotion_report.json37.0 KB
bundle/vuln_variant/root_cause_equivalence.json0.9 KB
bundle/repro/reproduction_steps.sh20.7 KB
bundle/repro/rca_report.md8.3 KB
bundle/repro/validation_verdict.json0.8 KB
bundle/repro/runtime_manifest.json1.3 KB
bundle/logs/demo_response.txt0.2 KB
bundle/logs/reproduction_steps.log2.0 KB
bundle/logs/vuln_1_response.txt0.3 KB
bundle/logs/fixed_1_response.txt0.0 KB
bundle/logs/vuln_1_server.log1.8 KB
bundle/logs/vuln_1_sudo_shim.log0.4 KB
bundle/logs/vuln_2_response.txt0.3 KB
bundle/logs/vuln_2_sudo_shim.log0.4 KB
bundle/logs/fixed_1_server.log1.5 KB
bundle/logs/fixed_2_response.txt0.0 KB
bundle/vuln_variant/reproduction_steps.sh11.7 KB
bundle/logs/vuln_variant/reproduction_steps.log2.2 KB
bundle/vuln_variant/validation_verdict.json3.1 KB
bundle/vuln_variant/variant_manifest.json3.7 KB
bundle/vuln_variant/patch_analysis.md6.8 KB
bundle/vuln_variant/rca_report.md9.0 KB
bundle/vuln_variant/runtime_manifest.json2.4 KB
bundle/logs/vuln_variant/fixed_c0_parent_install_baseline_http_code.txt0.0 KB
bundle/logs/vuln_variant/fixed_c0_parent_install_baseline_marker.txt0.0 KB
bundle/logs/vuln_variant/fixed_c0_parent_install_baseline_response.txt0.0 KB
bundle/logs/vuln_variant/fixed_c1_install_trailing_slash_http_code.txt0.0 KB
bundle/logs/vuln_variant/fixed_c1_install_trailing_slash_marker.txt0.0 KB
bundle/logs/vuln_variant/fixed_c1_install_trailing_slash_response.txt0.0 KB
bundle/logs/vuln_variant/fixed_c2_start_daemon_http_code.txt0.0 KB
bundle/logs/vuln_variant/fixed_c2_start_daemon_marker.txt0.0 KB
bundle/logs/vuln_variant/fixed_c2_start_daemon_response.txt0.0 KB
bundle/logs/vuln_variant/fixed_c3_cli_tools_mitm_http_code.txt0.0 KB
bundle/logs/vuln_variant/fixed_c3_cli_tools_mitm_marker.txt0.0 KB
bundle/logs/vuln_variant/fixed_c3_cli_tools_mitm_response.txt0.0 KB
bundle/logs/vuln_variant/fixed_server.log1.1 KB
bundle/logs/vuln_variant/fixed_version.txt0.1 KB
bundle/logs/vuln_variant/latest_version.txt0.0 KB
bundle/logs/vuln_variant/route_files.log3.6 KB
bundle/logs/vuln_variant/shell_sink_scan.log30.4 KB
bundle/logs/vuln_variant/vuln_c0_parent_install_baseline_http_code.txt0.0 KB
bundle/logs/vuln_variant/vuln_c0_parent_install_baseline_marker.txt0.1 KB
bundle/logs/vuln_variant/vuln_c0_parent_install_baseline_response.txt0.3 KB
bundle/logs/vuln_variant/vuln_c1_install_trailing_slash_http_code.txt0.0 KB
bundle/logs/vuln_variant/vuln_c1_install_trailing_slash_marker.txt0.0 KB
bundle/logs/vuln_variant/vuln_c1_install_trailing_slash_response.txt0.0 KB
bundle/logs/vuln_variant/vuln_c2_start_daemon_http_code.txt0.0 KB
bundle/logs/vuln_variant/vuln_c2_start_daemon_marker.txt0.0 KB
bundle/logs/vuln_variant/vuln_c2_start_daemon_response.txt0.0 KB
bundle/logs/vuln_variant/vuln_c3_cli_tools_mitm_http_code.txt0.0 KB
bundle/logs/vuln_variant/vuln_c3_cli_tools_mitm_marker.txt0.0 KB
bundle/logs/vuln_variant/vuln_c3_cli_tools_mitm_response.txt0.0 KB
bundle/logs/vuln_variant/vuln_server.log1.6 KB
bundle/logs/vuln_variant/vuln_sudo_shim.log1.7 KB
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.