fix(security): use getClientAddress() instead of spoofable headers for IP
- commit
- 1a6a56760d3556c92df7c2781ac13dab5b18034f
- author
- Kit · · 2026-02-16 11:01 UTC
- parent
- 0ff6d73
- signature
- SSH Unverified
SHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6TkThe key is unknown
2 files · +5 -8
M src/routes/api/register/+server.ts +2 -2
| @@ -25,7 +25,7 @@ function cleanupOldCaptchaAttempts() { | |||
| 25 | 25 | } | |
| 26 | 26 | } | |
| 27 | 27 | ||
| 28 | - | export const POST: RequestHandler = async ({ request }) => { | |
| 28 | + | export const POST: RequestHandler = async ({ request, getClientAddress }) => { | |
| 29 | 29 | log.debug('New registration attempt received'); | |
| 30 | 30 | ||
| 31 | 31 | // Lazy cleanup on each request (replaces setInterval for Cloudflare compatibility) | |
| @@ -39,7 +39,7 @@ export const POST: RequestHandler = async ({ request }) => { | |||
| 39 | 39 | } | |
| 40 | 40 | ||
| 41 | 41 | // Get the IP address for rate limiting and Turnstile validation | |
| 42 | - | const ip = request.headers.get('x-forwarded-for') || 'unknown'; | |
| 42 | + | const ip = getClientAddress(); | |
| 43 | 43 | const maskedIp = ip | |
| 44 | 44 | .split('.') | |
| 45 | 45 | .map((octet, idx) => (idx < 3 ? 'xxx' : octet)) | |
| @@ -25,7 +25,7 @@ function cleanupOldCaptchaAttempts() { | |||||
| 25 | } | 25 | } | ||
| 26 | } | 26 | } | ||
| 27 | 27 | ||||
| 28 | - | export const POST: RequestHandler = async ({ request }) => { | 28 | + | export const POST: RequestHandler = async ({ request, getClientAddress }) => { |
| 29 | log.debug('New registration attempt received'); | 29 | log.debug('New registration attempt received'); | ||
| 30 | 30 | ||||
| 31 | // Lazy cleanup on each request (replaces setInterval for Cloudflare compatibility) | 31 | // Lazy cleanup on each request (replaces setInterval for Cloudflare compatibility) | ||
| @@ -39,7 +39,7 @@ export const POST: RequestHandler = async ({ request }) => { | |||||
| 39 | } | 39 | } | ||
| 40 | 40 | ||||
| 41 | // Get the IP address for rate limiting and Turnstile validation | 41 | // Get the IP address for rate limiting and Turnstile validation | ||
| 42 | - | const ip = request.headers.get('x-forwarded-for') || 'unknown'; | 42 | + | const ip = getClientAddress(); |
| 43 | const maskedIp = ip | 43 | const maskedIp = ip | ||
| 44 | .split('.') | 44 | .split('.') | ||
| 45 | .map((octet, idx) => (idx < 3 ? 'xxx' : octet)) | 45 | .map((octet, idx) => (idx < 3 ? 'xxx' : octet)) | ||
M src/routes/api/session/login/+server.ts +3 -6
| @@ -31,7 +31,7 @@ function cleanupOldAttempts() { | |||
| 31 | 31 | } | |
| 32 | 32 | } | |
| 33 | 33 | ||
| 34 | - | export const POST: RequestHandler = async ({ request, cookies }) => { | |
| 34 | + | export const POST: RequestHandler = async ({ request, cookies, getClientAddress }) => { | |
| 35 | 35 | log.debug('New login attempt received'); | |
| 36 | 36 | ||
| 37 | 37 | // Lazy cleanup on each request (replaces setInterval for Cloudflare compatibility) | |
| @@ -44,11 +44,8 @@ export const POST: RequestHandler = async ({ request, cookies }) => { | |||
| 44 | 44 | }); | |
| 45 | 45 | } | |
| 46 | 46 | ||
| 47 | - | // Get the IP address for rate limiting and Turnstile validation | |
| 48 | - | // Prefer Cloudflare's CF-Connecting-IP header, then x-forwarded-for (first IP), then fallback | |
| 49 | - | const cfConnectingIp = request.headers.get('cf-connecting-ip'); | |
| 50 | - | const forwardedFor = request.headers.get('x-forwarded-for') || ''; | |
| 51 | - | const ip = cfConnectingIp || forwardedFor.split(',')[0]?.trim() || 'unknown'; | |
| 47 | + | // Get the IP address for rate limiting | |
| 48 | + | const ip = getClientAddress(); | |
| 52 | 49 | const maskedIp = ip | |
| 53 | 50 | .split('.') | |
| 54 | 51 | .map((octet, idx) => (idx < 3 ? 'xxx' : octet)) | |
| @@ -31,7 +31,7 @@ function cleanupOldAttempts() { | |||||
| 31 | } | 31 | } | ||
| 32 | } | 32 | } | ||
| 33 | 33 | ||||
| 34 | - | export const POST: RequestHandler = async ({ request, cookies }) => { | 34 | + | export const POST: RequestHandler = async ({ request, cookies, getClientAddress }) => { |
| 35 | log.debug('New login attempt received'); | 35 | log.debug('New login attempt received'); | ||
| 36 | 36 | ||||
| 37 | // Lazy cleanup on each request (replaces setInterval for Cloudflare compatibility) | 37 | // Lazy cleanup on each request (replaces setInterval for Cloudflare compatibility) | ||
| @@ -44,11 +44,8 @@ export const POST: RequestHandler = async ({ request, cookies }) => { | |||||
| 44 | }); | 44 | }); | ||
| 45 | } | 45 | } | ||
| 46 | 46 | ||||
| 47 | - | // Get the IP address for rate limiting and Turnstile validation | 47 | + | // Get the IP address for rate limiting |
| 48 | - | // Prefer Cloudflare's CF-Connecting-IP header, then x-forwarded-for (first IP), then fallback | 48 | + | const ip = getClientAddress(); |
| 49 | - | const cfConnectingIp = request.headers.get('cf-connecting-ip'); | |||
| 50 | - | const forwardedFor = request.headers.get('x-forwarded-for') || ''; | |||
| 51 | - | const ip = cfConnectingIp || forwardedFor.split(',')[0]?.trim() || 'unknown'; | |||
| 52 | const maskedIp = ip | 49 | const maskedIp = ip | ||
| 53 | .split('.') | 50 | .split('.') | ||
| 54 | .map((octet, idx) => (idx < 3 ? 'xxx' : octet)) | 51 | .map((octet, idx) => (idx < 3 ? 'xxx' : octet)) | ||