GHSA-jg62-j5h6-8mpq Nezha Monitoring: Unbounded WebSocket Streams — Resource Exhaustion DoS
1. DescriptionThe 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:A `ioStreamContext` struct with several channels and sync primitivesTwo goroutines via `StartStream()` (io_stream.go:358-369) — bidirectional `io.CopyBuffer`A gRPC IOStream between the dashboard and the agentAn agent-side PTY/shell process**Vulnerable code:**`terminal.go:27-67` — `createTerminal`:`fm.go:28-67` — `createFM`:`io_stream.go:55-67` — `CreateStreamWithPurpose` (inserts into unbounded map):`io_stream.go:319-372` — `StartStream` spawns two goroutines per stream:The `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`.The `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.2. PoCA conceptual attack (no Docker needed):Each request:Creates a new stream entry in `ioStreams`Sends a `TaskTypeTerminalGRPC` task to the agentWhen the WebSocket attachment occurs (`GET /ws/terminal/{id}`), spawns 2 goroutines for I/O relay and allocates a 1 MB buffer per goroutineThe attack targets three resource domains:**Dashboard memory/goroutines** — each stream adds goroutines, channels, and buffers**Agent resources** — each stream spawns a PTY/shell process on the monitored server**gRPC connection pool** — concurrent IOStreams consume gRPC multiplexing capacityThe `POST /file (createFM)` endpoint provides an alternative path with the same unbounded behavior, using `ScopeServerRead+Write+Delete` instead of `ScopeServerExec`.3. Impact**Denial of Service against the dashboard**: memory exhaustion, goroutine starvation, or gRPC stream table overflow from rapid stream creation**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**Operational cascade**: if the dashboard OOMs, all agent monitoring and alerting is lost**PAT connection-registry bypass**: rapid create-connect-disconnect cycles may evade cleanup trackingThe 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.4. RemediationImplement layered rate limiting and concurrency control:**Per-user stream cap** in `CreateStream` — reject if the user already has N active streams (e.g., 10 per user):**Per-server semaphore** — limit concurrent streams to any single server (e.g., 20 per server)**Rate limiter on `createTerminal` and `createFM`** — mirror the existing MCP rate limiter (`mcp_ratelimit.go`) for legacy WebSocket endpoints**Add a configurable `MaxStreamsPerUser` / `MaxStreamsPerServer` setting** so operators can tune limits without code changes
CVSS v3.1 score: 6.5