diff --git a/.jules/flash.md b/.jules/flash.md new file mode 100644 index 0000000..6ac234f --- /dev/null +++ b/.jules/flash.md @@ -0,0 +1,11 @@ +## 2026-04-05 - IP Extraction Vulnerability and Next.js Static Prerendering Error +**Category:** Security | Bug +**Finding:** +1. The `x-forwarded-for` header handling in `src/app/api/v1/payment-links/route.ts` and `src/lib/api/verify-api-key.ts` was vulnerable to IP spoofing and database insertion limits by inserting the raw string which can contain multiple comma-separated proxy chains. +2. Next.js static prerendering `npm run build` would fail because `createAppKit()` inside `src/providers.tsx` was conditionally executed inside `if (projectId)` which resolved to an empty string during build without environment variables. +**Learning:** +1. Always parse proxy chains properly: `ip.split(',')[0].trim()` instead of inserting raw values directly. +2. Next.js executes module level code during static generation without runtime environment variables. Third-party SDK initialization must be run unconditionally with fallback values. +**Action:** +1. Always split and trim the `x-forwarded-for` header to fetch the real client IP. +2. Ensure third-party initialization using environment variables has a fallback and is executed unconditionally. diff --git a/src/app/api/v1/payment-links/route.ts b/src/app/api/v1/payment-links/route.ts index aa7e98a..4f51f71 100644 --- a/src/app/api/v1/payment-links/route.ts +++ b/src/app/api/v1/payment-links/route.ts @@ -118,7 +118,7 @@ export async function POST(req: NextRequest) { method: 'POST', status_code: 201, request_body: body, - ip_address: clientIp.split(',')[0] + ip_address: clientIp.split(',')[0].trim() // 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..c415b9f 100644 --- a/src/lib/api/verify-api-key.ts +++ b/src/lib/api/verify-api-key.ts @@ -52,12 +52,13 @@ 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 clientIp = req.headers.get('x-forwarded-for') || '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.split(',')[0].trim(), user_agent: req.headers.get('user-agent') || 'unknown' // eslint-disable-next-line @typescript-eslint/no-explicit-any }).then(({ error }: any) => { diff --git a/src/providers.tsx b/src/providers.tsx index 264c46a..5dd268a 100644 --- a/src/providers.tsx +++ b/src/providers.tsx @@ -14,7 +14,7 @@ import type { AppKitNetwork } from '@reown/appkit/networks' const queryClient = new QueryClient() const alchemyKey = process.env.NEXT_PUBLIC_ALCHEMY_KEY -const projectId = process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID || '' +const projectId = process.env.NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID || 'fallback-project-id' // Testnet keywords to filter out const TESTNET_KEYWORDS = [ @@ -69,24 +69,22 @@ const solanaAdapter = new SolanaAdapter({ }) // Initialize AppKit modal (runs once at module load) -if (projectId) { - createAppKit({ - adapters: [wagmiAdapter, solanaAdapter], - projectId, - networks: mainnetNetworks as [AppKitNetwork, ...AppKitNetwork[]], - defaultNetwork: allNetworks.mainnet, - metadata: { - name: 'Flash Protocol', - description: 'Cross-chain payment gateway', - url: typeof window !== 'undefined' ? window.location.origin : 'https://flashprotocol.com', - icons: ['/logo-black.png'], - }, - features: { - analytics: false, - }, - themeMode: 'dark', - }) -} +createAppKit({ + adapters: [wagmiAdapter, solanaAdapter], + projectId, + networks: mainnetNetworks as [AppKitNetwork, ...AppKitNetwork[]], + defaultNetwork: allNetworks.mainnet, + metadata: { + name: 'Flash Protocol', + description: 'Cross-chain payment gateway', + url: typeof window !== 'undefined' ? window.location.origin : 'https://flashprotocol.com', + icons: ['/logo-black.png'], + }, + features: { + analytics: false, + }, + themeMode: 'dark', +}) export function Providers({ children }: { children: ReactNode }) { return (