WebSocket and login safeguards
UsewsRateLimit()on upgrades,loginThrottle()on failed authentication, androtateSession()after login or a privilege change.
These focused safeguards cover authentication entry points, upload boundaries, and WebSocket upgrades with first-party helpers instead of copy-pasted local policy.
1. wsRateLimit()
wsRateLimit() adapts the existing rateLimit() shared-bucket primitive to the WebSocket upgrade boundary. Put the same groupId on HTTP login routes and the WebSocket session route so an attacker cannot dodge the bucket by switching transports.
- 01requestAttackerloginThrottle()Brute-force POST /login attemptseach attempt spends from groupId: auth-entry
- 02asyncloginThrottle()Shared bucketIncrement the same keyed counterwindowMs / max enforced
- 03requestAttackerwsRateLimit()Switch transports: WebSocket upgradebeforeUpgrade on /session
- 04asyncwsRateLimit()Shared bucketSpends from the SAME groupId bucketno fresh budget for switching transport
- 05noteShared bucketAttackerLimit exhausted, both paths rejectHTTP 429 / upgrade refused
2. loginThrottle()
loginThrottle() is the built-in preset for credential-entry routes. It combines a shared hard limit with a short progressive delay before the hard 429 response. By default it does not trust proxy IP headers; pass a keyGenerator or opt in to trustProxyHeaders: true / trustedProxies only behind a trusted proxy. When proxy headers are trusted, the key is the rightmost X-Forwarded-For entry (the one your proxy appended), so rotating spoofed left entries cannot reset the budget; multi-hop chains declare their length with trustedHops. Prefer trustedProxies when the origin can be reached without the proxy (see the autoBan note).
3. rotateSession()
rotateSession() watches session privilege fields and calls session.regenerate() after the handler when those fields change. It skips itself when the handler already regenerated the session, so explicit login flows keep their exact behavior.
4. Upload MIME and magic-byte guards
fileField() already enforced maxBytes and MIME allowlists. Add magicBytes: true to derive known signatures from accept, or pass custom signatures for private formats. The OpenAPI generator emits x-magic-bytes alongside x-accept and x-max-bytes.
5. requirePayloadAuth
OpenAPI security scheme builders accept requirePayloadAuth: true for schemes such as webhook signatures that must authenticate the request body. A route using that scheme cannot set auth.payload: false; Daloy throws at route registration. The public OpenAPI document uses x-daloy-require-payload-auth rather than leaking a non-spec field.
6. WebSocket safe defaults
app.ws() now normalizes safe runtime defaults for Node and Bun: close on excessive outbound backpressure, a 1 MiB backpressure limit, compression off by default, a non-zero idle timeout, and a 1 MiB inbound payload cap. In production under secureDefaults, perMessageDeflate: true is refused. Daloy also refuses a maxPayloadLength larger than a route body schema's declared maximum when the schema exposes one.