Mail Server Rate Limit
Per-IP DoS defenses shared by b.mail.server.mx and b.mail.server.submission. Both listeners boot with sensible defaults; operators tighten or relax per-deployment via the rateLimit opt on either listener.
Defenses:
- **Per-IP concurrent connections** — bounded to maxConcurrentConnectionsPerIp (default 10). A single hostile peer cannot open thousands of TCP slots and starve legitimate senders. Sliding-window kernel-level limits (iptables connlimit, ELB connection cap) are still recommended upstream — this is the framework's own ceiling for when the kernel limit isn't wired.
- **Per-IP connection rate** — bounded to connectionsPerIpPerMinute (default 60). Rapid reconnect / scan attacks tripped here; legitimate retry-with-backoff traffic stays under the cap.
- **Per-IP AUTH-failure budget** — bounded to authFailuresPerIpPer15Min (default 10; submission listener only). Credential-stuffing class — RFC 4954 §6 codes AUTH refusals as 535 5.7.8; we count those per remote IP in a rolling 15-minute window and refuse new AUTH attempts past the cap with 421 4.7.0. The framework's authenticator is unaware of this layer; the rate-limit lives at the wire- protocol boundary so a credential leak past the listener is still bounded.
- **Slow-loris / minBytesPerSecond on DATA** — bounded to minBytesPerSecond (default 100 bytes/sec) during the DATA- body phase. The state machine's idleTimeoutMs already cuts fully-stalled connections; this floor cuts a hostile peer trickling one byte per minute to hold a connection for hours within the idle window.
## What this module is NOT
- **Not an HTTP rate-limiter.** b.middleware.rateLimit covers the HTTP request-response shape; this module covers the SMTP-transactional state machine where rate-limits apply at the connection-boundary + the AUTH command + the DATA byte- rate, not per-request. - **Not a replacement for kernel / proxy-level limits.** This module is the in-process belt; iptables / NFTables / ELB / CloudFlare / haproxy / nginx-stream stay the suspenders. A framework-level limiter sees only what reaches the process; the kernel sees the connection floods before they cost an event-loop tick.
## Wire-up
var rateLimit = b.mail.server.rateLimit.create({
maxConcurrentConnectionsPerIp: 10,
connectionsPerIpPerMinute: 60,
authFailuresPerIpPer15Min: 10,
minBytesPerSecond: 100,
});
var mx = b.mail.server.mx.create({ tlsContext, rateLimit, ... });
The listener calls rateLimit.admitConnection(ip) in the net.createServer callback and refuses new connections with 421 4.7.0 Too many connections when the verdict is no. AUTH- failure budgeting (noteAuthFailure + checkAuthAdmit) is wired in the submission listener's AUTH handler. The slow-loris defense is wired in the DATA-body collector.
b.mail.server.rateLimit.create(opts?) #
{
maxConcurrentConnectionsPerIp: number, // default 10
connectionsPerIpPerMinute: number, // default 60
authFailuresPerIpPer15Min: number, // default 10
minBytesPerSecond: number, // default 100 (DATA-body slow-loris floor)
rcptFailuresPerIpPerMinute: number, // default 50 (RCPT 550 enumeration bound)
disabled: boolean, // default false — test escape hatch
}
Build a rate-limit handle. The listeners compose this internally with the framework defaults; operators override caps by passing their own rateLimit opt to b.mail.server.mx.create or b.mail.server.submission.create. Direct construction is for operators sharing one budget across multiple listeners (e.g. an MX + a submission server on the same IP space).
var rl = b.mail.server.rateLimit.create({
maxConcurrentConnectionsPerIp: 5,
connectionsPerIpPerMinute: 30,
});
var ok = rl.admitConnection("192.0.2.1");
// → { ok: true } or { ok: false, reason: "concurrent-per-ip" | "rate-per-ip" }
b.mail.server.rateLimit.resolve(spec) #
Resolve a rate-limit spec into a limiter. false disables limiting (a disabled limiter that always admits), an already-built limiter — one exposing admitConnection — passes through unchanged, and anything else is treated as create() options. Every mail server (IMAP / POP3 / SMTP MX / Submission / ManageSieve) composes this at the top of its create() so the spec contract is identical across protocols.
var b = require("blamejs");
var rl = b.mail.server.rateLimit.resolve(false); // disabled
// → a limiter whose admitConnection always admits
Last updated 2026-08-08T16:39:49.652Z by seeder.