Web Security

Content Security Policy (CSP) Header Examples and Best Practices

Real-world CSP header examples for common web app scenarios. Covers directive reference, nonces, hashes, reporting, and how to set up CSP in Next.js without breaking your app.

December 23, 20255 min readShipSafer Team

Content Security Policy (CSP) is one of the most powerful browser security mechanisms available — and one of the most frequently misconfigured. A correctly deployed CSP eliminates entire classes of XSS attacks. A misconfigured one either does nothing or breaks your application.

This guide provides real-world CSP examples, explains every directive, and walks through implementing CSP in modern web apps without breaking anything.

What CSP Does

CSP is an HTTP header that tells the browser which sources it's allowed to load content from. If a script, style, image, or other resource doesn't come from an approved source, the browser blocks it.

Without CSP, a successful XSS injection can execute arbitrary JavaScript in the victim's browser context. With a strict CSP, even if an attacker injects <script>, the browser refuses to execute it.

The Most Important Directive: default-src

default-src is the fallback for all other resource types. If you only set one directive, this is it:

Content-Security-Policy: default-src 'self'

This allows resources only from your own origin. Any other directive that isn't explicitly set falls back to default-src.

Complete CSP Directive Reference

DirectiveControls
default-srcFallback for all types
script-src<script>, JavaScript
style-src<style>, CSS
img-src<img>, background images
font-srcWeb fonts
connect-srcfetch, XHR, WebSocket, EventSource
media-src<audio>, <video>
object-src<object>, <embed>, <applet>
child-srcWorkers, iframes
frame-src<iframe> sources
worker-srcWeb Workers
frame-ancestorsWho can embed this page
form-actionWhere <form> can submit
base-uriAllowed <base> href values
upgrade-insecure-requestsUpgrade HTTP resources to HTTPS
block-all-mixed-contentBlock HTTP resources on HTTPS pages
report-uri / report-toWhere to send violation reports

CSP Values

ValueMeaning
'self'Same origin (scheme + host + port)
'none'Block everything
'unsafe-inline'Allow inline scripts/styles (weakens CSP significantly)
'unsafe-eval'Allow eval() and similar (weakens CSP)
'nonce-{value}'Allow specific inline script with matching nonce
'sha256-{hash}'Allow specific inline script by its hash
https:Any HTTPS source
data:data: URIs
blob:blob: URIs
example.comSpecific hostname
*.example.comAll subdomains

Real-World CSP Examples

Strict CSP (recommended for new apps)

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{RANDOM_NONCE}';
  style-src 'self' 'nonce-{RANDOM_NONCE}';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.yourdomain.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests

This uses nonces to allow inline scripts/styles without 'unsafe-inline'. The nonce must be unique per request and unpredictable (use crypto.randomUUID() or similar).

Next.js App with Analytics and CDN

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{NONCE}' https://analytics.yourdomain.com;
  style-src 'self' 'nonce-{NONCE}';
  img-src 'self' data: https://images.unsplash.com https://avatars.githubusercontent.com;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.yourdomain.com https://analytics.yourdomain.com;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none'

SaaS App with Stripe and Intercom

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{NONCE}' https://js.stripe.com https://widget.intercom.io;
  style-src 'self' 'nonce-{NONCE}';
  img-src 'self' data: https: blob:;
  font-src 'self' data:;
  connect-src 'self' https://api.stripe.com https://api-iam.intercom.io wss://nexus-websocket-a.intercom.io https://yourdomain.com;
  frame-src https://js.stripe.com https://hooks.stripe.com;
  object-src 'none';
  base-uri 'self'

Legacy App (with inline scripts — less secure)

If you have a legacy app you can't immediately refactor to use nonces:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'unsafe-inline' 'unsafe-eval';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  object-src 'none';
  base-uri 'self'

This still provides value by blocking external script loading, even without restricting inline scripts. Treat it as a starting point, not a final state.

Nonces in Practice

A nonce (number used once) is a cryptographically random value added to your CSP header and your <script> tags. Browsers allow scripts with matching nonces, blocking everything else:

<!-- CSP header: script-src 'nonce-abc123' -->
<script nonce="abc123">
  // This executes
</script>
<script>
  // This is BLOCKED
</script>

Implementing Nonces in Next.js

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
  const csp = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
    style-src 'self' 'nonce-${nonce}';
    img-src 'self' data: https:;
    object-src 'none';
    base-uri 'self';
    frame-ancestors 'none'
  `.replace(/\s+/g, ' ').trim();

  const response = NextResponse.next();
  response.headers.set('Content-Security-Policy', csp);
  response.headers.set('x-nonce', nonce);
  return response;
}

Then use the nonce in your layout:

// app/layout.tsx
import { headers } from 'next/headers';

export default async function RootLayout({ children }) {
  const nonce = (await headers()).get('x-nonce') ?? '';
  return (
    <html>
      <head>
        <script nonce={nonce} src="/scripts/analytics.js" />
      </head>
      <body>{children}</body>
    </html>
  );
}

CSP Reporting

The most useful feature for deployment: report violations without blocking anything.

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' 'nonce-{NONCE}';
  report-uri /api/csp-report

Report-Only mode sends violation reports without enforcing anything. Use this before going live with enforcement to discover what your CSP would break.

Your /api/csp-report endpoint receives POST requests with JSON bodies like:

{
  "csp-report": {
    "document-uri": "https://yourdomain.com/app",
    "violated-directive": "script-src",
    "blocked-uri": "https://cdn.example.com/analytics.js",
    "original-policy": "default-src 'self'; script-src 'self'"
  }
}

Testing Your CSP

  • browser DevTools → Console tab: CSP violations are logged with details
  • Report-Only mode + monitor your report endpoint for violations
  • CSP Evaluator (Google): paste your CSP and get a security analysis
  • ShipSafer: automated CSP testing against your live domain with actionable suggestions
csp
content-security-policy
xss
http-headers
web-security

Check Your Security Score — Free

See exactly how your domain scores on DMARC, TLS, HTTP headers, and 25+ other automated security checks in under 60 seconds.