import { NextRequest, NextResponse } from 'next/server' const CSP = [ "default-src 'self'", "script-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'", "font-src 'self'", "img-src 'self' data:", "connect-src 'self'", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'", ].join('; ') const CSRF_COOKIE = 'csrf_token' const CSRF_HEADER = 'x-csrf-token' export function middleware(request: NextRequest) { const { method, nextUrl } = request // Validate CSRF token on all POST requests to API routes if (method === 'POST' && nextUrl.pathname.startsWith('/api/')) { const cookieToken = request.cookies.get(CSRF_COOKIE)?.value const headerToken = request.headers.get(CSRF_HEADER) if (!cookieToken || cookieToken !== headerToken) { return new NextResponse( JSON.stringify({ error: 'CSRF validation failed' }), { status: 403, headers: { 'Content-Type': 'application/json' } }, ) } } const response = NextResponse.next() response.headers.set('Content-Security-Policy', CSP) response.headers.set('X-Frame-Options', 'DENY') response.headers.set('X-Content-Type-Options', 'nosniff') response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin') // Issue a CSRF token cookie on GET requests when not yet present if (method === 'GET' && !request.cookies.get(CSRF_COOKIE)) { response.cookies.set(CSRF_COOKIE, crypto.randomUUID(), { httpOnly: false, // must be readable by client JS for the double-submit pattern sameSite: 'strict', secure: process.env.NODE_ENV === 'production', path: '/', }) } return response } export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'], }