Nezha Monitoring: Unbounded WebSocket Streams — Resource Exhaustion DoS
Date: 2026-06
Affected: go/github.com/nezhahq/nezha
Attack vector: dos
Category: vulnerability-disclosure
Severity: Medium
CVE: CVE-2026-53522
CWE: CWE-770
CVSS: 6.5 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H)

## 1. Description The Nezha dashboard exposes two endpoints that create long-lived WebSocket streams to monitored agents: - `POST /api/v1/terminal` → `createTerminal()` (terminal.go:27-67) - `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**. Each stream allocates: 1. A `ioStreamContext` struct with several channels and sync primitives 2. Two goroutines via `StartStream()` (io_stream.go:358-369) — bidirectional `io.CopyBuffer` 3. A gRPC IOStream between the dashboard and the agent 4. An agent-side PTY/shell process **Vulnerable code:** `terminal.go:27-67` — `createTerminal`: ```go func createTerminal(c *gin.Context) (*model.CreateTerminalResponse, error) { // ... validation ... rpc.NezhaHandlerSingleton.CreateStream(streamId, getUid(c), server.ID) // ... sends TaskTypeTerminalGRPC to agent ... return &model.CreateTerminalResponse{...}, nil } ``` `fm.go:28-67` — `createFM`: ```go func createFM(c *gin.Context) (*model.CreateFMResponse, error) { // ... validation ... rpc.NezhaHandlerSingleton.CreateStream(streamId, getUid(c), server.ID) // ... sends TaskTypeFM to agent ... return &model.CreateFMResponse{...}, nil } ``` `io_stream.go:55-67` — `CreateStreamWith


Mitigations:

Architectural layers (MAESTRO):