NATS Security Testing: Authentication Bypass, Subject Authorization Bypass, and JetStream Abuse

NATS is a high-performance cloud-native messaging system used for microservice communication, event streaming via JetStream, and service mesh. Unlike Kafka (which requires Zookeeper/KRaft), NATS runs as a single binary and is easy to misconfigure. Default configurations allow unauthenticated connections, subject wildcards can be abused to subscribe to topics beyond intended scope, JetStream persists messages that can be replayed by any authorized consumer, and NATS accounts provide multi-tenancy isolation that can be bypassed through operator-level credential exposure. This guide covers systematic NATS security assessment.

Table of Contents

  1. Authentication and No-Auth Testing
  2. Subject Authorization Bypass via Wildcards
  3. JetStream Stream Data Access Testing
  4. Account Isolation Testing
  5. Credentials File Exposure
  6. Service Subject Impersonation
  7. TLS and Certificate Testing
  8. NATS Security Hardening

Authentication and No-Auth Testing

# NATS defaults to no authentication — any client can connect and pub/sub
# Test connectivity without credentials

# Test unauthenticated connection (uses Go NATS CLI tool)
nats pub test.subject "probe" --server nats://nats.example.com:4222 2>&1
# If succeeds: no authentication required

# Or with netcat (NATS protocol is plaintext):
echo "CONNECT {}\r\nPUB test.probe 5\r\nhello\r\nPING\r\n" | nc nats.example.com 4222
# Response: PONG = unauthenticated connection succeeded

# Subscribe to all subjects (# wildcard = match everything)
nats sub ">" --server nats://nats.example.com:4222 &
# Then wait for messages published to any subject by any service

# Check NATS server information (publicly exposed)
curl -s http://nats.example.com:8222/varz | python3 -c "
import json,sys
r = json.load(sys.stdin)
print(f'Version: {r.get(\"version\",\"?\")}')
print(f'Auth required: {r.get(\"auth_required\",False)}')
print(f'TLS required: {r.get(\"tls_required\",False)}')
print(f'Max connections: {r.get(\"max_connections\",\"?\")}')
print(f'Connected clients: {r.get(\"connections\",\"?\")}')
print(f'JetStream enabled: {r.get(\"jetstream\",{}).get(\"enabled\",False)}')
"

# List connected clients (monitoring endpoint — no auth by default)
curl -s http://nats.example.com:8222/connz | python3 -c "
import json,sys
r = json.load(sys.stdin)
for conn in r.get('connections',[]):
    print(f\"Client: {conn.get('name','?')} IP: {conn.get('ip','?')} subs: {conn.get('num_subscriptions',0)}\")
"

Subject Authorization Bypass via Wildcards

# NATS subject authorization restricts which subjects a user can publish/subscribe to
# Test whether wildcard permissions are overly broad

# NATS subject wildcards:
# * = matches single token: orders.* matches orders.create but not orders.v1.create
# > = matches remaining: orders.> matches orders.create AND orders.v1.create.all

# Check a user's permissions (if you have access to server config)
# Overly broad examples to look for:
# publish: [">"]           = can publish to ALL subjects
# subscribe: [">"]         = can subscribe to ALL subjects (eavesdrop on everything)
# publish: ["admin.>"]     = can publish admin commands

# Test: subscribe with wildcard to capture cross-service messages
nats sub ">" --server nats://user:password@nats.example.com:4222 --count 100 2>/dev/null | \
  head -100
# Captures ALL messages across all services

# Test: publish to admin/privileged subjects
nats pub "admin.command" '{"action":"purge_all"}' \
  --server nats://user:password@nats.example.com:4222 2>&1
# If no -ERR Permissions Violation: user can publish admin commands

# Test: subscribe to other team's subjects
nats sub "team-b.>" --server nats://team-a-user:pass@nats.example.com:4222 2>&1
# Should return -ERR Permissions Violation for 'SUB "team-b.>"'
# If no error: team isolation not enforced

JetStream Stream Data Access Testing

# JetStream persists messages in streams — subscribers can replay historical messages
# Test whether stream access controls are properly configured

# List all JetStream streams (requires stream:list permission or admin)
nats stream ls --server nats://nats.example.com:4222 2>/dev/null

# Attempt to subscribe to a JetStream stream as a consumer
nats consumer sub ORDERS new-consumer \
  --server nats://user:pass@nats.example.com:4222 \
  --deliver all 2>/dev/null | head -20
# --deliver all replays ALL historical messages in the stream

# Test: create a stream on a subject you can publish to
nats stream add test-capture \
  --subjects ">" \
  --server nats://user:pass@nats.example.com:4222 \
  --storage file \
  --retention limits \
  --max-age 1h 2>/dev/null
# If allowed: captures ALL messages published to any subject for 1 hour

# Check stream metadata
curl -s http://nats.example.com:8222/jsz | python3 -c "
import json,sys
r = json.load(sys.stdin)
for stream in r.get('account_details',[]):
    for s in stream.get('stream_detail',[]):
        print(f\"Stream: {s.get('name','?')} messages: {s.get('state',{}).get('messages',0)}\")
"

NATS Security Hardening

NATS Security Hardening Checklist:
Security TestMethodRisk
Unauthenticated NATS connectionnats pub test "probe" without credentialsCritical
Wildcard ">" subscribe permissionnats sub ">" — if succeeds, reads all messagesCritical
JetStream historical message replaynats consumer sub STREAM new --deliver allHigh
Monitoring endpoint exposes cluster topologycurl :8222/connz — lists all connected clientsHigh
Publish to admin/control subjectsnats pub admin.command payloadHigh
Cross-account subject access without exportsSubscribe to account-B subject from account-AMedium

Automate NATS Security Testing

Ironimo tests NATS deployments for unauthenticated connections, subject authorization wildcard abuse, JetStream stream data replay access, account isolation bypass, credentials exposure, monitoring endpoint information disclosure, and TLS misconfiguration.

Start free scan