CVE-2026-53522: Nezha Monitoring Remote Code Execution Vulnerability
MODERATECVE-2026-53522 is a MODERATE (CVSS 6.5) remote code execution vulnerability (CWE-770: Allocation of Resources Without Limits or Throttling) affecting Nezha Monitoring libraries in the Go ecosystem. The issue involves unbounded websocket streams — resource exhaustion dos. Remediation is available by upgrading affected packages to version 2.2.0.
CVE-2026-53522 at a Glance
Security Impact
CVSS: 6.5
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H).
How to Fix CVE-2026-53522
To remediate this vulnerability, upgrade all affected Nezha Monitoring dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade github.com/nezhahq/nezha to 2.2.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for github.com/nezhahq/nezha
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-53522, inspect your project dependencies for Nezha Monitoring:
inspect dependencies for github.com/nezhahq/nezha
Analyze the terminal output to locate any version of Nezha Monitoring in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.2.0), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-770: Allocation of Resources Without Limits or Throttling, where the product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated. In Nezha Monitoring, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-53522 Affected Nezha Monitoring Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
github.com/nezhahq/nezha
|
Go |
≥ 1.0.0
< 2.2.0
|
2.2.0
|
References
Frequently Asked Questions
What is CVE-2026-53522?
Which Nezha Monitoring versions are affected?
What version fixes CVE-2026-53522?
Is github.com/nezhahq/nezha affected?
What is the CVSS score of CVE-2026-53522?
What CWE is associated with CVE-2026-53522?
How can I check Go dependencies for CVE-2026-53522?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-jg62-j5h6-8mpq",
"modified" : "2026-06-26T23:04:18Z",
"published" : "2026-06-26T23:04:18Z",
"aliases" : [ "CVE-2026-53522" ],
"summary" : "Nezha Monitoring: Unbounded WebSocket Streams — Resource Exhaustion DoS",
"details" : "## 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",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "github.com/nezhahq/nezha"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "1.0.0"
}, {
"fixed" : "2.2.0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/nezhahq/nezha/security/advisories/GHSA-jg62-j5h6-8mpq"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-53522"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/nezhahq/nezha"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-770" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-06-26T23:04:18Z",
"nvd_published_at" : "2026-06-12T22:16:52Z"
}
}