GitHub Advisoryadvisory · Go

GHSA-jg62-j5h6-8mpq Nezha Monitoring: Unbounded WebSocket Streams — Resource Exhaustion DoS

MEDIUM Published 2026-06-26 Modified 2026-06-26 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

References