fix(gateway): clamp auth limiter prune intervals

This commit is contained in:
Peter Steinberger
2026-05-30 18:59:34 -04:00
parent 287f531de6
commit a1d7a7536a
2 changed files with 26 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js";
import {
AUTH_RATE_LIMIT_SCOPE_DEVICE_TOKEN,
AUTH_RATE_LIMIT_SCOPE_HOOK_AUTH,
@@ -238,6 +239,19 @@ describe("auth rate limiter", () => {
}
});
it("clamps oversized positive auto-prune intervals", () => {
vi.useFakeTimers();
try {
const setIntervalSpy = vi.spyOn(globalThis, "setInterval");
limiter = createAuthRateLimiter({ pruneIntervalMs: Number.MAX_SAFE_INTEGER });
expect(setIntervalSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS);
} finally {
vi.useRealTimers();
}
});
// ---------- undefined / empty IP ----------
it("normalizes undefined IP to 'unknown'", () => {

View File

@@ -16,6 +16,7 @@
* {@link createAuthRateLimiter} and pass it where needed.
*/
import { resolveTimerTimeoutMs } from "../shared/number-coercion.js";
import { isLoopbackAddress, resolveClientIp } from "./net.js";
// ---------------------------------------------------------------------------
@@ -96,12 +97,22 @@ export function normalizeRateLimitClientIp(ip: string | undefined): string {
return resolveClientIp({ remoteAddr: ip }) ?? "unknown";
}
function resolvePruneIntervalMs(value: number | undefined): number {
if (value === undefined) {
return PRUNE_INTERVAL_MS;
}
if (Number.isFinite(value) && value <= 0) {
return 0;
}
return resolveTimerTimeoutMs(value, PRUNE_INTERVAL_MS);
}
export function createAuthRateLimiter(config?: RateLimitConfig): AuthRateLimiter {
const maxAttempts = config?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
const windowMs = config?.windowMs ?? DEFAULT_WINDOW_MS;
const lockoutMs = config?.lockoutMs ?? DEFAULT_LOCKOUT_MS;
const exemptLoopback = config?.exemptLoopback ?? true;
const pruneIntervalMs = config?.pruneIntervalMs ?? PRUNE_INTERVAL_MS;
const pruneIntervalMs = resolvePruneIntervalMs(config?.pruneIntervalMs);
const entries = new Map<string, RateLimitEntry>();