GHSA-q6xx-5vr8-p898: Github.com/nezhahq/nezha Nezha Vulnerable To Cross-tenant Terminal/file-manager Vulnerability
CRITICALGHSA-q6xx-5vr8-p898 is a CRITICAL (CVSS 9.9) nezha vulnerable to cross-tenant terminal/file-manager vulnerability (CWE-639: Authorization Bypass Through User-Controlled Key) affecting Github.com/nezhahq/nezha libraries in the Go ecosystem. The issue involves nezha vulnerable to cross-tenant terminal/file-manager session hijack via websocket stream uuid without ownership check. Remediation is available by upgrading affected packages to version 2.0.10.
GHSA-q6xx-5vr8-p898 at a Glance
Security Impact
CVSS: 9.9
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H).
How to Fix GHSA-q6xx-5vr8-p898
To remediate this vulnerability, upgrade all affected Github.com/nezhahq/nezha dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade github.com/nezhahq/nezha to Latest secure release
Upgrade github.com/nezhahq/nezha to 2.0.10
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for github.com/nezhahq/nezha
How to Check if You Are Affected
To determine whether your application is affected by GHSA-q6xx-5vr8-p898, inspect your project dependencies for Github.com/nezhahq/nezha:
inspect dependencies for github.com/nezhahq/nezha
Analyze the terminal output to locate any version of Github.com/nezhahq/nezha in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.0.10), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-639: Authorization Bypass Through User-Controlled Key, where the system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data. In Github.com/nezhahq/nezha, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
GHSA-q6xx-5vr8-p898 Affected Github.com/nezhahq/nezha Versions
2 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
github.com/nezhahq/nezha
|
Go |
≥ 1.14.13
≤ 1.14.14
|
|
|
github.com/nezhahq/nezha
|
Go |
≥ 2.0.0
< 2.0.10
|
2.0.10
|
References
Frequently Asked Questions
What is GHSA-q6xx-5vr8-p898?
Which Github.com/nezhahq/nezha versions are affected?
What version fixes GHSA-q6xx-5vr8-p898?
Is github.com/nezhahq/nezha affected?
What is the CVSS score of GHSA-q6xx-5vr8-p898?
What CWE is associated with GHSA-q6xx-5vr8-p898?
How can I check Go dependencies for GHSA-q6xx-5vr8-p898?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-q6xx-5vr8-p898",
"modified" : "2026-06-26T22:31:41Z",
"published" : "2026-06-26T22:31:41Z",
"aliases" : [ ],
"summary" : "Nezha vulnerable to cross-tenant terminal/file-manager session hijack via WebSocket stream UUID without ownership check",
"details" : "### Summary\n\nIn 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.\n\nThis was silently fixed in commit [`6661d6a`](https://github.com/nezhahq/nezha/commit/6661d6a7fc1c269f55c7f4e775082ad23fbe0f54) (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.\n\n### Details\n\n**Stream allocation — `service/rpc/io_stream.go` (v2.0.9):**\n\n```go\nfunc (s *NezhaHandler) CreateStream(streamId string) {\n s.ioStreamMutex.Lock()\n defer s.ioStreamMutex.Unlock()\n\n s.ioStreams[streamId] = &ioStreamContext{\n userIoConnectCh: make(chan struct{}),\n agentIoConnectCh: make(chan struct{}),\n }\n}\n```\n\nNo creator is bound to the stream.\n\n**Stream attach — `cmd/dashboard/controller/terminal.go` (v2.0.9):**\n\n```go\n// @Router /ws/terminal/{id} [get]\nfunc terminalStream(c *gin.Context) (any, error) {\n streamId := c.Param(\"id\")\n if _, err := rpc.NezhaHandlerSingleton.GetStream(streamId); err != nil {\n return nil, err\n }\n defer rpc.NezhaHandlerSingleton.CloseStream(streamId)\n // ... WebSocket upgrade and bidirectional pipe ...\n}\n```\n\nThe 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`.\n\n**Where the UUID leaks:**\n\n`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:\n\n- Reverse-proxy access logs (nginx, Caddy, Cloudflare).\n- Referer headers when the page embeds external resources or error reporters.\n- Browser history / bookmark sync.\n- Frontend telemetry (Sentry, Bugsnag) breadcrumbs that include the WebSocket URL.\n- Any shared-tenant or multi-operator log viewer.\n\nAny authenticated user with access to one of these side channels can attach to a live session.\n\n### PoC\n\n1. 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).\n2. 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.\n3. 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.\n4. 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.\n\nSame flow works against `/ws/file/:id` (file-manager hijack: arbitrary read/write on the target server's filesystem).\n\n### Impact\n\n- **Severity**: Critical. Interactive RCE on a server administered by another user, with no audit signal to the rightful session owner.\n- **Attack complexity**: Low. The attacker needs an authenticated dashboard account (which any `RoleMember` is) and one captured UUID from a side channel.\n- **Confidentiality / Integrity / Availability**: all High. `/ws/file/:id` exposes arbitrary read+write on the target filesystem; `/ws/terminal/:id` is a full shell.\n\nThis 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.\n\n### Fix reference\n\nAlready fixed in master by commit [`6661d6a`](https://github.com/nezhahq/nezha/commit/6661d6a7fc1c269f55c7f4e775082ad23fbe0f54) (\"fix(rpc): bind io_stream sessions to creator to prevent terminal/fm hijack\"):\n\n- `CreateStream` now accepts a `creatorUserID uint64` and stores it on the `ioStreamContext`.\n- New `IsStreamAuthorizedForUser(streamId, userID, isAdmin)` helper.\n- `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.\n\nShipped in v2.0.10 (2026-05-19). The v1.14 line has not received a backport.\n\n### Why this advisory\n\nThe 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.\n\n### Recommended action\n\n- Publish this GHSA so v2.x operators below v2.0.10 see the alert in their dependency scanners.\n- 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.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "github.com/nezhahq/nezha"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "1.14.13"
}, {
"last_affected" : "1.14.14"
} ]
} ]
}, {
"package" : {
"ecosystem" : "Go",
"name" : "github.com/nezhahq/nezha"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "2.0.0"
}, {
"fixed" : "2.0.10"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 2.0.9"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/nezhahq/nezha/security/advisories/GHSA-q6xx-5vr8-p898"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/nezhahq/nezha"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-639", "CWE-862" ],
"severity" : "CRITICAL",
"github_reviewed" : true,
"github_reviewed_at" : "2026-06-26T22:31:41Z",
"nvd_published_at" : null
}
}