Skip to content

Commit 82e8b9d

Browse files
authored
Merge pull request #564 from arumes31/jules-11679132576181270106-84432f98
🛡️ Sentinel: [HIGH] Fix body parsing in fast-path rate limits
2 parents 067af1f + 97a6a3b commit 82e8b9d

2 files changed

Lines changed: 8 additions & 4 deletions

File tree

.jules/sentinel.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,7 @@
7272
**Vulnerability:** DOM-based XSS vulnerability due to unescaped interpolations in custom vanilla JS modal `rauthDialog`. Both user-facing dialog messages and template variables were vulnerable to injection via `innerHTML`.
7373
**Learning:** Developers building custom UI components (like dialog overlays) in vanilla JS sometimes use template literals + `innerHTML` directly without properly escaping inputs first, treating them like React props which inherently escape.
7474
**Prevention:** Establish a global or reusable `escapeHtml` utility and mandate its usage for all dynamic values being interpolated into HTML template literals prior to assignment to `innerHTML`.
75+
## 2024-05-31 - Fast-Path Rate Limiting Missing c.FormValue Exhaustion Fix
76+
**Vulnerability:** A fast-path rate limit implementation attempting to avoid parsing `c.FormValue()` (which reads the request body and can cause exhaustion DoS with large payloads) was still indirectly parsing the form by using a global `getRD()` helper that called `c.FormValue()`.
77+
**Learning:** When attempting to implement early fast-paths to bypass body parsing on limited connections, all helper functions utilized in the error response must be audited. Functions that conditionally fall back to form parsing (`getRD` via `c.FormValue`) will silently defeat the fast-path check, rendering the protection ineffective against resource exhaustion.
78+
**Prevention:** In fast-path error handlers that aim to avoid parsing large bodies (like early rate limits), strictly use `c.QueryParam()` instead of shared helper functions that may fall back to parsing `c.FormValue()`.

internal/handlers/auth.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ func (h *AuthHandler) Login(c echo.Context) error {
288288
// Fast-path read-only check to prevent large payload parsing if already throttled.
289289
if core.IsRateLimitExceeded("login_access:"+clientIP, h.Cfg.RateLimitLoginAccessMax) {
290290
slog.Warn("General login access rate limit exceeded (fast-path)", "ip", clientIP)
291-
return c.Render(http.StatusTooManyRequests, "login.html", map[string]interface{}{"error": "Too many requests. Please wait a minute.", "csrf": c.Get("csrf"), "rd": getRD(c)})
291+
return c.Render(http.StatusTooManyRequests, "login.html", map[string]interface{}{"error": "Too many requests. Please wait a minute.", "csrf": c.Get("csrf"), "rd": c.QueryParam("rd")})
292292
}
293293

294294
// Delegate 2FA verification before consuming this handler's rate-limit
@@ -433,7 +433,7 @@ func (h *AuthHandler) initiate2FASetupSession(c echo.Context, username string) e
433433

434434
func (h *AuthHandler) checkLoginIPRateLimits(c echo.Context, clientIP, template string, extraContext map[string]interface{}) error {
435435
if core.IsRateLimitExceeded("login_access:"+clientIP, h.Cfg.RateLimitLoginAccessMax) {
436-
ctx := map[string]interface{}{"error": "Too many requests. Please wait a minute.", "csrf": c.Get("csrf"), "rd": getRD(c)}
436+
ctx := map[string]interface{}{"error": "Too many requests. Please wait a minute.", "csrf": c.Get("csrf"), "rd": c.QueryParam("rd")}
437437
for k, v := range extraContext {
438438
ctx[k] = v
439439
}
@@ -543,7 +543,7 @@ func (h *AuthHandler) Verify2FA(c echo.Context) error {
543543
}
544544

545545
if core.IsRateLimitExceeded("2fa_fail_user:"+username, h.Cfg.RateLimitLoginFailUserMax) {
546-
return c.Render(http.StatusTooManyRequests, "login.html", map[string]interface{}{"error": "Too many failed attempts. Please try again later.", "csrf": c.Get("csrf"), "display2fa": true, "rd": getRD(c)})
546+
return c.Render(http.StatusTooManyRequests, "login.html", map[string]interface{}{"error": "Too many failed attempts. Please try again later.", "csrf": c.Get("csrf"), "display2fa": true, "rd": c.QueryParam("rd")})
547547
}
548548

549549
totpOK := totp.Validate(code, secret)
@@ -613,7 +613,7 @@ func (h *AuthHandler) CompleteSetup2FA(c echo.Context) error {
613613
}
614614

615615
if core.IsRateLimitExceeded("2fa_fail_user:"+username, h.Cfg.RateLimitLoginFailUserMax) {
616-
return c.Render(http.StatusTooManyRequests, "setup_2fa.html", map[string]interface{}{"error": "Too many failed attempts. Please try again later.", "csrf": c.Get("csrf"), "rd": getRD(c)})
616+
return c.Render(http.StatusTooManyRequests, "setup_2fa.html", map[string]interface{}{"error": "Too many failed attempts. Please try again later.", "csrf": c.Get("csrf"), "rd": c.QueryParam("rd")})
617617
}
618618

619619
code := c.FormValue("totp_code")

0 commit comments

Comments
 (0)