diff --git a/.jules/flash.md b/.jules/flash.md new file mode 100644 index 0000000..79407ac --- /dev/null +++ b/.jules/flash.md @@ -0,0 +1,5 @@ +## 2025-04-07 - Insecure extraction of x-forwarded-for leading to DB errors +**Category:** Security +**Finding:** The `x-forwarded-for` header was either directly inserted into the database or split by commas without trimming. +**Learning:** This could lead to a vulnerability where users can pass huge lengths of proxy chains leading to database insert failures when inserting logs due to string size constraints, as well as inaccurate logging. Always extract the first IP and trim it to avoid these issues. +**Action:** When extracting the client IP from the `x-forwarded-for` header, always handle proxy chains securely by splitting the string by comma and trimming whitespace (`ip.split(',')[0].trim()`). diff --git a/src/app/api/v1/payment-links/route.ts b/src/app/api/v1/payment-links/route.ts index aa7e98a..ea6ca55 100644 --- a/src/app/api/v1/payment-links/route.ts +++ b/src/app/api/v1/payment-links/route.ts @@ -111,14 +111,15 @@ export async function POST(req: NextRequest) { } // 5. Log API call (optional/async) - const clientIp = req.headers.get('x-forwarded-for') || 'unknown' + const forwardedFor = req.headers.get('x-forwarded-for') + const clientIp = forwardedFor ? forwardedFor.split(',')[0].trim() : 'unknown' supabase.from('api_logs').insert({ merchant_id: merchant.id, endpoint: '/api/v1/payment-links', method: 'POST', status_code: 201, request_body: body, - ip_address: clientIp.split(',')[0] + ip_address: clientIp // eslint-disable-next-line @typescript-eslint/no-explicit-any }).then(({ error }: any) => { if(error) console.error('Failed to log API call', error) diff --git a/src/lib/api/verify-api-key.ts b/src/lib/api/verify-api-key.ts index 9b858a8..3db004f 100644 --- a/src/lib/api/verify-api-key.ts +++ b/src/lib/api/verify-api-key.ts @@ -52,12 +52,15 @@ export async function verifyApiKey(req: NextRequest) { // Note: In Next.js middleware/edge, we should be careful with async/await blocking using waitUntil if available, // but here we are in a helper function called by route handlers. // We will do a fire-and-forget insert. + const forwardedFor = req.headers.get('x-forwarded-for'); + const clientIp = forwardedFor ? forwardedFor.split(',')[0].trim() : 'unknown'; + supabase.from('api_logs').insert({ merchant_id: merchant.id, endpoint: req.nextUrl.pathname, method: req.method, status_code: 200, // Assumed success if we get here - ip_address: req.headers.get('x-forwarded-for') || 'unknown', + ip_address: clientIp, user_agent: req.headers.get('user-agent') || 'unknown' // eslint-disable-next-line @typescript-eslint/no-explicit-any }).then(({ error }: any) => {