Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
CRITICAL severity

GHSA-q6xx-5vr8-p898 — nezha

CRITICAL

GHSA-q6xx-5vr8-p898 is a critical-severity (CVSS 9.9) CWE-639 vulnerability in github.com/nezhahq/nezha. A fix is available for github.com/nezhahq/nezha — see the affected versions and patch details below.

Nezha vulnerable to cross-tenant terminal/file-manager session hijack via WebSocket stream UUID without ownership check

Also known asCVE-2026-62283GO-2026-5821
Published
Jun 26, 2026
Updated
Aug 22, 2026
Affected
2 pkgs
Patched
1 / 2
Exploits
None indexed
Exploitation data as of Sep 23, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • A successful exploit gives an attacker total control of the affected component, not partial access.
  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for GHSA-q6xx-5vr8-p898.

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs31th percentile — riskier than 31% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

GHSA-q6xx-5vr8-p898 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 379,145 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

2 pkgs affected
🐹github.com/nezhahq/nezha🐹github.com/nezhahq/nezha

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

Summary

In nezha v1.14.13–v1.14.14 and v2.0.0–v2.0.9, the WebSocket endpoints GET /ws/terminal/:id and GET /ws/file/:id authenticate the caller only by the presence of a valid stream UUID, with no ownership check tying that UUID to the user who created the stream. Any authenticated dashboard user (including a RoleMember) who learns a live stream UUID can attach to the session and gain interactive shell access or full file-manager control on the target server — i.e. cross-tenant RCE.

This was silently fixed in commit 6661d6a (2026-05-18, shipped in v2.0.10). At submission time no public CVE/GHSA covers this fix, so operators of v1.14.x and pre-v2.0.10 v2.x deployments have no signal that they are running vulnerable code.

Details

Stream allocation — service/rpc/io_stream.go (v2.0.9):

func (s *NezhaHandler) CreateStream(streamId string) {
    s.ioStreamMutex.Lock()
    defer s.ioStreamMutex.Unlock()

    s.ioStreams[streamId] = &ioStreamContext{
        userIoConnectCh:  make(chan struct{}),
        agentIoConnectCh: make(chan struct{}),
    }
}

No creator is bound to the stream.

Stream attach — cmd/dashboard/controller/terminal.go (v2.0.9):

// @Router /ws/terminal/{id} [get]
func terminalStream(c *gin.Context) (any, error) {
    streamId := c.Param("id")
    if _, err := rpc.NezhaHandlerSingleton.GetStream(streamId); err != nil {
        return nil, err
    }
    defer rpc.NezhaHandlerSingleton.CloseStream(streamId)
    // ... WebSocket upgrade and bidirectional pipe ...
}

The only authorization check is GetStream(streamId) — "does this UUID exist in the in-memory map". getUid(c) is never compared against the user who called createTerminal. The same pattern is present in fmStream(c) in cmd/dashboard/controller/fm.go.

Where the UUID leaks:

createTerminal returns the UUID to the legitimate client, which then opens wss://<dashboard>/ws/terminal/<UUID>. As a URL path component the UUID is exposed via:

  • Reverse-proxy access logs (nginx, Caddy, Cloudflare).
  • Referer headers when the page embeds external resources or error reporters.
  • Browser history / bookmark sync.
  • Frontend telemetry (Sentry, Bugsnag) breadcrumbs that include the WebSocket URL.
  • Any shared-tenant or multi-operator log viewer.

Any authenticated user with access to one of these side channels can attach to a live session.

PoC

  1. Deploy nezha v2.0.9. Add at least one server. Configure two accounts: admin (RoleAdmin, owns the server) and member (RoleMember, no access to that server).
  2. As admin, open the web terminal for the server. The browser opens wss://<dashboard>/ws/terminal/<UUID>. Capture this UUID from the network inspector, server access log, or Referer header.
  3. From a separate session logged in as member, open wss://<dashboard>/ws/terminal/<UUID> (same UUID). The member's WebSocket attaches to the same ioStreamContext because terminalStream only checks GetStream(streamId) — no ownership check.
  4. The member can now read the admin's shell output and inject keystrokes, achieving shell-level RCE on the target server, with no visible signal to the legitimate session owner.

Same flow works against /ws/file/:id (file-manager hijack: arbitrary read/write on the target server's filesystem).

Impact

  • Severity: Critical. Interactive RCE on a server administered by another user, with no audit signal to the rightful session owner.
  • Attack complexity: Low. The attacker needs an authenticated dashboard account (which any RoleMember is) and one captured UUID from a side channel.
  • Confidentiality / Integrity / Availability: all High. /ws/file/:id exposes arbitrary read+write on the target filesystem; /ws/terminal/:id is a full shell.

This is the same impact tier as CVE-2026-46716 (cross-tenant cron RCE) and arguably worse, because the entry point is a passively-leaked URL rather than an authenticated POST — attackers do not need direct dashboard interaction once the UUID is leaked through logs or telemetry.

Fix reference

Already fixed in master by commit 6661d6a ("fix(rpc): bind io_stream sessions to creator to prevent terminal/fm hijack"):

  • CreateStream now accepts a creatorUserID uint64 and stores it on the ioStreamContext.
  • New IsStreamAuthorizedForUser(streamId, userID, isAdmin) helper.
  • terminalStream and fmStream call this helper before the WebSocket upgrade and before the defer CloseStream(streamId), so a rejected attempt does not tear down a legitimate stream.

Shipped in v2.0.10 (2026-05-19). The v1.14 line has not received a backport.

Why this advisory

The fix landed silently. The other May 17–21 fixes received public GHSAs (GHSA-99gv-2m7h-3hh9, GHSA-rxf6-wjh4-jfj6, GHSA-hvv7-hfrh-7gxj, GHSA-w4g9-mxgg-j532, GHSA-6x26-5727-rrm9, GHSA-4g6j-g789-rghm) covering cron RCE, AlertRule trigger, telemetry leak, notification SSRF, DDNS SSRF, and agent forge-results respectively — but none cover the terminal / file-manager session hijack. This advisory closes that gap so operators of v1.14.x and v2.0.0–v2.0.9 know to upgrade.

Recommended action

  • Publish this GHSA so v2.x operators below v2.0.10 see the alert in their dependency scanners.
  • Either backport 6661d6a to a v1.14.15 release, or mark the v1.14 line end-of-life in SECURITY.md so operators understand the support boundary.

Affected Packages

2 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/nezhahq/nezha≥ 1.14.13No fix
🐹Gogithub.com/nezhahq/nezha≥ 2.0.0&&< 2.0.102.0.10go get github.com/nezhahq/nezha@v2.0.10

Detection & mitigation playbook

Open-source dependency
  1. Detect

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

  2. Fix

    No patched version of github.com/nezhahq/nezha has shipped for GHSA-q6xx-5vr8-p898 yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-q6xx-5vr8-p898 can be triaged on real exposure rather than presence alone.

Tailored to GHSA-q6xx-5vr8-p898. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### Summary In nezha **v1.14.13–v1.14.14** and **v2.0.0–v2.0.9**, the WebSocket endpoints `GET /ws/terminal/:id` and `GET /ws/file/:id` authenticate the caller only by the presence of a valid stream UUID, with no ownership check tying that UUID to the user who created the stream. Any authenticated dashboard user (including a `RoleMember`) who learns a live stream UUID can attach to the session and gain interactive shell access or full file-manager control on the target server — i.e. cross-tenant RCE. This was silently fixed in commit [`6661d6a`](https://github.com/nezhahq/nezha/commit/6661d6
O3 Security · Impact-Aware SCA

Is GHSA-q6xx-5vr8-p898 in your dependencies?

O3 Security finds GHSA-q6xx-5vr8-p898 across Go dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

GHSA-q6xx-5vr8-p898: nezha (Critical 9.9) | O3 Security