Setting Access-Control-Allow-Origin to wildcard or reflecting the request Origin lets any website make authenticated requests to your API. [CWE-942 · A05:2021]
allows unauthorized cross-origin access to API endpoints and user data
BeforeMerge scans your pull requests against this rule and 5+ others. Get actionable feedback before code ships.
Impact: HIGH (allows unauthorized cross-origin access to API endpoints and user data)
Next.js route handlers have no CORS headers by default — which is secure. Problems start when developers add permissive CORS to "fix" cross-origin errors during development and ship it to production. The two most dangerous patterns:
Access-Control-Allow-Origin: * — any website can read your API responsesOrigin header — any website can make credentialed requestsIncorrect (permissive CORS):
// app/api/user/route.ts
// ❌ Wildcard allows ANY website to read user data
export async function GET(request: NextRequest) {
const user = await getCurrentUser(request)
return NextResponse.json(user, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers': '*',
},
})
}// ❌ Reflecting Origin without validation — even worse with credentials
export async function GET(request: NextRequest) {
const origin = request.headers.get('origin')
return NextResponse.json(data, {
headers: {
'Access-Control-Allow-Origin': origin ?? '*', // Reflects anything!
'Access-Control-Allow-Credentials': 'true', // With cookies!
},
})
}// ❌ middleware.ts blanket CORS for all API routes
export function middleware(request: NextRequest) {
const response = NextResponse.next()
response.headers.set('Access-Control-Allow-Origin', '*')
return response
}Correct (strict CORS with origin allowlist):
// lib/cors.ts
const ALLOWED_ORIGINS = new Set([
'https://app.example.com',
'https://admin.example.com',
...(process.env.NODE_ENV === 'development' ? ['http://localhost:3000'] : []),
])
export function getCorsHeaders(request: NextRequest) {
const origin = request.headers.get('origin')
// Only allow listed origins
if (!origin || !ALLOWED_ORIGINS.has(origin)) {
return {} // No CORS headers = browser blocks the request
}
return {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Max-Age': '86400', // Cache preflight for 24 hours
}
}// app/api/user/route.ts
import { getCorsHeaders } from '@/lib/cors'
// ✅ Handle preflight
export async function OPTIONS(request: NextRequest) {
return new NextResponse(null, {
status: 204,
headers: getCorsHeaders(request),
})
}
export async function GET(request: NextRequest) {
const session = await auth()
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const user = await getUser(session.user.id)
return NextResponse.json(user, {
headers: getCorsHeaders(request),
})
}Key CORS rules:
* with Access-Control-Allow-Credentials: true — browsers block this, but developers often work around it with Origin reflection, which is worseNEXT_PUBLIC_Detection hints:
# Find permissive CORS headers
grep -rn "Access-Control-Allow-Origin.*\*" src/ --include="*.ts" --include="*.tsx"
# Find Origin reflection
grep -rn "Access-Control-Allow-Origin.*origin\|Allow-Origin.*request.headers" src/ --include="*.ts"Reference: MDN CORS · CWE-942: Permissive Cross-domain Policy