From c1a48cf110beeb9fc51bab9201f88c2112b49300 Mon Sep 17 00:00:00 2001 From: Esc1200 Date: Sun, 7 Jun 2026 18:36:11 -0500 Subject: [PATCH] fix(security): use trusted IP source for rate limiter client key The rate limiter in hostedUiAuthClientKey() prioritized X-Forwarded-For (first entry) over X-Real-IP. Since X-Forwarded-For is set by the client on the first hop, an attacker can spoof it to bypass rate limiting by rotating arbitrary client identifiers. Fix: - Prefer x-real-ip (set by the trusted reverse proxy) as the primary key - If falling back to x-forwarded-for, use the LAST entry (rightmost) which is set by the trusted proxy closest to the server, not the first entry which is client-controllable - Maintain 'unknown' fallback for missing headers --- src/lib/server/hosted-ui-auth.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/server/hosted-ui-auth.ts b/src/lib/server/hosted-ui-auth.ts index 9954500b..6a1e3289 100644 --- a/src/lib/server/hosted-ui-auth.ts +++ b/src/lib/server/hosted-ui-auth.ts @@ -184,8 +184,16 @@ export function hostedUiWorkspaceId(env: HostedUiAuthEnv): string | null { } export function hostedUiAuthClientKey(request: Request): string { - const forwardedFor = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim(); - return forwardedFor || request.headers.get('x-real-ip')?.trim() || 'unknown'; + // Prefer x-real-ip (set by trusted reverse proxy) over x-forwarded-for. + // x-forwarded-for first entry is client-controllable; use last entry (set by trusted proxy). + const realIp = request.headers.get('x-real-ip')?.trim(); + if (realIp) return realIp; + const forwardedFor = request.headers.get('x-forwarded-for'); + if (forwardedFor) { + const parts = forwardedFor.split(',').map(p => p.trim()).filter(Boolean); + if (parts.length > 0) return parts[parts.length - 1]; + } + return 'unknown'; } export function hostedUiAuthRateLimitStatus(clientKey: string, now = Date.now()): {