From 4900ecb19ab9c6d279c8771b834fcb29e526a1e0 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 29 Sep 2026 08:37:39 -0700 Subject: logging: throttle log lines that clients can trigger at will Evicted-JWT replays, blocked sends from frozen or limited accounts and failed payment retries each logged one line per request, so a looping client could flood the log, and an evicted token replayed across a proxy pool made atk-autoban.sh block every IP it saw. Add quiet()/suppressed() in lib/logging.ts: the first occurrence per key logs as before, repeats within the window are counted and reported on the next line that gets through. The evicted-auth summary line is worded so the auto-banner ignores it. Co-Authored-By: Claude Fable 5.1 --- lib/logging.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'lib/logging.ts') diff --git a/lib/logging.ts b/lib/logging.ts index c68a3199..af6ae942 100644 --- a/lib/logging.ts +++ b/lib/logging.ts @@ -23,6 +23,27 @@ export const shortError = (msg: any, max = 400): string => { return s.length > max ? `${s.slice(0, max)}…` : s; }; +// Per-key log throttle for lines an attacker can emit at will (evicted-JWT +// replays from a proxy pool, frozen-account send attempts, lnurl scans of +// missing usernames). Returns null while the key is inside its quiet window, +// otherwise the number of calls suppressed since the last emitted line — so +// the first occurrence always logs, and the next one after the window carries +// the suppressed count. In-memory: a restart simply logs again. +const quietUntil = new Map(); +export const quiet = (key: string, ms = 10 * 60 * 1000): number | null => { + const now = Date.now(); + const q = quietUntil.get(key); + if (q && q.until > now) { + q.dropped++; + return null; + } + if (quietUntil.size > 10000) quietUntil.clear(); + quietUntil.set(key, { until: now + ms, dropped: 0 }); + return q?.dropped ?? 0; +}; +export const suppressed = (n: number | null) => + n ? `(+${n} similar suppressed)` : ""; + export const line = () => { const stack = new Error().stack; const stackLine = stack.split("\n")[1]; -- cgit v1.2.3