GitHub Security Advisory: GHSA-pq2q-rcw4-3hr6
Package: github.com/nats-io/nats-server/v2 (GO)
Severity: HIGH (CVSS 7.5)
CWE: CWE-190: Integer Overflow or Wraparound
Vulnerable versions: >= 2.12.0, < 2.12.5
Patched in: 2.12.5

Summary: NATS: Pre-auth remote server crash via WebSocket frame length overflow in wsRead

Details:
### 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](https://github.com/nats-io/nats-server/blob/a69f51f/server/websocket.go#L278)

```go
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:**

1. The attacker sends a WebSocket frame with the MSB set in the 64-bit length field (e.g., `0x[PII_REDACTED]000001`).

2. At line 278, `int(0x[PII_REDACTED]000001)` produces `[PII_REDACTED]854775807` on 64-bit Go (two's complement reinterpretation — Go does not panic on integer conversion overflow).

3. `r.rem` is now negative. At line 307–311, the bounds clamp fails:

   ```go
   n = r.rem                    // n = [PII_REDACTED]854775807
   if pos+n > max {             // 14 + (-huge) = negative, NOT > max → FALSE
       n = max - pos            // clamp NEVER fires
   }
   b = buf[pos : pos+n]         // buf[14 : [PII_REDACTED]854775793] → PANIC
   ```

   The 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 l