fix(gateway): prune expired entries instead of clearing all hook auth failure state (#15848)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 188a40e8a3
Co-authored-by: AI-Reviewer-QS <255312808+AI-Reviewer-QS@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
This commit is contained in:
AI-Reviewer-QS
2026-02-14 08:46:12 +08:00
committed by GitHub
parent 67b5c093b5
commit 28431b84cc
4 changed files with 132 additions and 11 deletions

View File

@@ -207,13 +207,34 @@ export function createHooksRequestHandler(
nowMs: number,
): { throttled: boolean; retryAfterSeconds?: number } => {
if (!hookAuthFailures.has(clientKey) && hookAuthFailures.size >= HOOK_AUTH_FAILURE_TRACK_MAX) {
hookAuthFailures.clear();
// Prune expired entries instead of clearing all state.
for (const [key, entry] of hookAuthFailures) {
if (nowMs - entry.windowStartedAtMs >= HOOK_AUTH_FAILURE_WINDOW_MS) {
hookAuthFailures.delete(key);
}
}
// If still at capacity after pruning, drop the oldest half.
if (hookAuthFailures.size >= HOOK_AUTH_FAILURE_TRACK_MAX) {
let toRemove = Math.floor(hookAuthFailures.size / 2);
for (const key of hookAuthFailures.keys()) {
if (toRemove <= 0) {
break;
}
hookAuthFailures.delete(key);
toRemove--;
}
}
}
const current = hookAuthFailures.get(clientKey);
const expired = !current || nowMs - current.windowStartedAtMs >= HOOK_AUTH_FAILURE_WINDOW_MS;
const next: HookAuthFailure = expired
? { count: 1, windowStartedAtMs: nowMs }
: { count: current.count + 1, windowStartedAtMs: current.windowStartedAtMs };
// Delete-before-set refreshes Map insertion order so recently-active
// clients are not evicted before dormant ones during oldest-half eviction.
if (hookAuthFailures.has(clientKey)) {
hookAuthFailures.delete(clientKey);
}
hookAuthFailures.set(clientKey, next);
if (next.count <= HOOK_AUTH_FAILURE_LIMIT) {
return { throttled: false };