feat(security): add per-IP rate limiting on guest-order endpoints (#19) #29

Open
hermes wants to merge 1 commit from fix/guest-order-rate-limiting into master
3 changed files with 140 additions and 2 deletions
Showing only changes of commit 68cb20edba - Show all commits

View file

@ -15,6 +15,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import se.bilhalsning.dto.ErrorResponse; import se.bilhalsning.dto.ErrorResponse;
import se.bilhalsning.security.GuestOrderRateLimitFilter;
import se.bilhalsning.security.JwtAuthenticationFilter; import se.bilhalsning.security.JwtAuthenticationFilter;
import se.bilhalsning.security.JwtService; import se.bilhalsning.security.JwtService;
@ -41,7 +42,8 @@ public class SecurityConfig {
@Bean @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, public SecurityFilterChain securityFilterChain(HttpSecurity http,
JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception { JwtAuthenticationFilter jwtAuthenticationFilter,
GuestOrderRateLimitFilter guestOrderRateLimitFilter) throws Exception {
http http
.csrf(csrf -> csrf.disable()) .csrf(csrf -> csrf.disable())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
@ -64,6 +66,7 @@ public class SecurityConfig {
writeError(response, HttpStatus.UNAUTHORIZED, UNAUTHENTICATED_MESSAGE)) writeError(response, HttpStatus.UNAUTHORIZED, UNAUTHENTICATED_MESSAGE))
.accessDeniedHandler((request, response, ex) -> .accessDeniedHandler((request, response, ex) ->
writeError(response, HttpStatus.FORBIDDEN, FORBIDDEN_MESSAGE))) writeError(response, HttpStatus.FORBIDDEN, FORBIDDEN_MESSAGE)))
.addFilterBefore(guestOrderRateLimitFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();

View file

@ -0,0 +1,123 @@
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
package se.bilhalsning.security;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import jakarta.servlet.FilterChain;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import jakarta.servlet.ServletException;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import jakarta.servlet.http.HttpServletRequest;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import jakarta.servlet.http.HttpServletResponse;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import java.io.IOException;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import java.time.Instant;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import java.util.ArrayDeque;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import java.util.Deque;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import java.util.concurrent.ConcurrentHashMap;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import org.springframework.beans.factory.annotation.Value;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import org.springframework.http.HttpStatus;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import org.springframework.http.MediaType;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import org.springframework.stereotype.Component;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
import org.springframework.web.filter.OncePerRequestFilter;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
/**
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* In-memory per-IP rate limiter for public guest-order endpoints.
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
*
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* <p>Uses a sliding-window log algorithm: for each client IP, stores request
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* timestamps in a deque. On each request, timestamps older than the window
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* are pruned. If the remaining count exceeds the limit, the request is
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* rejected with HTTP 429.
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
*
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* <p>This is a Phase 0 interim measure. It is not suitable for multi-instance
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* deployments (state is per-JVM) and resets on restart. For production, use
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* Bucket4j with a Redis backing or a reverse proxy (nginx {@code limit_req}).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
*
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* <p>Configurable via application properties:
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* <ul>
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* <li>{@code app.rate-limit.guest-create} POST /api/guest-orders (default 5/min)</li>
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* <li>{@code app.rate-limit.guest-default} all other /api/guest-orders/** (default 20/min)</li>
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* </ul>
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
*/
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
@Component
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
public class GuestOrderRateLimitFilter extends OncePerRequestFilter {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
@Value("${app.rate-limit.guest-create:5}")
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
private int createLimit;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
@Value("${app.rate-limit.guest-default:20}")
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
private int defaultLimit;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
private static final long WINDOW_SECONDS = 60;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
private final ConcurrentHashMap<String, Deque<Instant>> store = new ConcurrentHashMap<>();
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
@Override
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
protected void doFilterInternal(
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
HttpServletRequest request,
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
HttpServletResponse response,
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
FilterChain filterChain) throws ServletException, IOException {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
String path = request.getRequestURI();
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
if (!path.startsWith("/api/guest-orders")) {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
filterChain.doFilter(request, response);
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
return;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
String ip = extractClientIp(request);
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
String key = ip + ":" + (isCreatePath(path) ? "create" : "default");
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
int limit = isCreatePath(path) ? createLimit : defaultLimit;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
if (isRateLimited(key, limit)) {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
response.setCharacterEncoding("UTF-8");
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
response.getWriter().write(
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
"{\"message\":\"För många förfrågningar. Försök igen om en minut.\"}");
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
return;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
filterChain.doFilter(request, response);
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
/**
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* Check and record a request. Returns true if the request should be
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* rejected (rate limit exceeded), false otherwise.
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
*/
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
private boolean isRateLimited(String key, int limit) {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Instant now = Instant.now();
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Instant cutoff = now.minusSeconds(WINDOW_SECONDS);
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Deque<Instant> timestamps = store.computeIfAbsent(key, k -> new ArrayDeque<>());
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
synchronized (timestamps) {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
while (!timestamps.isEmpty() && timestamps.peekFirst().isBefore(cutoff)) {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
timestamps.pollFirst();
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
if (timestamps.size() >= limit) {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
return true;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
timestamps.addLast(now);
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
return false;
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
private boolean isCreatePath(String path) {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
return path.equals("/api/guest-orders");
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
private String extractClientIp(HttpServletRequest request) {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
String forwarded = request.getHeader("X-Forwarded-For");
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
if (forwarded != null && !forwarded.isBlank()) {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
return forwarded.split(",")[0].trim();
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
String realIp = request.getHeader("X-Real-IP");
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
if (realIp != null && !realIp.isBlank()) {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
return realIp.trim();
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
return request.getRemoteAddr();
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
/**
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
* Reset the rate limit state. For testing only.
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
*/
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
public void reset() {
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
store.clear();
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).
}
Review

🟡 Unbounded map growth. Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine maximumSize).

🟡 **Unbounded map growth.** Stale IP keys (pruned deques) are never removed from the map. With X-Forwarded-For spoofing, an attacker can cause unbounded memory growth by sending unique header values. Consider a scheduled cleanup task or a bounded cache (Caffeine `maximumSize`).
Review

💡 Add Retry-After header. response.setHeader("Retry-After", "60") is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.

💡 **Add `Retry-After` header.** `response.setHeader("Retry-After", "60")` is trivial and helps well-behaved clients back off. RFC 6585 recommends it for 429 responses.
Review

💡 Use the existing ErrorResponse DTO. The codebase has ErrorResponse(String message) and SecurityConfig uses objectMapper.writeValueAsString(new ErrorResponse(message)) for all error responses. Inject ObjectMapper and follow the same pattern to avoid hardcoded JSON and keep responses consistent.

💡 **Use the existing `ErrorResponse` DTO.** The codebase has `ErrorResponse(String message)` and SecurityConfig uses `objectMapper.writeValueAsString(new ErrorResponse(message))` for all error responses. Inject `ObjectMapper` and follow the same pattern to avoid hardcoded JSON and keep responses consistent.
Review

🟡 X-Forwarded-For is trusted unconditionally. If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like app.rate-limit.trust-forwarded-headers (default false).

🟡 **X-Forwarded-For is trusted unconditionally.** If the app is directly reachable (no stripping reverse proxy), an attacker can rotate this header per request to bypass rate limiting entirely. Consider a config flag like `app.rate-limit.trust-forwarded-headers` (default `false`).

View file

@ -23,7 +23,11 @@ import se.bilhalsning.service.UserService;
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
@TestPropertySource(properties = "app.jwt.secret=this-is-a-test-secret-that-is-at-least-32-bytes-long!!") @TestPropertySource(properties = {
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
"app.jwt.secret=this-is-a-test-secret-that-is-at-least-32-bytes-long!!",
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
"app.rate-limit.guest-create=1000",
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
"app.rate-limit.guest-default=1000"
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
})
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
class GuestOrderControllerTest { class GuestOrderControllerTest {
@Autowired @Autowired
@ -35,6 +39,14 @@ class GuestOrderControllerTest {
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
@MockitoBean @MockitoBean
private UserService userService; private UserService userService;
@Autowired
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
private se.bilhalsning.security.GuestOrderRateLimitFilter rateLimitFilter;
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
@org.junit.jupiter.api.BeforeEach
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
void resetRateLimit() {
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
rateLimitFilter.reset();
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
}
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
// --- POST /api/guest-orders (create) --- // --- POST /api/guest-orders (create) ---
@Test @Test

Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.
Review

💡 The rate limiter is wired in but never actually tested. No test fires createLimit + 1 requests to verify a 429 is returned. For a security control, consider a dedicated GuestOrderRateLimitFilterTest with low limits that exercises the actual rejection path.

💡 The rate limiter is wired in but never actually tested. No test fires `createLimit + 1` requests to verify a 429 is returned. For a security control, consider a dedicated `GuestOrderRateLimitFilterTest` with low limits that exercises the actual rejection path.