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?) #

stable0.9.47
{
  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) #

stable0.15.13

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.