|$ curl https://forge-ai.dev/api/markdown?path=docs/security/csp
$cat docs/content-security-policy.md
updated Recently·30 min read·published

Content Security Policy

SecurityIntermediate🎯Free Tools
Introduction

Content Security Policy (CSP) is an HTTP header that restricts which resources a browser is allowed to load. It's the primary defense against Cross-Site Scripting (XSS), clickjacking, and data injection attacks. A well-configured CSP eliminates entire classes of vulnerabilities.

CSP Directives
DirectiveControlsExample
default-srcFallback for all resource types'self'
script-srcJavaScript execution'self' 'nonce-abc'
style-srcCSS stylesheets'self' 'unsafe-inline'
img-srcImages'self' data: https:
connect-srcFetch/XHR/WebSocket'self' api.example.com
frame-ancestorsWho can embed this page'none'
Nonce-Based CSP

A nonce (number used once) is a random token generated per request. Only scripts with the correct nonce can execute, blocking any injected scripts.

middleware.ts
TypeScript
1// Next.js middleware — generate nonce per request
2import { NextResponse } from "next/server";
3import type { NextRequest } from "next/server";
4import crypto from "crypto";
5
6export function middleware(request: NextRequest) {
7 const nonce = crypto.randomBytes(16).toString("base64");
8
9 const csp = [
10 "default-src 'self'",
11 "script-src 'self' 'nonce-" + nonce + "'",
12 "style-src 'self' 'nonce-" + nonce + "'",
13 "img-src 'self' data: https:",
14 "font-src 'self'",
15 "connect-src 'self' https://api.example.com",
16 "frame-ancestors 'none'",
17 "base-uri 'self'",
18 "form-action 'self'",
19 "report-uri /api/csp-report",
20 ].join("; ");
21
22 const response = NextResponse.next();
23 response.headers.set("Content-Security-Policy", csp);
24 response.headers.set("X-Content-Type-Options", "nosniff");
25 response.headers.set("X-Frame-Options", "DENY");
26 response.headers.set("X-XSS-Protection", "0");
27
28 // Pass nonce to the page via header
29 response.headers.set("X-Nonce", nonce);
30
31 return response;
32}
33
34export const config = {
35 matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
36};
layout.tsx
TypeScript
1// Use nonce in your layout
2export default function RootLayout({ children, nonce }: { children: React.ReactNode; nonce: string }) {
3 return (
4 <html lang="en">
5 <head>
6 <script src="/analytics.js" nonce={nonce} />
7 <style nonce={nonce}>{`.container { max-width: 1200px; }`}</style>
8 </head>
9 <body>{children}</body>
10 </html>
11 );
12}

warning

Never use 'unsafe-inline' in production CSP. It completely defeats the purpose of CSP against XSS. Always use nonces or hashes.
CSP Reporting
csp-report/route.ts
TypeScript
1// CSP report endpoint (receives violation reports)
2import { NextRequest, NextResponse } from "next/server";
3
4export async function POST(request: NextRequest) {
5 const report = await request.json();
6
7 console.error("CSP Violation:", {
8 documentUri: report["csp-report"]["document-uri"],
9 violatedDirective: report["csp-report"]["violated-directive"],
10 blockedUri: report["csp-report"]["blocked-uri"],
11 sourceFile: report["csp-report"]["source-file"],
12 lineNumber: report["csp-report"]["line-number"],
13 });
14
15 // Send to monitoring (Datadog, Sentry, etc.)
16 await sendToMonitoring(report);
17
18 return NextResponse.json({ status: "received" });
19}
report-only.ts
TypeScript
1// Report-Only mode: test CSP without enforcing
2const cspReportOnly = [
3 "default-src 'self'",
4 "script-src 'self' 'nonce-${nonce}'",
5 "report-uri /api/csp-report",
6 "report-to csp-endpoint",
7].join("; ");
8
9// Send as Report-Only header first
10response.headers.set("Content-Security-Policy-Report-Only", cspReportOnly);

info

Always deploy CSP in Report-Only mode first. Monitor violation reports for a few days, fix any legitimate resources being blocked, then switch to enforcement mode.
$Blueprint — Engineering Documentation·Section ID: SEC-CSP-01·Revision: 1.0