Fix IP spoofing vulnerability in rate limiter by prioritizing request.ip (#132)

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
Ralksta
2026-04-21 22:51:35 +02:00
committed by GitHub
parent 16e1c7647b
commit 13fcfe8436
2 changed files with 50 additions and 1 deletions

View File

@@ -1,5 +1,50 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { checkRateLimit } from '@/lib/rate-limit';
import { NextRequest } from 'next/server';
import { checkRateLimit, getClientIp } from '@/lib/rate-limit';
describe('getClientIp', () => {
it('prioritizes request.ip when present', () => {
const req = {
ip: '1.2.3.4',
headers: new Headers({
'x-real-ip': '5.6.7.8',
'x-forwarded-for': '9.10.11.12',
}),
} as unknown as NextRequest;
expect(getClientIp(req)).toBe('1.2.3.4');
});
it('falls back to x-real-ip if request.ip is missing', () => {
const req = {
headers: new Headers({
'x-real-ip': '5.6.7.8',
'x-forwarded-for': '9.10.11.12',
}),
} as unknown as NextRequest;
expect(getClientIp(req)).toBe('5.6.7.8');
});
it('falls back to x-forwarded-for if request.ip and x-real-ip are missing', () => {
const req = {
headers: new Headers({
'x-forwarded-for': '9.10.11.12, 13.14.15.16',
}),
} as unknown as NextRequest;
// Should take the first IP from x-forwarded-for list
expect(getClientIp(req)).toBe('9.10.11.12');
});
it('returns unknown if no IP headers are present', () => {
const req = {
headers: new Headers(),
} as unknown as NextRequest;
expect(getClientIp(req)).toBe('unknown');
});
});
describe('checkRateLimit', () => {
// Use unique IP prefixes per test to avoid cross-contamination

View File

@@ -12,7 +12,11 @@
import { NextRequest } from 'next/server';
export function getClientIp(request: NextRequest): string {
// @ts-expect-error - Next.js 15 removed request.ip but hosting platforms like Vercel still populate it
const ip = request.ip as string | undefined;
return (
ip ??
request.headers.get('x-real-ip') ??
request.headers.get('x-forwarded-for')?.split(',')[0].trim() ??
'unknown'