Synadia Protect

Rules

Rules define the security policies the gateway evaluates against NATS protocol traffic. Every connection and message flowing through the gateway is matched against rules that decide whether to allow, deny, or suspend it.

Rule types

Rules target one of two protocol stages:

TypeEvaluatesWhen
connectthe CONNECT protocol messageonce, when the client or leafnode connects
messagePUB, HPUB, MSG, HMSG, LMSG, LHMSGon every msg protocol

SUB, UNSUB (and leaf equivalents LS+, LS-) are not evaluated by rules — they are traced and counted but not subject to policy.

Actions

When a rule matches, it produces an action:

ActionTerminalDescription
allownopermit the operation
denyyesreject the operation and close the connection
suspendnopark the connection — keep alive but drop the backend connection
erroryesa deny initiated by an error processing a rule (logged and recorded in metrics)
lognolog the raw protocol to the auditor, continue evaluation

Terminal actions (deny, error) stop all further rule evaluation immediately. Non-terminal actions accumulate and evaluation continues.

Built-in rules

The gateway ships with built-in rules for common use cases. Using built-in rules requires no expression writing — just activate the rule and provide configuration values.

List available built-in rules:

$ protect admin bundle builtins list --local

Built-in rules follow a naming convention:

com.synadia.protect.builtins.v1.<action>.<category>.<type>

Get details and example configuration for any built-in rule:

$ protect admin bundle builtins info --local <rule-id>

Configuring built-in rules

Create a .conf file with two sections — activations to enable the rules and configurations to provide their settings:

activations:
  com.synadia.protect.builtins.v1.deny.payload.message: true

configurations:
  com.synadia.protect.builtins.v1.deny.payload.message:
    'logs.>': '(?i)password|secret|api_key'

A single .conf file can activate multiple built-in rules:

activations:
  com.synadia.protect.builtins.v1.allow.header.message: true
  com.synadia.protect.builtins.v1.deny.time.message: true

configurations:
  com.synadia.protect.builtins.v1.allow.header.message:
    X-Tenant: '^acme$'
  com.synadia.protect.builtins.v1.deny.time.message:
    - schedule: '* 0-9 * * *'
      subject: 'orders.>'
    - schedule: '* 17-23 * * *'
      subject: 'orders.>'

See the built-in rules reference for all available rules and their configuration schemas.

Available categories

CategoryTypeWhat it inspects
cidr_clientconnectclient IP address
cidr_serverconnectbackend server IP
consumer_filtermessageJetStream consumer filter subjects
external_jwt_signerconnectJWT signature on connection
headermessagemessage header existence/patterns
payloadmessagemessage payload content (regex per subject)
timeconnect, messagecron schedule (UTC)
protolenmessageprotocol message length
subject_wildcardmessagewildcard subjects

Custom rules

For cases the built-ins don't cover, write custom rules as .yaml files with expressions in the Expr language. Expr is a simple, safe expression language — not a general-purpose programming language. Custom rules have full access to the connection context, message contents, headers, and metadata.

The Rules Reference has the complete field tables for all evaluation objects, conditions, and custom functions.

Rule file structure

name: <string> # unique within the bundle
description: <string> # optional

facts: # connection-level filters (static for the connection lifetime)
  - connection_kind: client # required: client or leaf

conditions: # per-message filters
  - rule_type: connect # required: connect or message

default: allow # action when no rule body matches

rules: # one or more rule bodies
  - expression: <expr>
    success: deny # action when expression returns true
    fail: allow # action when expression returns false
    message: 'audit log text' # shown in audit logs on deny/suspend

Facts

Facts are static properties of a connection — they never change after connect. The engine uses facts to pre-filter the ruleset so only relevant rules are evaluated on a specific connection.

FactValuesDescription
connection_kindclient, leafrequired on every rule
remote_ipIP addressexact match on source IP

Facts use OR logic within the same key and AND across different keys:

facts:
  - connection_kind: client
  - connection_kind: leaf # matches client OR leaf
  - remote_ip: 10.0.0.1
  - remote_ip: 10.0.0.2 # matches either IP

The rule applies if the connection is a client or a leafnode and the source IP is either 10.0.0.1 or 10.0.0.2. If either value differs, the rule is not evaluated.

Conditions

Conditions filter per protocol message. They determine whether a rule applies to a given message.

Required:

KeyValuesDescription
rule_typeconnect, messagewhich protocol messages this rule handles

Direction (message rules):

KeyValuesDescription
directionto_backend, from_backend, both, inherittraffic direction filter. inherit (or omitting) uses the port's default_rule_direction

Connect conditions (available for both connect and message rule types):

KeyMatches against
usernameConnect.Username
passwordConnect.Password
tokenConnect.Token
nkeyConnect.Nkey
jwtConnect.JWT
nameConnect.Name
langConnect.Lang
versionConnect.Version

Message conditions (available for message rule type):

KeyMatches against
subjectexact match on Message.Subject
subject_matchNATS wildcard match on Message.Subject
subject_not_matchnegative NATS wildcard match
reply_toexact match on Message.ReplyTo
has_headertrue if header key exists
not_headertrue if header key does not exist
accountAccountInfo.Account
is_system_accountAccountInfo.IsSystemAccount

Conditions use OR within the same key, AND across different keys.

Rule bodies

Each rule body has an expression that evaluates to a boolean:

rules:
  - expression: Connect.Username == "system"
    success: deny
    fail: allow
    message: 'system user not allowed'

Evaluation stops on the first terminal action (deny or error). If no rule body produces a terminal action, the default applies.

Evaluation environment

Rule expressions have access to these objects:

ObjectDescriptionAvailable in
Metaconnection metadata — direction, address, remote server, timeconnect, message
ConnectNATS CONNECT protocol fields — username, token, JWT, etc.connect, message
MessageNATS message — subject, payload, headers, reply-tomessage
AccountInfoaccount name and system account flagconnect, message

Fields are accessed using PascalCase: Connect.Username, Message.Subject, Meta.Address.

Custom functions

The gateway extends Expr with NATS-specific functions:

FunctionDescription
subjectMatch(subject, pattern)NATS wildcard match (* and >)
subjectHasWildcards(subject)true if subject contains wildcards
isLiteralSubject(subject)true if subject has no wildcards
matchCIDR(address, cidr)IPv4/IPv6 CIDR match
matchesTime(schedule, timestamp)cron schedule match (UTC)
hasHeader(config, headers)check headers against config map
payloadMatches(config, subject, payload)match payload against regex by subject pattern
regexMatch(str, pattern)Go regex match
bytesToString(data)convert payload bytes to string
normalizeJSSubject(subject)strip domain/prefix from JetStream subjects
fromJsRequest()parse JetStream request from message payload
hasAllowedConsumerFilter(allowed)check consumer filters against allowed list
hasDeniedConsumerFilter(denied)check consumer filters against denied list
IsTrustedExternalSigner(token, config)JWT signature verification

Allow all clients, deny system user

name: client_connect
description: client connection restrictions

facts:
  - connection_kind: client

conditions:
  - rule_type: connect

default: allow

rules:
  - success: deny
    message: system user not allowed
    expression: |
      Connect.Username == "system"

Block large messages on leafnodes

name: message_sizes
description: check message sizes

facts:
  - connection_kind: leaf

conditions:
  - rule_type: message

default: allow

rules:
  - success: deny
    message: message too big
    expression: |
      len(Message.Payload) > 256 * 1024

Protect JetStream streams from deletion

name: protect_streams
description: prevent stream deletion and purge

facts:
  - connection_kind: leaf

conditions:
  - rule_type: message
  - account: production
  - subject_match: '$JS.>'

default: allow

rules:
  - success: deny
    message: stream removal not allowed
    expression: |
      subjectMatch(Message.Subject, "$JS.API.STREAM.DELETE.>")

  - success: deny
    message: stream purge not allowed
    expression: |
      subjectMatch(Message.Subject, "$JS.API.STREAM.PURGE.>")

Office hours with CIDR restriction

name: office_hours_only
description: restrict access to business hours from office IPs

facts:
  - connection_kind: client

conditions:
  - rule_type: connect

default: deny

rules:
  - success: allow
    message: office hours from trusted network
    expression: |
      matchCIDR(Meta.Address, "10.0.0.0/8") && matchesTime("* 9-17 * * 1-5", Meta.Time)
Previous
Ports