GitHub Advisoryadvisory · Go
GHSA-jg62-j5h6-8mpq Nezha Monitoring: Unbounded WebSocket Streams — Resource Exhaustion DoS
MEDIUM
Official page ↗
- Aliases
- CVE-2026-53522
- Ecosystem
- Go
- Packages
- github.com/nezhahq/nezha
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
- Weaknesses
- CWE-770
- Reviewed
- 2026-06-26
Affected packages
• Go github.com/nezhahq/nezha: >= 1.0.0, < 2.2.0 → fixed in 2.2.0
Description
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` — `CreateStreamWithPurpose` (inserts into unbounded map):
```go
func (s *NezhaHandler) CreateStreamWithPurpose(...) {
s.ioStreamMutex.Lock()
defer s.ioStreamMutex.Unlock()
s.ioStreams[streamId] = &ioStreamContext{
creatorUserID: creatorUserID,
targetServerID: targetServerID,
purpose: purpose,
userIoConnectCh: make(chan struct{}),
agentIoConnectCh: make(chan struct{}),
revokedCh: make(chan struct{}),
}
}
```
`io_stream.go:319-372` — `StartStream` spawns two goroutines per stream:
```go
func (s *NezhaHandler) StartStream(streamId string, timeout time.Duration) error {
// ...
go func() {
_, innerErr := io.CopyBuffer(userIo, agentIo, bp.buf)
errCh = maxStreamsPerUser { return error }
// ... existing code ...
}
```
2. Per-server semaphore — limit concurrent streams to any single server (e.g., 20 per server)
3. Rate limiter on `createTerminal` and `createFM` — mirror the existing MCP rate limiter (`mcp_ratelimit.go`) for legacy WebSocket endpoints
4. Add a configurable `MaxStreamsPerUser` / `MaxStreamsPerServer` setting so operators can tune limits without code changes
Related
Alias · CVE (1)
- CVE-2026-53522 Nezha Monitoring is a self-hostable, lightweight, servers and websites monitoring and O&M tool. vulnerability
Weakness · CWE (1)
- CWE-770 Allocation of Resources Without Limits or Throttling weakness