Adaptive challenge count#220
Conversation
tiagozip
left a comment
There was a problem hiding this comment.
i don't know if this would work as-is. we prefer having as little DB queries on the /challenge endpoint as possible as well, to reduce the damage of a DoS attack on it.
we also prefer not to store IPs in plain text anywhere, including in Redis. additionally, on per-ip mode, we're storing two keys for every single ip hitting the endpoint. this is reasonable at first but can quickly escalate with a big botnet.
|
Hi and thanks for the feedback. I've reworked the implementation to minimize DB impact. Operations have been reduced to 2 INCR at most (1 per-IP + 1 global), down from 6 in the initial version. The previous design used separate INCR (write) + GET (read) calls: since INCR already returns the current value, the read is now eliminated entirely. EXPIRE is only called once per key per window. When adaptive is disabled (the default), zero DB calls are made. This is the same pattern used by the existing rate-limiter and I don't think it can be reduced further without moving to in-memory counters, which would diverge from the current architecture. Regarding your concern about IP storage: IPs are no longer stored in plain text. Keys now use |
|
sure, it'd be great to have the existing ratelimiter use redis too. we can probably drop it from a few places as well to make requests lighter. |
|
|
||
| When both global and per-IP tiers are configured, the **highest** resulting challenge count is used. The base challenge count (set in the **Main** section) is always the minimum. | ||
|
|
||
| Example configuration: |
| <input type="range" id="cfgObfuscationLevel" min="1" max="10" value="${key.config.obfuscationLevel ?? 5}"> | ||
| </div> | ||
| </div> | ||
| <h3 class="config-section-title" style="margin-top:16px">Adaptive challenge count</h3> |
There was a problem hiding this comment.
i dont like the inline css very much but whatever it's fine
There was a problem hiding this comment.
also, could you share a screenshot of this?
| return { enabled, windowMs, tiers, globalTiers }; | ||
| } | ||
|
|
||
| function tiersEqual(a, b) { |
There was a problem hiding this comment.
it doesn't seem that this function is used anywhere outside of adaptiveConfigEquals. please inline it there.
| } | ||
|
|
||
| function hashIp(ip, siteKey) { | ||
| return createHmac("sha256", siteKey).update(ip).digest("hex").slice(0, 16); |
This PR introduces an adaptive challenge count mechanism that dynamically increases the number of proof-of-work challenges based on request frequency, providing progressive defense before hard rate-limiting kicks in.
Currently, challenge parameters are static per site key. The only defense against high request volume is rate-limiting, which is binary — requests are either allowed or blocked (429). This leaves a gap where an attacker can stay just below the rate limit while still automating solves at scale.
Adaptive challenge count fills this gap by gradually increasing computational cost as request frequency rises, making automation progressively more expensive without blocking legitimate users.