Filed by the auto-audit routine. Dimension: dx. Severity: low.
What
RateLimit.perMinute(0) (and perHour(0), perDay(0), per(0, ms)) creates a rate limiter where every request is rejected with 429. The check at index.ts:511 is if (count > opts.max) - after the first increment() call, count is always at least 1, so 1 > 0 is immediately true on every request. There is no input guard, no validation error, and no documentation warning. The test suite intentionally uses RateLimit.perMinute(0) as a fixture to force blocking (lines 480, 659, 665, 679), which shows the behavior is understood internally, but it is not surfaced to users.
Where
packages/middleware/src/index.ts:511 (if (count > opts.max))
packages/middleware/src/index.ts:578 (RateLimit.perMinute factory, no guard on max)
Why it matters
A developer who reads max as "maximum simultaneous connections" or who initializes it from a config value that can be 0 ("disabled" in their mental model) will lock out all users with no diagnostic output. The fail-open behavior when the cache is missing (by design, with a warning) is the opposite of max: 0 failing closed silently. The asymmetry is confusing.
Suggested direction
Add a RangeError guard in buildRateLimit(): if (opts.max <= 0) throw new RangeError('RateLimit: max must be a positive integer, got ' + opts.max). Alternatively, if max: 0 is intentionally supported as a "block all" mode (useful in feature-flag contexts), document it explicitly in the JSDoc for perMinute(n) and add a note in the pitfalls section of docs/guide/rate-limiting.md.
audit:auto - package @rudderjs/middleware - cycle 2026-06-23
What
RateLimit.perMinute(0)(andperHour(0),perDay(0),per(0, ms)) creates a rate limiter where every request is rejected with 429. The check atindex.ts:511isif (count > opts.max)- after the firstincrement()call,countis always at least 1, so1 > 0is immediately true on every request. There is no input guard, no validation error, and no documentation warning. The test suite intentionally usesRateLimit.perMinute(0)as a fixture to force blocking (lines 480, 659, 665, 679), which shows the behavior is understood internally, but it is not surfaced to users.Where
packages/middleware/src/index.ts:511(if (count > opts.max))packages/middleware/src/index.ts:578(RateLimit.perMinutefactory, no guard onmax)Why it matters
A developer who reads
maxas "maximum simultaneous connections" or who initializes it from a config value that can be0("disabled" in their mental model) will lock out all users with no diagnostic output. The fail-open behavior when the cache is missing (by design, with a warning) is the opposite ofmax: 0failing closed silently. The asymmetry is confusing.Suggested direction
Add a
RangeErrorguard inbuildRateLimit():if (opts.max <= 0) throw new RangeError('RateLimit: max must be a positive integer, got ' + opts.max). Alternatively, ifmax: 0is intentionally supported as a "block all" mode (useful in feature-flag contexts), document it explicitly in the JSDoc forperMinute(n)and add a note in the pitfalls section ofdocs/guide/rate-limiting.md.audit:auto - package @rudderjs/middleware - cycle 2026-06-23