csp-config-generator
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 origin
- `script-src 'self' 'nonce-{NONCE}'` - Scripts require nonce
- [etc.]
## Adding New Resources
To add a new external resource:
1. Identify the resource type
2. Add domain to appropriate directive
3. Test in development
4. 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.mdfor directive documentation - Consult
references/csp-best-practices.mdfor 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
More from hopeoverture/worldbuilding-app-skills
eslint-prettier-husky-config
This skill should be used when setting up code quality tooling with ESLint v9 flat config, Prettier formatting, Husky git hooks, lint-staged pre-commit checks, and GitHub Actions CI lint workflow. Apply when initializing linting, adding code formatting, configuring pre-commit hooks, setting up quality gates, or establishing lint CI checks for Next.js or React projects.
51testing-next-stack
Scaffolds comprehensive testing setup for Next.js applications including Vitest unit tests, React Testing Library component tests, and Playwright E2E flows with accessibility testing via axe-core. This skill should be used when setting up test infrastructure, generating test files, creating test utilities, adding accessibility checks, or configuring testing frameworks for Next.js projects. Trigger terms include setup testing, scaffold tests, vitest, RTL, playwright, e2e tests, component tests, unit tests, accessibility testing, a11y tests, axe-core, test configuration.
38markdown-editor-integrator
This skill should be used when installing and configuring markdown editor functionality using @uiw/react-md-editor. Applies when adding rich text editing, markdown support, WYSIWYG editors, content editing with preview, or text formatting features. Trigger terms include markdown editor, rich text editor, text editor, add markdown, install markdown editor, markdown component, WYSIWYG, content editor, text formatting, editor preview.
27form-generator-rhf-zod
This skill should be used when generating React forms with React Hook Form, Zod validation, and shadcn/ui components. Applies when creating entity forms, character editors, location forms, data entry forms, or any form requiring client and server validation. Trigger terms include create form, generate form, build form, React Hook Form, RHF, Zod validation, form component, entity form, character form, data entry, form schema.
23supabase-auth-ssr-setup
This skill should be used when configuring Supabase Auth for server-side rendering with Next.js App Router, including secure cookie handling, middleware protection, route guards, authentication utilities, and logout flow. Apply when setting up SSR auth, adding protected routes, implementing middleware authentication, configuring secure sessions, or building login/logout flows with Supabase.
18tailwind-shadcn-ui-setup
This skill should be used when setting up, configuring, or initializing Tailwind CSS (v3 or v4) and shadcn/ui for Next.js 16 App Router projects. Configure dark mode, design tokens, base layout with header/sidebar, accessibility defaults, and generate example components. Includes comprehensive setup automation, theme customization, and production-ready patterns. Use when the user requests "setup Tailwind", "configure shadcn/ui", "add dark mode", "initialize design system", or "setup UI framework" for Next.js projects.
17