This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { NextResponse, type NextRequest } from 'next/server'
|
||||
|
||||
// Review CR-10: the production nginx template used to send a static
|
||||
// `script-src 'self'` CSP. The App Router renders inline bootstrap scripts
|
||||
// (flight-data pushes) that a nonce-less policy blocks: HTML arrives 200 but
|
||||
// hydration never completes and the UI stays inert. The CSP is therefore
|
||||
// owned here: every response gets a fresh nonce, and Next.js — which reads
|
||||
// the CSP from the forwarded request headers — attaches it to the inline
|
||||
// scripts it emits. nginx passes this header through and must NOT add its
|
||||
// own script-src policy on top (browsers intersect multiple policies, which
|
||||
// would strip the nonce again). This is a nonce strategy, not a blanket
|
||||
// inline-script relaxation.
|
||||
export function middleware(request: NextRequest) {
|
||||
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
|
||||
// Dev-mode HMR uses a websocket on the same host; production never needs it.
|
||||
const connectSrc = process.env.NODE_ENV === 'development' ? `'self' ws:` : `'self'`
|
||||
const csp = [
|
||||
`default-src 'self'`,
|
||||
`script-src 'self' 'nonce-${nonce}'`,
|
||||
// Inline styles stay allowed (UI kit + sonner inject style attributes),
|
||||
// matching the previous nginx baseline.
|
||||
`style-src 'self' 'unsafe-inline'`,
|
||||
// Remote previews: og-image/favicon render from arbitrary https origins.
|
||||
`img-src 'self' data: blob: https:`,
|
||||
`font-src 'self' data:`,
|
||||
`connect-src ${connectSrc}`,
|
||||
`frame-ancestors 'none'`,
|
||||
`object-src 'none'`,
|
||||
`base-uri 'self'`,
|
||||
`form-action 'self'`,
|
||||
].join('; ')
|
||||
|
||||
const requestHeaders = new Headers(request.headers)
|
||||
requestHeaders.set('x-nonce', nonce)
|
||||
requestHeaders.set('Content-Security-Policy', csp)
|
||||
const response = NextResponse.next({ request: { headers: requestHeaders } })
|
||||
response.headers.set('Content-Security-Policy', csp)
|
||||
return response
|
||||
}
|
||||
|
||||
export const config = {
|
||||
// Everything except immutable static assets (they cannot carry a per-request
|
||||
// nonce anyway and contain no bootstrap scripts).
|
||||
matcher: ['/((?!_next/static/|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)'],
|
||||
}
|
||||
Reference in New Issue
Block a user