From 39b34556e8e6834096b586834d8e231bc18330f5 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 1 Aug 2026 08:16:44 +0000 Subject: [PATCH] Fix race condition in SlidingWindow rate limiter Co-authored-by: Sanan507 <227714367+Sanan507@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ .../java/com/algorithmrace/visualizer/RateLimitFilter.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..8ce91c0 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2024-05-18 - Race Condition in Sliding Window Rate Limiter +**Vulnerability:** A race condition in `RateLimitFilter.java` within the `SlidingWindow` class's `tryAcquire` method allowed multiple concurrent requests to bypass the rate limit if they hit the server simultaneously, because `timestamps.size() >= limit` check and `timestamps.addLast(now)` were not atomic. +**Learning:** Even when using thread-safe collections like `ConcurrentLinkedDeque`, composite operations (like checking size then adding an element) are not atomic and require synchronization if they must be executed as a single atomic unit to enforce business rules (like rate limits). +**Prevention:** Always synchronize composite operations on collections in a multi-threaded environment, or use atomic variables/constructs designed for the specific composite operation needed (e.g., atomic counters or higher-level rate limiting libraries like Resilience4j). diff --git a/backend/src/main/java/com/algorithmrace/visualizer/RateLimitFilter.java b/backend/src/main/java/com/algorithmrace/visualizer/RateLimitFilter.java index b694c2a..aa0d2f3 100644 --- a/backend/src/main/java/com/algorithmrace/visualizer/RateLimitFilter.java +++ b/backend/src/main/java/com/algorithmrace/visualizer/RateLimitFilter.java @@ -110,7 +110,7 @@ private static class SlidingWindow { private final java.util.Deque timestamps = new java.util.concurrent.ConcurrentLinkedDeque<>(); - boolean tryAcquire(int limit) { + synchronized boolean tryAcquire(int limit) { long now = System.currentTimeMillis(); long cutoff = now - WINDOW_MS;