fix(security): close the case-sensitivity bypass in the API rate limiter

Express's `case sensitive routing` is off by default, so /API/admin/events
reaches the same handler as /api/admin/events. Both the gate's `/api/` prefix
test and rateLimitService's public-endpoint classification compared the raw
path, so simply upper-casing a letter skipped the limiter entirely.

Verified against a real Express app before fixing: /api/admin/events routes
and hits the gate; /API/admin/events and /Api/Admin/Events route and miss it.

Both now match on a lower-cased path. The auth gate added alongside was
already immune -- its patterns carry the `i` flag for exactly this reason.

Not changed: rateLimitSecurity.hasValidAdminToken's /api/admin/ test has the
same shape, but there the case-sensitive comparison fails safe -- an
upper-cased path simply does not get the admin skip, so it is rate limited
rather than exempted. Making it case-insensitive would widen a skip, so it is
left alone. maintenance.js's isAdminRoute is fail-safe for the same reason.
This commit is contained in:
Paul Nothaft
2026-09-02 09:43:11 +02:00
parent 50e8ed6e58
commit a929affd7e
3 changed files with 22 additions and 5 deletions
+5 -2
View File
@@ -138,8 +138,11 @@ function shouldSkipRateLimit(req, config) {
// Check if we only rate limit public endpoints
if (config.publicEndpointsOnly) {
const isPublicEndpoint = req.path.startsWith('/api/public/') ||
req.path.startsWith('/api/gallery/') ||
// Lower-cased: Express routing is case-insensitive by default, so an
// upper-cased path reaches the same handler and must classify the same way.
const lowerPath = req.path.toLowerCase();
const isPublicEndpoint = lowerPath.startsWith('/api/public/') ||
lowerPath.startsWith('/api/gallery/') ||
isAuthEndpoint;
return !isPublicEndpoint;
}