CVE-2026-86065
Klever-Go: Unauthenticated WebSocket /subscribe: no read-size limit, no connection cap, permissive origin -> remote node memory/goroutine exhaustion (DoS)
Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, the default-open GET /subscribe endpoint in network/api/websocket/routes.go accepts unauthenticated WebSocket clients with permissive origin handling, does not call SetReadLimit to bound message size, and has no live-connection cap. SocketHub.HandleClientInsertion also accepts an unbounded address list that grows addressSubscription, and client.loopIn continues reading without a size limit, allowing one client to grow subscription maps or many clients to retain goroutines, buffered channels, and descriptors. The global HTTP request throttler does not count upgraded live WebSocket connections. Because the REST and WebSocket API runs in the node process, memory or scheduler exhaustion can crash the node and interrupt P2P and consensus participation. This issue is fixed in version 1.7.20.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- None
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
7.5
Klever-Go: Unauthenticated WebSocket /subscribe: no read-size limit, no connection cap, permissive origin -> remote node memory/goroutine exhaustion (DoS)
Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, the default-open GET /subscribe endpoint in network/api/websocket/routes.go accepts unauthenticated WebSocket clients with permissive origin handling, does not call SetReadLimit to bound message size, and has no live-connection cap. SocketHub.HandleClientInsertion also accepts an unbounded address list that grows addressSubscription, and client.loopIn continues reading without a size limit, allowing one client to grow subscription maps or many clients to retain goroutines, buffered channels, and descriptors. The global HTTP request throttler does not count upgraded live WebSocket connections. Because the REST and WebSocket API runs in the node process, memory or scheduler exhaustion can crash the node and interrupt P2P and consensus participation. This issue is fixed in version 1.7.20.
- Record
CVE-2026-86065- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-770
- Updated
- 2026-11-02
EUVDEUVD-2026-85575
7.5
Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, the default-open GET /subscribe endpoint in network/api/websocket/routes.go accepts unauthenticated WebSocket clients with permissive origin handling, does not call SetReadLimit to bound message size, and has no live-connection cap. SocketHub.HandleClientInsertion also accepts an unbounded address list that grows addressSubscription, and client.loopIn continues reading without a size limit, allowing one client to grow subscription maps or many clients to retain goroutines, buffered channels, and descriptors. The global HTTP request throttler does not count upgraded live WebSocket connections. Because the REST and WebSocket API runs in the node process, memory or scheduler exhaustion can crash the node and interrupt P2P and consensus participation. This issue is fixed in version 1.7.20.
- Record
EUVD-2026-85575- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-4fwh-wrm6-97xm
7.5
Klever-Go: Unauthenticated WebSocket /subscribe: no read-size limit, no connection cap, permissive origin -> remote node memory/goroutine exhaustion (DoS)
Summary
The unauthenticated WebSocket endpoint GET /subscribe is registered open: true by default
(config/node/api.yaml) and lets a remote, unauthenticated client exhaust the node's memory and
goroutines. Because the REST API runs IN-PROCESS with the node — network/api/api.go Start(...)
ends with ws.Run(kleverFacade.RestAPIInterface()) — exhausting/killing the API process takes down
the entire node, including its P2P and consensus participation. No API key, account, stake, or funds
are required.
Three compounding, independently-exploitable gaps stack on this one endpoint:
- Permissive origin —
upgrader.CheckOriginalways returnstrue(network/api/websocket/routes.go), so any web origin can complete the handshake. - No read-size limit — the connection never calls
conn.SetReadLimit(...). gorilla's default is UNLIMITED, so a singleconn.ReadJSON(processSubscription) orconn.ReadMessage(client.loopIn) can be forced to allocate an arbitrarily large buffer from ONE frame. - No connection / fan-out cap — the gin global throttler (
simultaneousRequests: 100) releases its slot as soon ashandleSubscribereturns, which it does immediately aftergo processSubscription(conn, hub). Live WebSocket connections are therefore NOT counted by it. There is no per-IP / per-connection / hub-level cap. Each accepted connection spawns 2 goroutines plus a 500-entry buffered channel, andreq.Addresseshas no length cap, so the hub'saddressSubscriptionmap grows 1:1 with attacker-supplied strings.
Affected Component / Code Path
Unauthenticated, reachable by default, no recovery on the resource-allocation path:
gin engine (network/api/api.go: Start -> ws.Run, IN-PROCESS with node)
-> GET /subscribe network/api/websocket/routes.go:34 (SubscribeTopics)
-> handleSubscribe network/api/websocket/routes.go:39
-> upgrader.Upgrade (CheckOrigin == true) network/api/websocket/routes.go:22 <-- GAP #1
-> go processSubscription(conn, hub) network/api/websocket/routes.go:46 (throttler slot freed here)
-> conn.ReadJSON(&req) (no SetReadLimit) network/api/websocket/routes.go:57 <-- GAP #2
-> hub.HandleClientInsertion(...) websocket/websocket.go:121 <-- GAP #3 (addresses uncapped)
-> websocket.NewClient -> loopIn/loopOut (2 goroutines + 500-buf chan per conn) websocket/client.go:24
-> conn.ReadMessage() (no SetReadLimit, no deadline) websocket/client.go:77 <-- GAP #2
Root-cause excerpts (commit 23b74e1):
network/api/websocket/routes.go
var upgrader = gorilla.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // GAP #1: any origin accepted
},
}
func handleSubscribe(c *gin.Context, hub *websocket.SocketHub) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Error(subscribeOp, "err", err.Error())
return
}
go processSubscription(conn, hub) // returns now -> gin global throttler slot released (GAP #3)
}
func processSubscription(conn *gorilla.Conn, hub *websocket.SocketHub) {
// no conn.SetReadLimit(...) anywhere (GAP #2)
_ = conn.SetReadDeadline(time.Now().Add(subscribeReadTimeout))
var req subscribeRequest
if err := conn.ReadJSON(&req); err != nil { ... } // unbounded read
_ = conn.SetReadDeadline(time.Time{}) // deadline cleared
...
client := websocket.NewClient(conn, hub)
hub.HandleClientInsertion(parsedTypes, req.Addresses, client) // req.Addresses uncapped (GAP #3)
}
websocket/websocket.go — HandleClientInsertion inserts every address with no length cap:
for _, address := range addresses {
if _, ok := h.addressSubscription[address]; !ok {
h.addressSubscription[address] = make(map[*client]userOptions) // grows 1:1 with attacker input
}
...
}
websocket/client.go — loopIn reads with no…
- Record
GHSA-4fwh-wrm6-97xm- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-770
- Updated
- 2026-11-02
NVD
7.5
Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, the default-open GET /subscribe endpoint in network/api/websocket/routes.go accepts unauthenticated WebSocket clients with permissive origin handling, does not call SetReadLimit to bound message size, and has no live-connection cap. SocketHub.HandleClientInsertion also accepts an unbounded address list that grows addressSubscription, and client.loopIn continues reading without a size limit, allowing one client to grow subscription maps or many clients to retain goroutines, buffered channels, and descriptors. The global HTTP request throttler does not count upgraded live WebSocket connections. Because the REST and WebSocket API runs in the node process, memory or scheduler exhaustion can crash the node and interrupt P2P and consensus participation. This issue is fixed in version 1.7.20.
- Record
CVE-2026-86065- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-770
- Updated
- 2026-11-02
OSV
7.5
Klever-Go: Unauthenticated WebSocket /subscribe: no read-size limit, no connection cap, permissive origin -> remote node memory/goroutine exhaustion (DoS)
Klever-Go is the Go implementation of the Klever blockchain protocol. Prior to 1.7.20, the default-open GET /subscribe endpoint in network/api/websocket/routes.go accepts unauthenticated WebSocket clients with permissive origin handling, does not call SetReadLimit to bound message size, and has no live-connection cap. SocketHub.HandleClientInsertion also accepts an unbounded address list that grows addressSubscription, and client.loopIn continues reading without a size limit, allowing one client to grow subscription maps or many clients to retain goroutines, buffered channels, and descriptors. The global HTTP request throttler does not count upgraded live WebSocket connections. Because the REST and WebSocket API runs in the node process, memory or scheduler exhaustion can crash the node and interrupt P2P and consensus participation. This issue is fixed in version 1.7.20.
- Record
CVE-2026-86065- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-770
- Updated
- 2026-11-02
OSVGHSA-4fwh-wrm6-97xm
7.5
Klever-Go: Unauthenticated WebSocket /subscribe: no read-size limit, no connection cap, permissive origin -> remote node memory/goroutine exhaustion (DoS)
Summary
The unauthenticated WebSocket endpoint GET /subscribe is registered open: true by default
(config/node/api.yaml) and lets a remote, unauthenticated client exhaust the node's memory and
goroutines. Because the REST API runs IN-PROCESS with the node — network/api/api.go Start(...)
ends with ws.Run(kleverFacade.RestAPIInterface()) — exhausting/killing the API process takes down
the entire node, including its P2P and consensus participation. No API key, account, stake, or funds
are required.
Three compounding, independently-exploitable gaps stack on this one endpoint:
- Permissive origin —
upgrader.CheckOriginalways returnstrue(network/api/websocket/routes.go), so any web origin can complete the handshake. - No read-size limit — the connection never calls
conn.SetReadLimit(...). gorilla's default is UNLIMITED, so a singleconn.ReadJSON(processSubscription) orconn.ReadMessage(client.loopIn) can be forced to allocate an arbitrarily large buffer from ONE frame. - No connection / fan-out cap — the gin global throttler (
simultaneousRequests: 100) releases its slot as soon ashandleSubscribereturns, which it does immediately aftergo processSubscription(conn, hub). Live WebSocket connections are therefore NOT counted by it. There is no per-IP / per-connection / hub-level cap. Each accepted connection spawns 2 goroutines plus a 500-entry buffered channel, andreq.Addresseshas no length cap, so the hub'saddressSubscriptionmap grows 1:1 with attacker-supplied strings.
Affected Component / Code Path
Unauthenticated, reachable by default, no recovery on the resource-allocation path:
gin engine (network/api/api.go: Start -> ws.Run, IN-PROCESS with node)
-> GET /subscribe network/api/websocket/routes.go:34 (SubscribeTopics)
-> handleSubscribe network/api/websocket/routes.go:39
-> upgrader.Upgrade (CheckOrigin == true) network/api/websocket/routes.go:22 <-- GAP #1
-> go processSubscription(conn, hub) network/api/websocket/routes.go:46 (throttler slot freed here)
-> conn.ReadJSON(&req) (no SetReadLimit) network/api/websocket/routes.go:57 <-- GAP #2
-> hub.HandleClientInsertion(...) websocket/websocket.go:121 <-- GAP #3 (addresses uncapped)
-> websocket.NewClient -> loopIn/loopOut (2 goroutines + 500-buf chan per conn) websocket/client.go:24
-> conn.ReadMessage() (no SetReadLimit, no deadline) websocket/client.go:77 <-- GAP #2
Root-cause excerpts (commit 23b74e1):
network/api/websocket/routes.go
var upgrader = gorilla.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // GAP #1: any origin accepted
},
}
func handleSubscribe(c *gin.Context, hub *websocket.SocketHub) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Error(subscribeOp, "err", err.Error())
return
}
go processSubscription(conn, hub) // returns now -> gin global throttler slot released (GAP #3)
}
func processSubscription(conn *gorilla.Conn, hub *websocket.SocketHub) {
// no conn.SetReadLimit(...) anywhere (GAP #2)
_ = conn.SetReadDeadline(time.Now().Add(subscribeReadTimeout))
var req subscribeRequest
if err := conn.ReadJSON(&req); err != nil { ... } // unbounded read
_ = conn.SetReadDeadline(time.Time{}) // deadline cleared
...
client := websocket.NewClient(conn, hub)
hub.HandleClientInsertion(parsedTypes, req.Addresses, client) // req.Addresses uncapped (GAP #3)
}
websocket/websocket.go — HandleClientInsertion inserts every address with no length cap:
for _, address := range addresses {
if _, ok := h.addressSubscription[address]; !ok {
h.addressSubscription[address] = make(map[*client]userOptions) // grows 1:1 with attacker input
}
...
}
websocket/client.go — loopIn reads with no…
- Record
GHSA-4fwh-wrm6-97xm- CVSS 3.1
- 7.5 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - Weakness
- CWE-770
- Updated
- 2026-11-02
OSVGO-2026-6581 No score
Klever-Go: Unauthenticated WebSocket /subscribe: no read-size limit, no connection cap, permissive origin -> remote node memory/goroutine exhaustion (DoS) in github.com/klever-io/klever-go
Klever-Go: Unauthenticated WebSocket /subscribe: no read-size limit, no connection cap, permissive origin -> remote node memory/goroutine exhaustion (DoS) in github.com/klever-io/klever-go
- Record
GO-2026-6581- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
github.com/klever-io/klever-goGo |
before 1.7.20 | 1.7.20 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-86065 is a high vulnerability (CVSS 7.5) in Klever-io Klever-go, published September 23, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.43%, the 37th percentile. Fixed in github.com/klever-io/klever-go 1.7.20.
References
github.com/klever-io/klever-go/security/advisories/GHSA-4fwh-wrm6-97xm
github.com/klever-io/klever-go/pull/76
github.com/klever-io/klever-go/commit/b8af922e749ec1fa7451b2c6e7c9f634d8603be2
github.com/klever-io/klever-go/releases/tag/v1.7.20
github.com/advisories/GHSA-4fwh-wrm6-97xm
github.com/CVEProject/cvelistV5/tree/main/cves/2026/86xxx/CVE-2026-86065.json
nvd.nist.gov/vuln/detail/CVE-2026-86065
github.com/klever-io/klever-go
Left out here: the rest of 2 source descriptions. The JSON record has them.