diff --git a/.changeset/finite-rate-limit-values.md b/.changeset/finite-rate-limit-values.md new file mode 100644 index 00000000..1474980d --- /dev/null +++ b/.changeset/finite-rate-limit-values.md @@ -0,0 +1,5 @@ +--- +"@chatcops/core": patch +--- + +Reject non-finite rate limit values during limiter creation. diff --git a/packages/core/src/limits/rate-limit.ts b/packages/core/src/limits/rate-limit.ts index 2f2aca3f..63e96a3c 100644 --- a/packages/core/src/limits/rate-limit.ts +++ b/packages/core/src/limits/rate-limit.ts @@ -56,8 +56,8 @@ export function createRateLimiter( rule: RateLimitRule, options: RateLimiterOptions = {} ): { check(key: string): Promise } { - if (rule.limit <= 0) throw new Error('limit must be greater than 0'); - if (rule.windowMs <= 0) throw new Error('windowMs must be greater than 0'); + if (!Number.isFinite(rule.limit) || rule.limit <= 0) throw new Error('limit must be a finite number greater than 0'); + if (!Number.isFinite(rule.windowMs) || rule.windowMs <= 0) throw new Error('windowMs must be a finite number greater than 0'); const prefix = options.prefix ?? 'rl'; const now = options.now ?? Date.now; diff --git a/packages/core/tests/limits/rate-limit.test.ts b/packages/core/tests/limits/rate-limit.test.ts index 3626bb36..876440bc 100644 --- a/packages/core/tests/limits/rate-limit.test.ts +++ b/packages/core/tests/limits/rate-limit.test.ts @@ -20,10 +20,14 @@ function countersOf(options?: Parameters[0]): CounterStore describe('createRateLimiter', () => { it('throws on invalid rule', () => { const counters = countersOf(); - expect(() => createRateLimiter(counters, { limit: 0, windowMs: 1000 })).toThrow('limit must be greater than 0'); - expect(() => createRateLimiter(counters, { limit: -1, windowMs: 1000 })).toThrow('limit must be greater than 0'); - expect(() => createRateLimiter(counters, { limit: 5, windowMs: 0 })).toThrow('windowMs must be greater than 0'); - expect(() => createRateLimiter(counters, { limit: 5, windowMs: -1 })).toThrow('windowMs must be greater than 0'); + expect(() => createRateLimiter(counters, { limit: 0, windowMs: 1000 })).toThrow('limit must be a finite number greater than 0'); + expect(() => createRateLimiter(counters, { limit: -1, windowMs: 1000 })).toThrow('limit must be a finite number greater than 0'); + expect(() => createRateLimiter(counters, { limit: Number.NaN, windowMs: 1000 })).toThrow('limit must be a finite number greater than 0'); + expect(() => createRateLimiter(counters, { limit: Number.POSITIVE_INFINITY, windowMs: 1000 })).toThrow('limit must be a finite number greater than 0'); + expect(() => createRateLimiter(counters, { limit: 5, windowMs: 0 })).toThrow('windowMs must be a finite number greater than 0'); + expect(() => createRateLimiter(counters, { limit: 5, windowMs: -1 })).toThrow('windowMs must be a finite number greater than 0'); + expect(() => createRateLimiter(counters, { limit: 5, windowMs: Number.NaN })).toThrow('windowMs must be a finite number greater than 0'); + expect(() => createRateLimiter(counters, { limit: 5, windowMs: Number.POSITIVE_INFINITY })).toThrow('windowMs must be a finite number greater than 0'); }); it('allows exactly limit requests and blocks the next one', async () => {