CSP Config Generator
To generate a strict Content Security Policy configuration for Next.js applications, follow these steps systematically.
Step 1: Analyze Application Resources
Identify all resource types used in the application.
Discover External Resources
Use Grep to find external resource references:
Scripts:
- "<script.*src="
- "import.*from.*http"
- "next/script"
Stylesheets:
- "<link.*stylesheet"
- "@import.*url"
- "next/font"
Images:
- "<img.*src="
- "next/image"
- "background-image.*url"
Fonts:
- "@font-face"
- "next/font/google"
- "fonts.googleapis.com"
APIs and Connections:
- "fetch("
- "axios"
- "WebSocket"
Media:
- "<video"
- "<audio"
- "<iframe"
Extract Domains
Collect all unique domains used:
-
CDNs (cdnjs.cloudflare.com, unpkg.com)
-
APIs (api.stripe.com, *.supabase.co)
-
Analytics (google-analytics.com, vercel.com)
-
Fonts (fonts.googleapis.com, fonts.gstatic.com)
-
Images (cloudinary.com, s3.amazonaws.com)
Consult references/csp-directives.md for directive documentation.
Step 2: Determine CSP Strategy
Choose the appropriate CSP implementation strategy:
Strategy A: Nonce-Based CSP (Recommended)
Most secure for Next.js apps with inline scripts:
-
Use nonce for inline scripts and styles
-
Strict directives
-
Works with Next.js App Router
Strategy B: Hash-Based CSP
For static content:
-
Use SHA-256 hashes for specific inline scripts
-
More restrictive
-
Requires rebuilding hashes on changes
Strategy C: Unsafe-Inline (Not Recommended)
Least secure, only for migration:
-
Allows all inline scripts
-
Use only temporarily while migrating
Recommend Strategy A (nonce-based) for modern Next.js apps.
Step 3: Generate CSP Directives
Build CSP directives based on discovered resources.
Default Directives
const cspDirectives = { 'default-src': ["'self'"], 'script-src': [ "'self'", "'nonce-{NONCE}'", // Will be replaced dynamically "'strict-dynamic'", ], 'style-src': [ "'self'", "'nonce-{NONCE}'", // Add specific domains if needed ], 'img-src': [ "'self'", 'data:', 'blob:', // Add CDN domains ], 'font-src': [ "'self'", 'data:', // Add font provider domains ], 'connect-src': [ "'self'", // Add API domains ], 'frame-src': [ "'self'", // Add allowed iframe sources ], 'object-src': ["'none'"], 'base-uri': ["'self'"], 'form-action': ["'self'"], 'frame-ancestors': ["'none'"], 'upgrade-insecure-requests': [], }
Add Discovered Domains
For each resource type, add discovered domains:
// If using Vercel Analytics cspDirectives['script-src'].push('https://va.vercel-scripts.com') cspDirectives['connect-src'].push('https://vitals.vercel-insights.com')
// If using Google Fonts cspDirectives['font-src'].push('https://fonts.gstatic.com') cspDirectives['style-src'].push('https://fonts.googleapis.com')
// If using Supabase cspDirectives['connect-src'].push('https://*.supabase.co')
// If using Cloudinary for images cspDirectives['img-src'].push('https://res.cloudinary.com')
Step 4: Generate Nonce Implementation
Create nonce generation and middleware.
Generate Nonce Utility
// lib/csp/nonce.ts import { headers } from 'next/headers'
export function generateNonce(): string { return Buffer.from(crypto.randomUUID()).toString('base64') }
export function getNonce(): string | undefined { return headers().get('x-nonce') ?? undefined }
Generate Middleware with Nonce
// middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) { const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const cspHeader = generateCSPHeader(nonce)
const requestHeaders = new Headers(request.headers) requestHeaders.set('x-nonce', nonce) requestHeaders.set('Content-Security-Policy', cspHeader)
const response = NextResponse.next({ request: { headers: requestHeaders, }, })
response.headers.set('Content-Security-Policy', cspHeader)
return response }
function generateCSPHeader(nonce: string): string {
const csp = [
{ name: 'default-src', values: ["'self'"] },
{ name: 'script-src', values: ["'self'", 'nonce-${nonce}', "'strict-dynamic'"] },
{ name: 'style-src', values: ["'self'", 'nonce-${nonce}'] },
{ name: 'img-src', values: ["'self'", 'data:', 'blob:'] },
{ name: 'font-src', values: ["'self'", 'data:'] },
{ name: 'connect-src', values: ["'self'"] },
{ name: 'frame-src', values: ["'self'"] },
{ name: 'object-src', values: ["'none'"] },
{ name: 'base-uri', values: ["'self'"] },
{ name: 'form-action', values: ["'self'"] },
{ name: 'frame-ancestors', values: ["'none'"] },
]
return csp
.map(({ name, values }) => ${name} ${values.join(' ')})
.join('; ')
}
export const config = { matcher: [ { source: '/((?!api|_next/static|_next/image|favicon.ico).*)', missing: [ { type: 'header', key: 'next-router-prefetch' }, { type: 'header', key: 'purpose', value: 'prefetch' }, ], }, ], }
Update Root Layout with Nonce
// app/layout.tsx import { getNonce } from '@/lib/csp/nonce'
export default function RootLayout({ children, }: { children: React.ReactNode }) { const nonce = getNonce()
return ( <html lang="en"> <head nonce={nonce}> {/* Head content will use nonce */} </head> <body nonce={nonce}>{children}</body> </html> ) }
Update Script Tags
// Use Next.js Script component with nonce import Script from 'next/script' import { getNonce } from '@/lib/csp/nonce'
export function AnalyticsScript() { const nonce = getNonce()
return ( <Script src="https://analytics.example.com/script.js" strategy="afterInteractive" nonce={nonce} /> ) }
Step 5: Generate next.config.ts Configuration
Alternative approach using headers in next.config.ts:
// next.config.ts import type { NextConfig } from 'next'
const nextConfig: NextConfig = { async headers() { return [ { source: '/(.*)', headers: [ { key: 'Content-Security-Policy', value: generateCSP(), }, ], }, ] }, }
function generateCSP(): string { const csp = [ { name: 'default-src', values: ["'self'"] }, { name: 'script-src', values: [ "'self'", "'unsafe-eval'", // Required for React dev mode "'unsafe-inline'", // Required for Next.js hydration // Add specific domains 'https://va.vercel-scripts.com', ], }, { name: 'style-src', values: [ "'self'", "'unsafe-inline'", // Required for Next.js styles 'https://fonts.googleapis.com', ], }, { name: 'img-src', values: ["'self'", 'data:', 'blob:', 'https:'], }, { name: 'font-src', values: ["'self'", 'data:', 'https://fonts.gstatic.com'], }, { name: 'connect-src', values: [ "'self'", 'https://vitals.vercel-insights.com', 'https://*.supabase.co', ], }, { name: 'frame-src', values: ["'self'"] }, { name: 'object-src', values: ["'none'"] }, { name: 'base-uri', values: ["'self'"] }, { name: 'form-action', values: ["'self'"] }, { name: 'frame-ancestors', values: ["'none'"] }, ]
// Remove 'unsafe-inline' and 'unsafe-eval' in production if (process.env.NODE_ENV === 'production') { const scriptSrc = csp.find(({ name }) => name === 'script-src') if (scriptSrc) { scriptSrc.values = scriptSrc.values.filter( (value) => value !== "'unsafe-eval'" && value !== "'unsafe-inline'" ) } }
return csp
.map(({ name, values }) => ${name} ${values.join(' ')})
.join('; ')
}
export default nextConfig
Step 6: Handle Development vs Production
Create environment-specific CSP:
// lib/csp/config.ts const isDevelopment = process.env.NODE_ENV === 'development'
export function getCSPDirectives() { const baseDirectives = { 'default-src': ["'self'"], 'script-src': [ "'self'", isDevelopment && "'unsafe-eval'", // React Fast Refresh ].filter(Boolean), 'style-src': [ "'self'", isDevelopment && "'unsafe-inline'", // Development styles ].filter(Boolean), // ... other directives }
return baseDirectives }
Step 7: Generate CSP Testing Suite
Create tests to verify CSP configuration:
// tests/csp.test.ts import { describe, it, expect } from 'vitest'
describe('CSP Configuration', () => { it('includes required directives', () => { const csp = generateCSPHeader('test-nonce')
expect(csp).toContain("default-src 'self'")
expect(csp).toContain("object-src 'none'")
expect(csp).toContain('nonce-test-nonce')
})
it('does not include unsafe-inline in production', () => { process.env.NODE_ENV = 'production' const csp = generateCSPHeader('test-nonce')
expect(csp).not.toContain("'unsafe-inline'")
expect(csp).not.toContain("'unsafe-eval'")
})
it('allows development-specific directives', () => { process.env.NODE_ENV = 'development' const csp = generateCSPHeader('test-nonce')
// Development may include unsafe-eval for React Fast Refresh
expect(csp).toBeDefined()
}) })
Step 8: Generate CSP Violation Reporting
Set up CSP violation reporting:
// app/api/csp-report/route.ts import { NextResponse } from 'next/server'
export async function POST(request: Request) { try { const report = await request.json()
// Log violation
console.error('CSP Violation:', {
blockedURI: report['csp-report']['blocked-uri'],
violatedDirective: report['csp-report']['violated-directive'],
originalPolicy: report['csp-report']['original-policy'],
documentURI: report['csp-report']['document-uri'],
})
// Optionally send to monitoring service
// await sendToMonitoring(report)
return NextResponse.json({ received: true })
} catch (error) { return NextResponse.json({ error: 'Invalid report' }, { status: 400 }) } }
Add report-uri to CSP:
const csp = [ // ... other directives { name: 'report-uri', values: ['/api/csp-report'] }, { name: 'report-to', values: ['csp-endpoint'] }, ]
Step 9: Generate Documentation
Create comprehensive CSP documentation using template from assets/csp-documentation-template.md :
Content Security Policy Configuration
Overview
This application uses a strict Content Security Policy to prevent XSS attacks.
Implementation
CSP is implemented via [middleware/next.config] using [nonce-based/hash-based] strategy.
Directives
default-src 'self'- Only load resources from same originscript-src 'self' 'nonce-{NONCE}'- Scripts require nonce- [etc.]
Adding New Resources
To add a new external resource:
- Identify the resource type
- Add domain to appropriate directive
- Test in development
- Update documentation
Troubleshooting
Common CSP violations and fixes...
Step 10: Validate CSP Configuration
Generate validation checklist:
CSP Validation Checklist
- All external scripts have nonce or are in script-src
- All external styles are in style-src
- All API endpoints are in connect-src
- object-src is set to 'none'
- frame-ancestors is configured
- No 'unsafe-inline' in production script-src
- CSP violations are being reported
- Development mode works correctly
- Production build passes CSP checks
Consulting References
Throughout generation:
-
Consult references/csp-directives.md for directive documentation
-
Consult references/csp-best-practices.md for security guidelines
-
Use templates from assets/csp-config-template.ts
-
Use documentation template from assets/csp-documentation-template.md
Output Format
Generate files:
lib/csp/ nonce.ts config.ts middleware.ts (enhanced) app/api/csp-report/ route.ts docs/ csp-configuration.md tests/ csp.test.ts
Verification Checklist
Before completing:
-
All resource types analyzed
-
CSP directives generated
-
Nonce implementation complete
-
Middleware configured
-
Development/production handled
-
Violation reporting set up
-
Documentation created
-
Tests generated
Completion
When finished:
-
Display generated CSP configuration
-
List all allowed domains
-
Explain implementation approach
-
Provide testing instructions
-
Offer to implement or adjust configuration