Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/flash.md
Original file line number Diff line number Diff line change
@@ -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()`).
5 changes: 3 additions & 2 deletions src/app/api/v1/payment-links/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚑ Flash Review

πŸ› Bug: The previous logic for extracting clientIp might have stored IP addresses with leading or trailing whitespace if the x-forwarded-for header contained spaces around the comma delimiters (e.g., '192.168.1.1 , 10.0.0.1'). The addition of .trim() correctly sanitizes the IP address, ensuring consistent and accurate logging. This is a good robustness improvement.

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)
Expand Down
5 changes: 4 additions & 1 deletion src/lib/api/verify-api-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
Loading