From a2b919c6cab35246c0d9c280834185a75d0f10fd Mon Sep 17 00:00:00 2001 From: Damjan Savic Date: Sun, 25 Jan 2026 13:24:50 +0100 Subject: [PATCH] fix: implement @next-safe/middleware for CSP (qa-requested) - Refactor src/middleware.ts to use chainMatch() and csp() from @next-safe/middleware - Replace manual response.headers.set() approach with @next-safe/middleware composition - Use chain() to properly compose i18n middleware with security middleware - Fixes Next.js rewrite limitation where headers set on rewrite responses don't propagate - Update next.config.ts comment to reflect correct CSP implementation Implements QA Session 2 fix request (previously not implemented correctly). Fixes QA rejections from Sessions 1, 2, and 3: security headers not appearing due to Next.js rewrite edge case (GitHub Issue #70515). Using industry-standard @next-safe/middleware package as documented solution for combining next-intl with security headers. Co-Authored-By: Claude Sonnet 4.5 --- next.config.ts | 1 + src/middleware.ts | 48 +++++++++++++++++++++-------------------------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/next.config.ts b/next.config.ts index 33bf79d..6297ea2 100644 --- a/next.config.ts +++ b/next.config.ts @@ -55,6 +55,7 @@ const nextConfig: NextConfig = { value: 'geolocation=(), microphone=(), camera=(), payment=(), usb=()', }, // Content-Security-Policy is handled by @next-safe/middleware in src/middleware.ts + // Other security headers (HSTS, Referrer-Policy, Permissions-Policy) are static and configured here ], }, { diff --git a/src/middleware.ts b/src/middleware.ts index 5a93abd..a7e99b1 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,6 +1,6 @@ +import { chain, chainMatch, isPageRequest, csp } from '@next-safe/middleware'; import createMiddleware from 'next-intl/middleware'; import { locales, defaultLocale } from './i18n/config'; -import { NextRequest } from 'next/server'; const handleI18nRouting = createMiddleware({ locales, @@ -8,33 +8,27 @@ const handleI18nRouting = createMiddleware({ localePrefix: 'always', }); -export default function middleware(request: NextRequest) { - // Handle i18n routing first - const response = handleI18nRouting(request); +// Define CSP using @next-safe/middleware +const securityMiddleware = csp({ + directives: { + 'default-src': ["'self'"], + 'script-src': ["'self'", "'unsafe-inline'", "'unsafe-eval'"], + 'style-src': ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'], + 'font-src': ["'self'", 'https://fonts.gstatic.com', 'data:'], + 'img-src': ["'self'", 'data:', 'blob:', 'https://mxadgucxhmstlzsbgmoz.supabase.co'], + 'connect-src': ["'self'", 'https://mxadgucxhmstlzsbgmoz.supabase.co'], + 'frame-ancestors': ["'self'"], + 'base-uri': ["'self'"], + 'form-action': ["'self'"], + }, +}); - // Add security headers to the response - response.headers.set( - 'Content-Security-Policy', - "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data: blob: https://mxadgucxhmstlzsbgmoz.supabase.co; connect-src 'self' https://mxadgucxhmstlzsbgmoz.supabase.co; frame-ancestors 'self'; base-uri 'self'; form-action 'self'" - ); - - response.headers.set( - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains; preload' - ); - - response.headers.set( - 'Referrer-Policy', - 'strict-origin-when-cross-origin' - ); - - response.headers.set( - 'Permissions-Policy', - 'geolocation=(), microphone=(), camera=(), payment=(), usb=()' - ); - - return response; -} +// Use chain to combine i18n middleware with security middleware +// First run i18n, then apply CSP only on page requests +export default chain( + handleI18nRouting, + chainMatch(isPageRequest)(securityMiddleware) +); export const config = { matcher: [