{"id":"GHSA-jg62-j5h6-8mpq","revision":null,"records":[{"revision":"1188ab56710b4116b1bf883dea552637","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-53522","aliases":[],"title":"Nezha Monitoring: Unbounded WebSocket Streams — Resource Exhaustion DoS","description":"Nezha Monitoring is a self-hostable, lightweight, servers and websites monitoring and O&M tool. From version 1.0.0 to before version 2.2.0, the Nezha dashboard exposes two endpoints that create long-lived WebSocket streams to monitored agents: POST /api/v1/terminal → createTerminal() (terminal.go:27-67) and POST /api/v1/file → createFM() (fm.go:28-67). Both call rpc.NezhaHandlerSingleton.CreateStream(streamId, ...) which inserts a new ioStreamContext into an unbounded map[string]*ioStreamContext (s.ioStreams in io_stream.go:59-67). There is no per-user rate limit, no global semaphore, and no per-server connection cap. This issue has been patched in version 2.2.0.","state":0,"publishedAt":"2026-06-12T21:04:38.357+00:00","modifiedAt":"2026-06-15T19:27:20.84+00:00","affected":[{"ecosystem":"Vendor product","name":"nezha","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"nezhahq\",\n                    \"product\": \"nezha\",\n                    \"versions\": [\n                        {\n                            \"version\": \">= 1.0.0, < 2.2.0\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","score":6.5,"severity":"MEDIUM"}],"references":["https://github.com/nezhahq/nezha/security/advisories/GHSA-jg62-j5h6-8mpq"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-53522","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-53522","identifiers":["CVE-2026-53522"]},"retained":false,"historicalAffected":[]},{"revision":"c88c4922994d4194ba59503b35a767cf","observedAt":"2026-11-07T11:37:40.273+00:00","statement":{"source":"euvd","id":"EUVD-2026-36601","aliases":["CVE-2026-53522","GHSA-jg62-j5h6-8mpq"],"title":"Nezha Monitoring is a self-hostable, lightweight, servers and websites monitoring and O&M tool. From version 1.0.0 to before version 2.2.0, the Nezha dashboard exposes two endpoints that create long-lived WebSocket streams to monitored agen…","description":"Nezha Monitoring is a self-hostable, lightweight, servers and websites monitoring and O&M tool. From version 1.0.0 to before version 2.2.0, the Nezha dashboard exposes two endpoints that create long-lived WebSocket streams to monitored agents: POST /api/v1/terminal → createTerminal() (terminal.go:27-67) and POST /api/v1/file → createFM() (fm.go:28-67). Both call rpc.NezhaHandlerSingleton.CreateStream(streamId, ...) which inserts a new ioStreamContext into an unbounded map[string]*ioStreamContext (s.ioStreams in io_stream.go:59-67). There is no per-user rate limit, no global semaphore, and no per-server connection cap. This issue has been patched in version 2.2.0.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","score":6.5,"severity":null}],"references":["https://github.com/nezhahq/nezha/security/advisories/GHSA-jg62-j5h6-8mpq"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-36601","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Jun 12, 2026, 9:04:38 PM","dateUpdated":"Jun 15, 2026, 7:27:20 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-36601","identifiers":["EUVD-2026-36601","CVE-2026-53522","GHSA-jg62-j5h6-8mpq"]},"retained":false,"historicalAffected":[]},{"revision":"e49505aca73c42fd9275967da077659f","observedAt":"2026-10-27T14:13:22.737+00:00","statement":{"source":"nvd","id":"CVE-2026-53522","aliases":[],"title":"Nezha Monitoring is a self-hostable, lightweight, servers and websites monitoring and O&M tool. From version 1.0.0 to before version 2.2.0, the Nezha dashboard exposes two endpoints that create long-lived WebSocket streams to monitored agen…","description":"Nezha Monitoring is a self-hostable, lightweight, servers and websites monitoring and O&M tool. From version 1.0.0 to before version 2.2.0, the Nezha dashboard exposes two endpoints that create long-lived WebSocket streams to monitored agents: POST /api/v1/terminal → createTerminal() (terminal.go:27-67) and POST /api/v1/file → createFM() (fm.go:28-67). Both call rpc.NezhaHandlerSingleton.CreateStream(streamId, ...) which inserts a new ioStreamContext into an unbounded map[string]*ioStreamContext (s.ioStreams in io_stream.go:59-67). There is no per-user rate limit, no global semaphore, and no per-server connection cap. This issue has been patched in version 2.2.0.","state":0,"publishedAt":"2026-06-12T22:16:52.377+00:00","modifiedAt":"2026-10-25T23:10:00.167+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","score":6.5,"severity":"MEDIUM"}],"references":["https://github.com/nezhahq/nezha/security/advisories/GHSA-jg62-j5h6-8mpq"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-53522","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-770"],"productCriteria":[],"sourceDates":{"published":"2026-06-12T22:16:52.377","lastModified":"2026-10-25T23:10:00.167"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-53522","identifiers":["CVE-2026-53522"]},"retained":false,"historicalAffected":[]},{"revision":"7a2335e93ff2463f953d1013e23de2fa","observedAt":"2026-10-24T13:19:41.952+00:00","statement":{"source":"osv","id":"GHSA-jg62-j5h6-8mpq","aliases":["CVE-2026-53522","GO-2026-5831"],"title":"Nezha Monitoring: Unbounded WebSocket Streams — Resource Exhaustion DoS","description":"## 1. Description\n\nThe Nezha dashboard exposes two endpoints that create long-lived WebSocket streams to monitored agents:\n\n- `POST /api/v1/terminal` → `createTerminal()` (terminal.go:27-67)\n- `POST /api/v1/file` → `createFM()` (fm.go:28-67)\n\nBoth call `rpc.NezhaHandlerSingleton.CreateStream(streamId, ...)` which inserts a new `ioStreamContext` into an **unbounded** `map[string]*ioStreamContext` (`s.ioStreams` in `io_stream.go:59-67`). There is **no per-user rate limit, no global semaphore, and no per-server connection cap**. Each stream allocates:\n\n1. A `ioStreamContext` struct with several channels and sync primitives\n2. Two goroutines via `StartStream()` (io_stream.go:358-369) — bidirectional `io.CopyBuffer`\n3. A gRPC IOStream between the dashboard and the agent\n4. An agent-side PTY/shell process\n\n**Vulnerable code:**\n\n`terminal.go:27-67` — `createTerminal`:\n```go\nfunc createTerminal(c *gin.Context) (*model.CreateTerminalResponse, error) {\n    // ... validation ...\n    rpc.NezhaHandlerSingleton.CreateStream(streamId, getUid(c), server.ID)\n    // ... sends TaskTypeTerminalGRPC to agent ...\n    return &model.CreateTerminalResponse{...}, nil\n}\n```\n\n`fm.go:28-67` — `createFM`:\n```go\nfunc createFM(c *gin.Context) (*model.CreateFMResponse, error) {\n    // ... validation ...\n    rpc.NezhaHandlerSingleton.CreateStream(streamId, getUid(c), server.ID)\n    // ... sends TaskTypeFM to agent ...\n    return &model.CreateFMResponse{...}, nil\n}\n```\n\n`io_stream.go:55-67` — `CreateStreamWithPurpose` (inserts into unbounded map):\n```go\nfunc (s *NezhaHandler) CreateStreamWithPurpose(...) {\n    s.ioStreamMutex.Lock()\n    defer s.ioStreamMutex.Unlock()\n    s.ioStreams[streamId] = &ioStreamContext{\n        creatorUserID:  creatorUserID,\n        targetServerID: targetServerID,\n        purpose:        purpose,\n        userIoConnectCh:  make(chan struct{}),\n        agentIoConnectCh: make(chan struct{}),\n        revokedCh:        make(chan struct{}),\n    }\n}\n```\n\n`io_stream.go:319-372` — `StartStream` spawns two goroutines per stream:\n```go\nfunc (s *NezhaHandler) StartStream(streamId string, timeout time.Duration) error {\n    // ...\n    go func() {\n        _, innerErr := io.CopyBuffer(userIo, agentIo, bp.buf)\n        errCh <- innerErr\n    }()\n    go func() {\n        _, innerErr := io.CopyBuffer(agentIo, userIo, bp.buf)\n        errCh <- innerErr\n    }()\n    return <-errCh\n}\n```\n\nThe `NezhaHandler.ioStreams` map is initialized as a plain `make(map[string]*ioStreamContext)` in `nezha.go:36` — no capacity limit, no eviction policy beyond explicit `CloseStream` / `RevokeStreamsForServer`.\n\nThe `HasPermission` check at terminal.go:41-43 and fm.go:43-45 controls **access scope** but does **not** limit creation volume. A user with `ScopeServerExec` (terminal) or `ScopeServerRead+Write+Delete` (file manager) can open unlimited streams.\n\n## 2. PoC\n\nA conceptual attack (no Docker needed):\n\n```\n# As an authenticated user with a valid JWT or PAT:\nfor i in {1..1000}; do\n  curl -X POST \"https://dashboard.example.com/api/v1/terminal\" \\\n    -H \"Authorization: Bearer $JWT\" \\\n    -H \"Content-Type: application/json\" \\\n    -d '{\"server_id\": 1}' &\ndone\nwait\n```\n\nEach request:\n- Creates a new stream entry in `ioStreams`\n- Sends a `TaskTypeTerminalGRPC` task to the agent\n- When the WebSocket attachment occurs (`GET /ws/terminal/{id}`), spawns 2 goroutines for I/O relay and allocates a 1 MB buffer per goroutine\n\nThe attack targets three resource domains:\n1. **Dashboard memory/goroutines** — each stream adds goroutines, channels, and buffers\n2. **Agent resources** — each stream spawns a PTY/shell process on the monitored server\n3. **gRPC connection pool** — concurrent IOStreams consume gRPC multiplexing capacity\n\nThe `POST /file (createFM)` endpoint provides an alternative path with the same unbounded behavior, using `ScopeServerRead+Write+Delete` instead of `ScopeServerExec`.\n\n## 3. Impact\n\n- **Denial of Service against the dashboard**: memory exhaustion, goroutine starvation, or gRPC stream table overflow from rapid stream creation\n- **Denial of Service against monitored agents**: each terminal session spawns a PTY process on the agent — an attacker can crash or degrade all agents behind the dashboard\n- **Operational cascade**: if the dashboard OOMs, all agent monitoring and alerting is lost\n- **PAT connection-registry bypass**: rapid create-connect-disconnect cycles may evade cleanup tracking\n\nThe attack requires only authenticated access with standard scopes — no special privileges. Any team member with terminal access to a server can DoS the entire infrastructure.\n\n## 4. Remediation\n\nImplement layered rate limiting and concurrency control:\n\n1. **Per-user stream cap** in `CreateStream` — reject if the user already has N active streams (e.g., 10 per user):\n   ```go\n   func (s *NezhaHandler) CreateStreamWithPurpose(...) {\n       s.ioStreamMutex.Lock()\n       defer s.ioStreamMutex.Unlock()\n       count := 0\n       for _, ctx := range s.ioStreams {\n           if ctx.creatorUserID == creatorUserID { count++ }\n       }\n       if count >= maxStreamsPerUser { return error }\n       // ... existing code ...\n   }\n   ```\n\n2. **Per-server semaphore** — limit concurrent streams to any single server (e.g., 20 per server)\n\n3. **Rate limiter on `createTerminal` and `createFM`** — mirror the existing MCP rate limiter (`mcp_ratelimit.go`) for legacy WebSocket endpoints\n\n4. **Add a configurable `MaxStreamsPerUser` / `MaxStreamsPerServer` setting** so operators can tune limits without code changes","state":0,"publishedAt":"2026-06-26T23:04:18+00:00","modifiedAt":"2026-07-07T16:11:11.689384+00:00","affected":[{"ecosystem":"Go","name":"github.com/nezhahq/nezha","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"1.0.0"},{"kind":"fixed","version":"2.2.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/nezhahq/nezha/security/advisories/GHSA-jg62-j5h6-8mpq","https://nvd.nist.gov/vuln/detail/CVE-2026-53522","https://github.com/nezhahq/nezha"],"sourceUrl":"https://github.com/advisories/GHSA-jg62-j5h6-8mpq","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-jg62-j5h6-8mpq","identifiers":["GHSA-jg62-j5h6-8mpq","CVE-2026-53522","GO-2026-5831"]},"retained":false,"historicalAffected":[]},{"revision":"b680632e12304ad38e256194dc0252e3","observedAt":"2026-10-24T13:19:41.952+00:00","statement":{"source":"osv","id":"GO-2026-5831","aliases":["CVE-2026-53522","GHSA-jg62-j5h6-8mpq"],"title":"Nezha Monitoring: Unbounded WebSocket Streams — Resource Exhaustion DoS in github.com/nezhahq/nezha","description":"Nezha Monitoring: Unbounded WebSocket Streams — Resource Exhaustion DoS in github.com/nezhahq/nezha.\n\nNOTE: The source advisory for this report contains additional versions that could not be automatically mapped to standard Go module versions.\n\n(If this is causing false-positive reports from vulnerability scanners, please suggest an edit to the report.)\n\nThe additional affected modules and versions are: github.com/nezhahq/nezha before v2.2.0.","state":0,"publishedAt":"2026-07-07T15:26:32+00:00","modifiedAt":"2026-07-07T16:01:34.7379647+00:00","affected":[{"ecosystem":"Go","name":"github.com/nezhahq/nezha","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"1.0.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[],"references":["https://github.com/nezhahq/nezha/security/advisories/GHSA-jg62-j5h6-8mpq","https://nvd.nist.gov/vuln/detail/CVE-2026-53522"],"sourceUrl":"https://osv.dev/vulnerability/GO-2026-5831","license":"OSV · upstream source license applies","key":"osv:GO-2026-5831","identifiers":["GO-2026-5831","CVE-2026-53522","GHSA-jg62-j5h6-8mpq"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-07T20:52:46.7845564+00:00","complete":true,"records":1769,"lastAttemptAt":"2026-11-07T20:52:46.7845564+00:00","lastSuccessAt":"2026-11-07T20:52:46.9707302+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:52:46.9707321+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-bb5c9d5a08063ef076042e33dd72a7d0ffa4c4487efdcf3106e29ba55ec2ad57-1766179264875bb4621a23ce4c6bcebec63fb794a16e35c2f5592f4ee300ee2e","complete":true,"records":420944,"lastAttemptAt":"2026-11-07T20:52:46.1142943+00:00","lastSuccessAt":"2026-11-07T20:52:46.0341312+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:52:46.1142943+00:00","snapshotPublishedAt":"2026-11-07T20:26:36+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-cbfb00168958467aaa6015d5230faa08","complete":true,"records":13633,"lastAttemptAt":"2026-11-07T21:04:54.6100336+00:00","lastSuccessAt":"2026-11-07T21:04:54.606+00:00","error":null,"lastVerifiedAt":"2026-11-07T21:04:54.606+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13685,"expectedIdentifiers":13685,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-07T20:52:46.7845564+00:00","complete":true,"records":1096,"lastAttemptAt":"2026-11-07T20:52:46.7845564+00:00","lastSuccessAt":"2026-11-07T20:52:47.1482288+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:52:47.1482303+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-b9464f9c589b4886928e7045b5ad1e11","complete":true,"records":13645,"lastAttemptAt":"2026-11-07T20:52:54.6850231+00:00","lastSuccessAt":"2026-11-07T20:52:54.68+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:52:54.68+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13685,"expectedIdentifiers":13685,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-0eb4d6dde62dffacd040ad992d4fd5a65cd7d0dc5f5f4cfa8b41967fc6f9ca5f","complete":true,"records":9914,"lastAttemptAt":"2026-11-07T20:52:46.6024506+00:00","lastSuccessAt":"2026-11-06T20:10:36.6450506+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:52:46.6024506+00:00","snapshotPublishedAt":"2026-11-06T20:02:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-fde804134ecdda0ddd5ff5c26ee1fff5a6fa659311f7ef2e51c6ad54964db0e2","complete":true,"records":1971,"lastAttemptAt":"2026-11-07T20:52:46.3552169+00:00","lastSuccessAt":"2026-11-06T19:03:17.0399903+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:52:46.3552169+00:00","snapshotPublishedAt":"2026-11-06T18:17:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-335633e78a15c9cde10a6025895b5617717087fb945489ae7e15d377bacf34a9","complete":true,"records":3055,"lastAttemptAt":"2026-11-07T20:52:46.7261257+00:00","lastSuccessAt":"2026-11-06T16:03:00.7615789+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:52:46.7261257+00:00","snapshotPublishedAt":"2026-11-06T16:02:48+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-40ad6183a951565ace1520a9b4d631e1d0bfb1d4bf7c5574c375032559f3ff53","complete":true,"records":231292,"lastAttemptAt":"2026-11-07T20:52:46.4923243+00:00","lastSuccessAt":"2026-11-07T10:29:00.4821259+00:00","error":null,"lastVerifiedAt":"2026-11-07T20:52:46.4923243+00:00","snapshotPublishedAt":"2026-11-07T09:32:47+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}