Skip to main content
Two built-in guards protect publicly exposed gateways: a per-IP connection budget that caps half-open sockets before authentication, and a per-connection flood guard that closes connections sending too many unauthorized frames.

Quick Start

1

Defaults are on

Both protections are active the moment you start the gateway β€” no configuration required.
Internet-facing deployments are protected immediately. Loopback clients (127.0.0.1, localhost, ::1) are always exempt.
2

Tune via YAML

Override the defaults in your gateway.yaml under the top-level gateway: block:
Missing keys inherit the defaults (32 and 10). Set either key to 0 to disable that protection.
3

Tune via Python

Pass the values directly to GatewayConfig:

How It Works

Loopback is always exempt. Connections from any semantic loopback address (127.0.0.0/8, ::1, localhost), or a gateway bound to loopback, bypass the pre-auth budget entirely. Your local dev workflow is never affected.

Configuration Options

Validation: Both values must be >= 0. Negative values raise ValueError at startup.

Close Codes Reference

Do not retry-loop on 4028. Reconnecting immediately after a 4028 close will trigger another 4028 and amplify load. Fix the authentication token or scope first, then reconnect.
Minimal correct reconnect handler:

When to Change the Defaults

Prefer declaring the proxy trusted so both guards stay active but key on the real client instead of the proxy peer:
The per-IP budget and rate limits then separate real callers instead of collapsing every request onto one proxy bucket. See Trusted Proxies.As a fallback, if your proxy already caps concurrent connections per IP and applies its own rate limiting, you can disable both guards:
Only disable when the proxy is on the same trusted network and you are confident it enforces comparable limits. A misconfigured proxy with preauth_max_connections_per_ip=0 leaves the gateway fully open to half-open socket exhaustion.
Multiple legitimate users behind a single NAT IP share one budget slot pool. Raise preauth_max_connections_per_ip to accommodate your expected peak simultaneous connections:
A value of 2–4Γ— your expected simultaneous users from that IP is a reasonable starting point.
Clients probing for available scopes before obtaining a token may exhaust their unauthorized frame budget quickly. Raise max_unauthorized_frames or, better, fix the client to send a valid token on the first frame:
The cleaner fix is client-side: fetch a token before opening the WebSocket, not after.
Lower both values to reduce the attack surface for internet-exposed deployments facing active scanning:
Test with your legitimate traffic first β€” values that are too low will reject real users.

Unresolvable IP Handling

When the client IP is literally missing (e.g., a broken reverse-proxy configuration omits the X-Forwarded-For header), all unresolvable connections are grouped into a single shared bucket keyed __unresolved__. This is fail-closed β€” the shared bucket still counts toward the cap, so a flood of unresolvable IPs cannot exhaust the gateway by bypassing IP-level limits. This __unresolved__ bucket applies only when the source IP is absent β€” it is not how trusted-proxy resolution works. To resolve the real client behind a declared proxy (rather than bucketing on the proxy peer), set gateway.trusted_proxies. See Trusted Proxies.
If you see unexpected 4029 closes on a proxied deployment, check that your proxy correctly forwards X-Forwarded-For or X-Real-IP, and declare the proxy in trusted_proxies so each real client keys on its own bucket.

AuthRateLimiter Overflow Hardening

Beyond the two user-tunable knobs, the underlying AuthRateLimiter includes a defensive-in-depth overflow fix:
  • When the key map is saturated (max tracked IPs reached) and a new IP arrives, the request is rejected rather than admitted β€” preventing a fresh-IP flood from evicting existing lockouts.
  • Existing lockouts are preserved even when the map is full β€” a flood of new IPs cannot clear the lockout of a previously blocked attacker.
  • Expired-lockout recovery: IPs already in the lockout table can clear their stale entry even when the map is saturated, preventing permanent trapping.
This is not a user-tunable knob. It is active by default on all gateway deployments.

Best Practices

Both guards β€” the per-IP connection budget and the unauthorized-frame flood guard β€” are active by default. Don’t disable them on internet-exposed deployments; they close the pre-auth window that a naive gateway leaves open to half-open socket floods.
A client closed with 4029 hit the per-IP pre-auth connection budget; 4028 means it sent too many unauthorized frames on one connection. Treat 4029 as β€œtoo many sockets from this IP” and 4028 as β€œthis connection misbehaved after connecting” when triaging client issues.
If legitimate clients behind a shared NAT trip the per-IP budget, raise it deliberately rather than turning the guard off. Size it to the maximum concurrent connections you expect from a single source IP, with headroom for reconnect storms.
These protections stop pre-auth abuse, not authenticated misuse. Pair them with per-identity rate limiting and authentication so an attacker who gets past the handshake still faces request-level limits.

Gateway

Full gateway configuration reference

Security

Security policies and deployment hardening

Gateway Handshake Protocol

Version negotiation, capabilities, and structured connection errors

Gateway Rate Limiting

Per-identity request rate limiting with sliding-window policy