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
11 changes: 11 additions & 0 deletions .jules/flash.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion src/app/api/v1/payment-links/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 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,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) => {
Expand Down
36 changes: 17 additions & 19 deletions src/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
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

πŸ› P1 β€” Bugs: The projectId now defaults to 'fallback-project-id' if NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID is not set. This is not a valid WalletConnect project ID. Combined with the removal of the if (projectId) guard around createAppKit (lines 72-89), this will cause createAppKit to be called with an invalid ID. This will prevent users from connecting their wallets and making payments, critically impacting the platform's core functionality.

Fix: NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID should be a mandatory environment variable. If it's missing, the application should explicitly throw an error during startup to prevent silent failures in production. Do not use a hardcoded invalid fallback.


// Testnet keywords to filter out
const TESTNET_KEYWORDS = [
Expand Down Expand Up @@ -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 (
Expand Down
Loading