---
# === IDENTITY ===
id: software/security/content-security-policy/2026
canonical_question: "How do I implement Content Security Policy (CSP)?"
aliases:
  - "CSP implementation guide"
  - "Content-Security-Policy header setup"
  - "how to configure CSP headers"
  - "CSP nonce strict-dynamic setup"
  - "Content Security Policy directives reference"
  - "CSP for XSS prevention"
  - "how to deploy Content Security Policy"
  - "CSP report-only mode"
entity_type: software_reference
domain: software > security > Content Security Policy
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.95
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "CSP Level 3 strict-dynamic widely adopted (2023); report-uri deprecated in favor of report-to (2024); Trusted Types gaining support (Chrome 83+, 2020)"
  next_review: 2026-08-26
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "ALWAYS start with Content-Security-Policy-Report-Only before enforcing -- deploying CSP in enforcement mode without testing WILL break site functionality"
  - "NEVER use 'unsafe-inline' for script-src in production -- it defeats CSP's primary XSS protection"
  - "NEVER use wildcard (*) as a source for script-src or default-src -- it allows loading scripts from any origin"
  - "Nonces MUST be cryptographically random (at least 128 bits) and regenerated on every page load -- reusing nonces defeats their purpose"
  - "CSP is defense-in-depth, NOT a replacement for output encoding -- always combine CSP with proper input validation and output encoding"
  - "frame-ancestors directive CANNOT be set via <meta> tag -- it must be sent as an HTTP header"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need XSS prevention beyond CSP (output encoding, sanitization)"
    use_instead: "software/security/xss-prevention/2026"
  - condition: "Need CORS configuration, not CSP"
    use_instead: "software/security/cors-configuration/2026"
  - condition: "Need HTTP security headers in general (HSTS, X-Frame-Options, etc.)"
    use_instead: "software/security/http-security-headers/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "server_platform"
    question: "What web server or framework are you using?"
    type: choice
    options: ["Express/Node.js", "Django", "Nginx", "Apache", "Cloudflare Workers", "Next.js", "Rails", "Other"]
  - key: "csp_goal"
    question: "What is your primary goal with CSP?"
    type: choice
    options: ["Prevent XSS attacks", "Control third-party scripts", "Prevent clickjacking", "Compliance requirement", "All of the above"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/security/content-security-policy/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/security/xss-prevention/2026"
      label: "XSS Prevention Guide"
    - id: "software/security/http-security-headers/2026"
      label: "HTTP Security Headers"
  solves: []
  alternative_to: []
  often_confused_with:
    - id: "software/security/cors-configuration/2026"
      label: "CORS Configuration (controls cross-origin requests, not resource loading)"

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Content Security Policy (CSP) - HTTP"
    author: MDN Web Docs
    url: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP
    type: official_docs
    published: 2026-02-19
    reliability: authoritative
  - id: src2
    title: "Content-Security-Policy (CSP) Header Reference"
    author: MDN Web Docs
    url: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy
    type: official_docs
    published: 2026-02-19
    reliability: authoritative
  - id: src3
    title: "Content Security Policy Cheat Sheet"
    author: OWASP Foundation
    url: https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html
    type: community_resource
    published: 2024-06-15
    reliability: authoritative
  - id: src4
    title: "Content Security Policy Level 3"
    author: W3C Web Application Security Working Group
    url: https://www.w3.org/TR/CSP3/
    type: rfc_spec
    published: 2025-06-06
    reliability: authoritative
  - id: src5
    title: "Mitigate XSS with a Strict Content Security Policy"
    author: Google Web Dev
    url: https://web.dev/articles/strict-csp
    type: technical_blog
    published: 2024-03-01
    reliability: high
  - id: src6
    title: "Content Security Policy (CSP) Implementation Guide"
    author: MDN Web Docs
    url: https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/CSP
    type: official_docs
    published: 2026-02-19
    reliability: authoritative
  - id: src7
    title: "Content-Security-Policy (CSP) Header Quick Reference"
    author: Foundeo Inc.
    url: https://content-security-policy.com/
    type: community_resource
    published: 2025-01-15
    reliability: high
---

# Content Security Policy (CSP): Implementation Guide

## TL;DR

- **Bottom line**: Deploy a strict nonce-based CSP with `strict-dynamic` to prevent inline script injection (XSS) -- start in report-only mode, then enforce after validating no breakage.
- **Key tool/command**: `Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`
- **Watch out for**: Using `'unsafe-inline'` in `script-src` defeats CSP's entire XSS protection -- use nonces or hashes instead.
- **Works with**: All modern browsers. CSP Level 3 (`strict-dynamic`) supported in Chrome 59+, Firefox 58+, Safari 15.4+, Edge 79+.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- ALWAYS start with `Content-Security-Policy-Report-Only` before enforcing -- deploying CSP in enforcement mode without testing WILL break site functionality
- NEVER use `'unsafe-inline'` for `script-src` in production -- it defeats CSP's primary XSS protection
- NEVER use wildcard (`*`) as a source for `script-src` or `default-src` -- it allows loading scripts from any origin
- Nonces MUST be cryptographically random (at least 128 bits) and regenerated on every page load -- reusing nonces defeats their purpose
- CSP is defense-in-depth, NOT a replacement for output encoding -- always combine CSP with proper input validation and output encoding
- `frame-ancestors` directive CANNOT be set via `<meta>` tag -- it must be sent as an HTTP header

## Quick Reference

**CSP Directives by Category:**

| # | Directive | Category | Controls | Default Fallback |
|---|---|---|---|---|
| 1 | `default-src` | Fetch | Fallback for all other fetch directives | None (browser default) |
| 2 | `script-src` | Fetch | JavaScript and WebAssembly resources | `default-src` |
| 3 | `style-src` | Fetch | CSS stylesheets | `default-src` |
| 4 | `img-src` | Fetch | Images and favicons | `default-src` |
| 5 | `connect-src` | Fetch | XHR, Fetch, WebSocket, EventSource | `default-src` |
| 6 | `font-src` | Fetch | Web fonts via @font-face | `default-src` |
| 7 | `frame-src` | Fetch | `<iframe>` and `<frame>` sources | `child-src` then `default-src` |
| 8 | `media-src` | Fetch | `<audio>`, `<video>`, `<track>` elements | `default-src` |
| 9 | `object-src` | Fetch | `<object>`, `<embed>` plugins | `default-src` |
| 10 | `worker-src` | Fetch | Worker, SharedWorker, ServiceWorker | `child-src` then `script-src` then `default-src` |
| 11 | `base-uri` | Document | URLs for `<base>` element | No fallback (allows any) |
| 12 | `form-action` | Navigation | Form submission target URLs | No fallback (allows any) |
| 13 | `frame-ancestors` | Navigation | Who can embed this page (replaces X-Frame-Options) | No fallback (allows any) |
| 14 | `upgrade-insecure-requests` | Document | Auto-upgrade HTTP to HTTPS | Disabled |
| 15 | `report-to` | Reporting | Where to send violation reports (replaces report-uri) | No reporting |

**Source Expression Keywords:**

| Keyword | Meaning | Use In |
|---|---|---|
| `'self'` | Same origin only | Any directive |
| `'none'` | Block all resources of this type | Any directive |
| `'unsafe-inline'` | Allow inline `<script>` and `<style>` (dangerous) | script-src, style-src |
| `'unsafe-eval'` | Allow `eval()`, `new Function()`, `setTimeout(string)` | script-src |
| `'nonce-{random}'` | Allow elements with matching nonce attribute | script-src, style-src |
| `'sha256-{hash}'` | Allow elements matching the hash | script-src, style-src |
| `'strict-dynamic'` | Trust scripts loaded by already-trusted scripts | script-src |
| `'unsafe-hashes'` | Allow inline event handlers matching hashes | script-src |
| `https:` | Allow any HTTPS origin | Any directive |
| `data:` | Allow data: URIs | Any directive |

## Decision Tree

```
START: What is your CSP deployment scenario?
+-- New application (no existing inline scripts)?
|   +-- YES -> Deploy strict nonce-based CSP immediately (see Step 1)
|   +-- NO |
+-- Existing app with inline scripts you control?
|   +-- YES -> Add nonces to all inline scripts, then deploy strict CSP (see Step 2)
|   +-- NO |
+-- Existing app with third-party inline scripts?
|   +-- YES -> Use hash-based CSP for static scripts + nonces for dynamic (see Step 3)
|   +-- NO |
+-- Legacy app that cannot be modified?
|   +-- YES -> Deploy allowlist-based CSP as interim measure, plan migration to strict CSP
|   +-- NO |
+-- Need to prevent clickjacking only?
|   +-- YES -> Use frame-ancestors 'none' or 'self' (Step 5)
|   +-- NO |
+-- DEFAULT -> Start with report-only mode to audit current resource loading (Step 1)
```

## Step-by-Step Guide

### 1. Deploy CSP in report-only mode

Start by deploying a strict policy in report-only mode to discover what would break without actually blocking anything. [src6]

```http
Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self';
  object-src 'none';
  base-uri 'none';
  report-to csp-endpoint;
```

Set up the Reporting-Endpoints header to receive violation reports:

```http
Reporting-Endpoints: csp-endpoint="https://example.com/csp-report"
```

**Verify**: Open browser DevTools > Console -- CSP violations appear as warnings with `[Report Only]` prefix. Check your report endpoint for incoming JSON reports.

### 2. Add nonces to all inline scripts

Generate a cryptographically random nonce per request and add it to every inline `<script>` tag. [src5]

```html
<!-- Server generates nonce per request: e.g., nonce="dGhpcyBpcyBhIG5vbmNl" -->
<script nonce="dGhpcyBpcyBhIG5vbmNl">
  // This script will execute because the nonce matches the CSP header
  console.log('Allowed by CSP');
</script>
```

```http
Content-Security-Policy: script-src 'nonce-dGhpcyBpcyBhIG5vbmNl' 'strict-dynamic'
```

**Verify**: Inline scripts without a nonce should be blocked. Check console for `Refused to execute inline script` errors.

### 3. Build a strict CSP policy

Combine the essential directives for a production-ready strict CSP. [src3]

```http
Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  style-src 'self' 'nonce-{RANDOM}';
  img-src 'self' https:;
  font-src 'self';
  connect-src 'self' https://api.example.com;
  frame-src 'none';
  object-src 'none';
  base-uri 'none';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;
  report-to csp-endpoint;
```

**Verify**: Run your CSP through Google's CSP Evaluator at `https://csp-evaluator.withgoogle.com/` -- it should show no high-severity findings.

### 4. Switch from report-only to enforcement

After monitoring report-only mode for at least 1-2 weeks with no unexpected violations, switch to enforcement. [src6]

```http
# Remove Content-Security-Policy-Report-Only and add:
Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  style-src 'self' 'nonce-{RANDOM}';
  object-src 'none';
  base-uri 'none';
  frame-ancestors 'none';
  report-to csp-endpoint;
```

**Verify**: `curl -sI https://your-site.com | grep -i content-security-policy` should return the enforced policy (not report-only).

### 5. Configure frame-ancestors for clickjacking protection

Replace the legacy `X-Frame-Options` header with CSP `frame-ancestors`. [src1]

```http
# Prevent all framing (equivalent to X-Frame-Options: DENY)
Content-Security-Policy: frame-ancestors 'none';

# Allow same-origin framing only (equivalent to X-Frame-Options: SAMEORIGIN)
Content-Security-Policy: frame-ancestors 'self';

# Allow specific origins to embed
Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.com;
```

**Verify**: Try embedding your page in an `<iframe>` on a different origin -- it should be blocked with a console error.

## Code Examples

### Express.js/Node.js: Helmet CSP with Per-Request Nonces

> Full script: [express-js-node-js-helmet-csp-with-per-request-non.js](scripts/express-js-node-js-helmet-csp-with-per-request-non.js) (32 lines)

```javascript
const express = require('express');     // ^4.18.0
const helmet = require('helmet');       // ^8.0.0
const crypto = require('crypto');
const app = express();
// Generate per-request nonce middleware
# ... (see full script)
```

### Django: django-csp Configuration

> Full script: [django-django-csp-configuration.py](scripts/django-django-csp-configuration.py) (25 lines)

```python
# settings.py -- using django-csp >= 4.0
# pip install django-csp
MIDDLEWARE = [
    "csp.middleware.CSPMiddleware",
    # ... other middleware
# ... (see full script)
```

### Nginx: CSP Header Configuration

```nginx
# /etc/nginx/snippets/csp-headers.conf

# Note: Nginx cannot generate per-request nonces natively.
# Use hash-based CSP or generate nonces in your app server.
# This example uses a hash-based approach for static sites.

add_header Content-Security-Policy "
  default-src 'self';
  script-src 'self' 'sha256-{HASH_OF_INLINE_SCRIPT}';
  style-src 'self';
  img-src 'self' https:;
  font-src 'self';
  connect-src 'self';
  object-src 'none';
  base-uri 'none';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;
" always;

# For dynamic sites, proxy nonce from upstream app:
# proxy_pass http://app_server;
# The app server sets the CSP header with nonces.
```

### Apache: CSP Header Configuration

```apache
# .htaccess or httpd.conf

# Hash-based CSP for static sites
Header always set Content-Security-Policy "\
  default-src 'self'; \
  script-src 'self' 'sha256-{HASH_OF_INLINE_SCRIPT}'; \
  style-src 'self'; \
  img-src 'self' https:; \
  font-src 'self'; \
  connect-src 'self'; \
  object-src 'none'; \
  base-uri 'none'; \
  form-action 'self'; \
  frame-ancestors 'none'; \
  upgrade-insecure-requests"

# Report-only mode for testing:
# Header always set Content-Security-Policy-Report-Only "..."
```

## Anti-Patterns

### Wrong: Using unsafe-inline with script-src

```http
# BAD -- unsafe-inline completely negates CSP's XSS protection
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
# Any XSS payload can execute inline scripts freely
```

### Correct: Nonce-based script-src

```http
# GOOD -- only scripts with the correct nonce execute
Content-Security-Policy: script-src 'nonce-abc123def456' 'strict-dynamic'
# XSS payloads cannot guess the per-request nonce
```

### Wrong: Wildcard sources in script-src

```http
# BAD -- allows scripts from ANY origin, including attacker-controlled domains
Content-Security-Policy: default-src *; script-src *
```

### Correct: Explicit origin allowlist

```http
# GOOD -- only scripts from your origin and trusted CDN
Content-Security-Policy: script-src 'self' https://cdn.trusted.com
# Better: use 'nonce-{RANDOM}' 'strict-dynamic' instead of domain allowlists
```

### Wrong: Overly permissive default-src with no overrides

```http
# BAD -- default-src https: allows loading from ANY HTTPS origin
Content-Security-Policy: default-src https:
# Attackers can host payloads on any HTTPS domain they control
```

### Correct: Restrictive default-src with explicit overrides

```http
# GOOD -- lock down default, open specific directives as needed
Content-Security-Policy: default-src 'none'; script-src 'nonce-{RANDOM}' 'strict-dynamic'; style-src 'self'; img-src 'self' https:; font-src 'self'; connect-src 'self'
```

### Wrong: CSP via meta tag for all directives

```html
<!-- BAD -- frame-ancestors, report-uri, report-to, and sandbox
     are IGNORED in meta tags. This gives false sense of security. -->
<meta http-equiv="Content-Security-Policy"
      content="frame-ancestors 'none'; report-uri /csp-report">
```

### Correct: CSP via HTTP header

```http
# GOOD -- all directives work via HTTP header
Content-Security-Policy: frame-ancestors 'none'; report-to csp-endpoint
```

### Wrong: Using unsafe-eval for convenience

```http
# BAD -- allows eval(), new Function(), setTimeout(string)
# Enables attackers to execute arbitrary code via string-to-code conversion
Content-Security-Policy: script-src 'self' 'unsafe-eval'
```

### Correct: Refactoring to avoid eval

```javascript
// GOOD -- replace eval() with safe alternatives
// Instead of: eval('alert("hello")')
// Use: structured data + DOM manipulation

// Instead of: setTimeout('doSomething()', 1000)
setTimeout(doSomething, 1000);  // pass function reference

// Instead of: new Function('return ' + userInput)
JSON.parse(userInput);  // for data parsing
```

## Common Pitfalls

- **Forgetting report-only first**: Deploying CSP in enforcement mode immediately breaks inline scripts, third-party widgets, and analytics. Fix: Always deploy with `Content-Security-Policy-Report-Only` first and monitor for 1-2 weeks. [src6]
- **Nonce reuse across requests**: Serving the same nonce for all requests (e.g., hardcoding it) allows attackers who discover it to bypass CSP. Fix: Generate a new cryptographically random nonce for every HTTP response using `crypto.randomBytes(16)` or equivalent. [src5]
- **Missing object-src and base-uri**: Omitting `object-src` allows Flash/Java plugin injection; omitting `base-uri` allows `<base>` tag hijacking to redirect relative URLs. Fix: Always include `object-src 'none'; base-uri 'none'` in your policy. [src3]
- **Allowlist bypasses via JSONP/Angular**: Allowlisting a CDN that hosts JSONP endpoints or Angular libraries lets attackers load script gadgets that bypass CSP. Fix: Use nonce-based CSP with `strict-dynamic` instead of domain allowlists. [src5]
- **CSP on meta tag limitations**: Using `<meta http-equiv="Content-Security-Policy">` ignores `frame-ancestors`, `report-uri`, `report-to`, and `sandbox` directives. Fix: Set CSP via HTTP response headers. [src2]
- **Mixed content after upgrade-insecure-requests**: The directive only upgrades navigational and subresource requests, not WebSocket (`ws://`) connections. Fix: Explicitly use `wss://` for WebSocket connections. [src1]
- **Breaking service workers**: Setting a restrictive `worker-src` or `script-src` without accounting for service worker scope blocks SW registration. Fix: Include `worker-src 'self'` if you use service workers. [src2]
- **report-uri deprecation**: `report-uri` is deprecated in CSP Level 3 in favor of `report-to`. Some older browsers only support `report-uri`. Fix: Include both `report-uri` and `report-to` during the transition period. [src4]

## Diagnostic Commands

```bash
# Check current CSP headers of a live site
curl -sI https://your-site.com | grep -i content-security-policy

# Check CSP with full headers
curl -sI https://your-site.com | grep -iE '(content-security|report)'

# Generate SHA-256 hash of an inline script for hash-based CSP
echo -n 'console.log("hello")' | openssl dgst -sha256 -binary | openssl base64
# Output: sha256-xxxxx (prefix with 'sha256-' in your CSP)

# Test CSP header syntax (use Google's CSP Evaluator)
# Visit: https://csp-evaluator.withgoogle.com/

# Browser DevTools: check for CSP violations
# Chrome: DevTools > Console > filter "content-security-policy"
# Firefox: DevTools > Console > filter "Content-Security-Policy"

# Scan for inline scripts that need nonces in your codebase
grep -rn '<script>' --include="*.html" --include="*.ejs" --include="*.hbs" .

# Scan for inline event handlers that CSP will block
grep -rn 'onclick=\|onload=\|onerror=\|onsubmit=' --include="*.html" .
```

## Version History & Compatibility

| CSP Level | Status | Browser Support | Key Features |
|---|---|---|---|
| CSP Level 3 | W3C Working Draft (June 2025) | Chrome 59+, Firefox 58+, Safari 15.4+, Edge 79+ | `strict-dynamic`, `report-to`, `worker-src`, `manifest-src`, nonce/hash enhancements |
| CSP Level 2 | W3C Recommendation | Chrome 40+, Firefox 31+, Safari 10+, Edge 15+ | `script-src`, `style-src`, `base-uri`, `form-action`, `frame-ancestors`, `report-uri`, nonce/hash |
| CSP Level 1 | W3C Recommendation (deprecated) | Chrome 25+, Firefox 23+, Safari 7+, Edge 12+ | Basic `default-src`, `script-src`, `img-src`, `style-src`, `connect-src` |
| Trusted Types | W3C Draft | Chrome 83+, Firefox behind flag | `require-trusted-types-for 'script'`, DOM sink protection |
| Reporting API v1 | W3C Draft | Chrome 96+, Edge 96+ | `report-to` with `Reporting-Endpoints` header |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Any web application serving HTML to browsers | Static file server with no HTML (pure API) | CORS headers for API protection |
| Need XSS defense-in-depth beyond output encoding | CSP is your only XSS defense | Output encoding + CSP together |
| Controlling third-party script loading (analytics, ads) | You trust all content from all origins | Still use CSP -- trust boundaries change |
| Preventing clickjacking (frame-ancestors) | Only need clickjacking protection | `X-Frame-Options` as legacy fallback, but prefer CSP |
| Compliance requires CSP (PCI DSS, SOC 2) | Internal-only tool with no external access | CSP still recommended but lower priority |

## Important Caveats

- `strict-dynamic` trusts scripts loaded by nonce-approved scripts -- if a trusted script has a gadget vulnerability (e.g., JSONP endpoint, prototype pollution), it can be exploited to bypass CSP
- CSP via `<meta>` tag does not support `frame-ancestors`, `report-uri`, `report-to`, or `sandbox` -- use HTTP headers for full protection
- Internet Explorer does not support the `Content-Security-Policy` header -- only the non-standard `X-Content-Security-Policy` (IE 10-11, limited support)
- `upgrade-insecure-requests` does not upgrade WebSocket connections (`ws://` to `wss://`) -- handle this explicitly in application code
- Hash-based CSP requires recalculating hashes whenever inline script content changes, making it fragile for frequently updated content -- prefer nonces for dynamic applications
- Multiple CSP headers are intersected (most restrictive wins), not merged -- adding a second header can only restrict, never relax, the policy

## Related Units
<!-- Generated from related_kos frontmatter -->

- [XSS Prevention Guide](/software/security/xss-prevention/2026)
- [HTTP Security Headers](/software/security/http-security-headers/2026)
- [CORS Configuration](/software/security/cors-configuration/2026)