Authentication attempts (pre-auth)
Failed authentication attempts are throttled per client IP, before any request handling. This is the brute-force guard for exposed Gateways.- Only wrong credentials count. Missing credentials (a client that never sent a token) and successful authentications do not consume budget; a successful auth resets the counter for that IP.
- Defaults: 10 failures per 60 seconds, then a 5 minute lockout for that IP.
- Loopback (
127.0.0.1/::1) is exempt by default so local CLI sessions cannot be locked out. - Counters are scoped per credential class, so a flood against one surface does not displace another. Scopes include the shared gateway token/password, device tokens, node pairing, paired-node reapproval, device bootstrap tokens, and watchOS challenge issuance.
gateway.auth.rateLimit in openclaw.json:
AUTH_RATE_LIMITED entries in the Gateway log mean someone is
guessing credentials; see the exposure runbook.
Browser-origin connections
WebSocket connections that carry a browserOrigin header use the same
limits but with the loopback exemption always off — a malicious page in
a local browser is still an untrusted client, so localhost gets no free pass
on that path. When such a connection arrives from a loopback address, its
failures are keyed by the normalized page origin (for example
browser-origin:https://evil.example) rather than the shared loopback IP,
so each origin gets its own bucket; from non-loopback addresses the key
stays the client IP. This is not configurable.
Webhooks
The HTTP/hooks ingress has its own failure limiter: 20 failed
authentications per 60 seconds per client IP, then a 60 second lockout.
Loopback is not exempt. Successful hook auth resets the counter. Throttled
requests receive plain HTTP 429 Too Many Requests with a Retry-After
header (seconds). Limits are fixed; if a legitimate integration trips this,
fix its credentials rather than retrying harder.
Control-plane writes (post-auth backstop)
Write-side admin RPCs (config.apply, config.patch, plugins.install,
plugins.setEnabled, plugins.uninstall, update.run, worktrees.*,
gateway.restart.request, …) are additionally rate-limited after
authorization: 30 requests per 60 seconds, per method, per
deviceId+clientIp.
This is not a security boundary — callers already hold operator.admin — it
is a backstop that bounds runaway client or agent loops hammering expensive
operations. Interactive use never hits it; each method has its own bucket, so
toggling a plugin does not consume the budget of config writes.
When exceeded, the request fails with a retryable error:
retryAfterMs. The limit is fixed (not configurable);
buckets expire on their own and are pruned by Gateway maintenance.
ACP session creation
The ACP translator caps session creation at 120 new sessions per 10 second window per translator instance. Exceeding it fails the request with an error whose message carries the wait time (there is no structuredretryAfterMs
field on this path):
Restart cooldown
Gateway restart requests coalesce, then enforce a 30 second cooldown between restart cycles. A restart requested during the cooldown is scheduled after it expires rather than rejected. This is separate from the control-plane limiter above:gateway.restart.request consumes a control-plane budget slot and
the resulting restart obeys the cooldown.
Operational notes
- All limiters are in-memory and per-process, and multiple Gateways do not share state. Replacing the Gateway process clears the Gateway-owned counters (auth lockouts, webhook throttle, control-plane buckets). The restart cooldown deliberately survives in-process restart cycles — that is what it throttles — and resets only with the process. The ACP session cap belongs to its translator instance and resets when that instance is recreated, not on Gateway restart.
- Bucket maps are bounded (hard entry caps plus periodic pruning), so unique-key floods cannot grow memory without bound.
- When a client is behind a reverse proxy, the effective IP is the resolved client IP; see trusted proxy auth for how proxy headers are validated before they can influence it.
- Retry signaling varies by surface: Gateway RPC limiters return
retryable: trueplusretryAfterMs, the webhook ingress uses HTTP 429 with aRetry-Afterheader, and ACP embeds the wait in the error message. In every case, back off for the indicated duration instead of retrying immediately.