🛡️ Sentinel: [CRITICAL] Fix rate limit DoS vulnerability (#136)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
@@ -103,7 +103,7 @@ describe('checkRateLimit', () => {
|
||||
expect(result.resetAt).toBeLessThanOrEqual(before + 61_000);
|
||||
});
|
||||
|
||||
it('does not crash when store is at capacity and prevents rate limit bypasses', () => {
|
||||
it('does not crash when store is at capacity and allows new requests by evicting oldest entries', () => {
|
||||
// Fill the store beyond MAX_STORE_ENTRIES (10_000 in prod)
|
||||
// by hammering with unique IPs.
|
||||
const uniqueIps = Array.from({ length: 10005 }, (_, i) => `evict-test-ip-${i}-${Date.now()}`);
|
||||
@@ -111,10 +111,10 @@ describe('checkRateLimit', () => {
|
||||
checkRateLimit(ip, 5);
|
||||
}
|
||||
|
||||
// A brand-new IP must be blocked to prevent spoofing-based cache floods
|
||||
// A brand-new IP must be allowed by evicting the oldest entry
|
||||
const freshIp = `evict-fresh-${Date.now()}`;
|
||||
const result = checkRateLimit(freshIp, 5);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.remaining).toBe(0);
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.remaining).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,10 +67,12 @@ export function checkRateLimit(
|
||||
// New window or expired
|
||||
if (!entry || now > entry.expiresAt) {
|
||||
if (store.size >= MAX_STORE_ENTRIES) {
|
||||
// Store is full. Prevent adding new entries to mitigate cache flooding
|
||||
// where an attacker spams spoofed IPs to evict legitimate users.
|
||||
// Deny new unrecognized IPs to fail securely during an active flood.
|
||||
return { success: false, remaining: 0, resetAt: now + windowMs };
|
||||
// Store is full. Evict the oldest entry to prevent cache flooding
|
||||
// DOS attacks, which could otherwise block all legitimate users.
|
||||
const oldestKey = store.keys().next().value;
|
||||
if (oldestKey !== undefined) {
|
||||
store.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
const resetAt = now + windowMs;
|
||||
store.set(key, { count: 1, expiresAt: resetAt });
|
||||
|
||||
Reference in New Issue
Block a user