Security Analysis

Content Security Policy Analysis Guide

How to build, audit, and strengthen a CSP that actually protects your site from injection attacks

SiteSecurityScore Team·18 min read·Updated Feb 20, 2026
Digital code representing Content Security Policy protection mechanisms

What is Content Security Policy?#

Content Security Policy (CSP) is an HTTP response header that tells the browser exactly which origins are allowed to load scripts, styles, images, and other resources on a page. It acts as an allowlist: anything not explicitly permitted gets blocked. This makes CSP one of the most effective defenses against cross-site scripting (XSS), a class of attack where an attacker injects malicious code into a page that the browser then executes as if it came from the site itself.

CSP also prevents several related attacks. It can stop clickjacking (where a transparent iframe overlay tricks a user into clicking on a hidden element) through its frame-ancestors directive. It can block data exfiltration by restricting which domains receive network requests. And it can eliminate the use of dangerous JavaScript features like eval(), which converts strings into executable code.

A CSP works through an enforcement model. When the browser receives the Content-Security-Policy header, it parses each directive and applies the rules to every resource the page tries to load. If a script, stylesheet, image, or connection does not match an allowed source, the browser blocks it and optionally sends a violation report to a URL you specify. You can also deploy a policy in report-only mode first, which logs violations without blocking anything, letting you test a policy before enforcing it.

Build a policy interactively

Use the CSP Generator to assemble your Content-Security-Policy header step by step, or read the CSP Generator Guide for a walkthrough of each directive.

CSP Directives Reference#

A CSP is built from individual directives, each of which controls a specific type of resource. The directives below are grouped by their security role. Every directive accepts a list of source expressions such as 'self', 'none', or a full domain URL.

Foundation Directives

These two directives form the backbone of any CSP. default-src sets the fallback policy for every resource type you have not listed separately, while script-src controls JavaScript execution, the primary attack surface for XSS.

default-src

Critical

Default fallback for all resource types not covered by a more specific directive. If you only set one directive, make it this one.

default-src 'self'
'self''none'https:data:

script-src

Critical

Controls which scripts the browser may execute. This is the most important directive for preventing XSS. Avoid unsafe-inline and unsafe-eval whenever possible. Instead, use a nonce (a random one-time token generated per request) or hash to allowlist individual inline script blocks. Adding 'strict-dynamic' lets nonce-trusted scripts load additional scripts dynamically.

script-src 'self' 'nonce-abc123' 'strict-dynamic' https://trusted.cdn.com
'self''unsafe-inline''unsafe-eval''strict-dynamic''wasm-unsafe-eval''unsafe-hashes'nonce-*sha256-*https:

Framing and Object Control

These directives prevent your page from being embedded without permission and block legacy plugin formats. frame-ancestors is the CSP replacement for the older X-Frame-Options header and is the primary defense against clickjacking.

frame-ancestors

High

Controls which sites can embed your page in an iframe, frame, or object tag. Setting this to 'none' blocks all framing and prevents clickjacking attacks.

frame-ancestors 'none'
'none''self'https:

object-src

Medium

Restricts which plugins (Flash, Java applets, Silverlight) can be loaded. Modern browsers have largely removed plugin support, but setting object-src to 'none' closes a legacy attack surface.

object-src 'none'
'none''self'

base-uri

Medium

Controls which URLs can appear in the page's <base> tag. An attacker who can inject a <base> element can redirect all relative URLs on the page to a malicious domain.

base-uri 'self'
'self''none'

Resource Fetch Directives

Each of these overrides default-src for a specific resource type. You only need to list the ones where your policy differs from the default. For example, if default-src is 'self' and you load fonts from Google Fonts, you add a font-src directive for that additional origin.

style-src

High

Controls which stylesheets can be applied. Like script-src, prefer nonces or hashes over unsafe-inline for inline styles.

style-src 'self' https://fonts.googleapis.com
'self''unsafe-inline'nonce-*sha256-*

img-src

Medium

Restricts which origins can serve images. The data: scheme is often needed for inline images such as base64-encoded placeholders or SVG data URIs.

img-src 'self' data: https://cdn.example.com
'self'data:https:blob:

connect-src

High

Limits the origins for fetch(), XMLHttpRequest, WebSocket, and EventSource connections. This directive controls where your JavaScript can send network requests.

connect-src 'self' https://api.example.com
'self'https:wss:

font-src

Medium

Controls which origins can serve web fonts. Most sites only need 'self' and their font CDN.

font-src 'self' https://fonts.gstatic.com
'self'https:data:

media-src

Medium

Restricts origins for audio and video elements. Only needed if your site hosts or embeds media content.

media-src 'self' https://media.example.com
'self'https:blob:

worker-src

Medium

Controls which origins can serve Web Workers, Service Workers, and Shared Workers. Falls back to child-src, then script-src, then default-src if not set.

worker-src 'self'
'self''none'blob:

child-src

Medium

Controls both workers and nested browsing contexts (iframes). Acts as a fallback for frame-src and worker-src. In most cases, using frame-src and worker-src separately gives better control.

child-src 'self'
'self''none'https:blob:

manifest-src

Medium

Restricts which origins can serve web app manifest files. Only needed if your application uses a web manifest for PWA features.

manifest-src 'self'
'self''none'

Nonces, Hashes, and Modern CSP Keywords

CSP Level 2 and 3 introduced nonces, hashes, and trust propagation keywords that let you allow specific inline code without the security compromise of 'unsafe-inline'. These are the recommended approach for modern web applications.

Nonces ('nonce-...')

Recommended

A nonce is a random, base64 encoded token generated per server request. You include it in both the CSP header and the inline script/style tag's nonce attribute. Because it changes on every request, an attacker cannot predict or reuse it. This is the most practical way to allow inline code securely.

script-src 'self' 'nonce-R4nd0mT0ken=='

When a nonce is present, the browser ignores 'unsafe-inline' in the same directive. You can safely include both for backward compatibility.

Hashes ('sha256-...', 'sha384-...', 'sha512-...')

Recommended

A hash allowlists a specific inline script or style by the SHA hash of its exact content. If the content changes by even one character, the hash will no longer match and the browser will block it. Hashes are ideal for static inline code that does not change between deployments.

script-src 'self' 'sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng='

For external scripts, the element must also have a matching integrity attribute (Subresource Integrity).

'strict-dynamic'

script-src only

When present, trust propagates from a nonce or hash protected script to any scripts it loads dynamically. This means your main script can use document.createElement('script') to load additional scripts without listing each origin. Host allowlists and 'unsafe-inline' are ignored when strict-dynamic is active.

script-src 'nonce-abc123' 'strict-dynamic'

'wasm-unsafe-eval'

script-src only

Allows WebAssembly compilation (WebAssembly.compile and instantiate) without enabling general eval(). If your application uses WASM but not eval(), this is much safer than 'unsafe-eval' because it only allows WebAssembly, not arbitrary string to code conversion.

script-src 'self' 'wasm-unsafe-eval'

'unsafe-hashes'

script-src, style-src

Allows specific inline event handlers (onclick, onload, etc.) or style attributes whose content matches a hash in the policy. This is more targeted than 'unsafe-inline' because only the exact matching code runs. Requires corresponding sha256/sha384/sha512 hash values.

script-src 'self' 'unsafe-hashes' 'sha256-...'

upgrade-insecure-requests

Standalone directive

Instructs the browser to automatically upgrade all HTTP requests to HTTPS before they are sent. This is a standalone directive that takes no source values. Useful during migration from HTTP to HTTPS to prevent mixed content issues.

upgrade-insecure-requests

How to Audit Your CSP#

A Content Security Policy should be reviewed regularly, especially after adding new third-party scripts, changing CDNs, or modifying how your application loads resources. The audit process below catches misconfigurations before they weaken your defenses or break functionality.

Step 1: Deploy in Report-Only Mode

Before enforcing a new or updated policy, deploy it using the Content-Security-Policy-Report-Only header. This logs violations without blocking any content, letting you see what would break before it actually does.

Report-only mode
# Report-only mode (logs violations, does not block)
Content-Security-Policy-Report-Only: default-src 'self';
  script-src 'self'; report-uri /csp-report

# After confirming no issues, switch to enforcing
Content-Security-Policy: default-src 'self';
  script-src 'self';

Step 2: Check with Browser DevTools

Open Developer Tools (F12) and navigate through your entire application. The Console tab shows CSP violation messages with the blocked resource URL and the directive that caused the block. Test user interactions like form submissions, file uploads, and modal dialogs since these often load additional resources.

  • Look for red "Refused to load" messages in the Console
  • Check the Network tab for blocked requests (status column)
  • Filter Console output by "CSP" to isolate policy violations

Step 3: Scan with Automated Tools

Use SiteSecurityScore or similar tools to get an automated analysis of your CSP directives. Automated scans catch missing directives, unsafe values, and overly permissive source lists that are easy to miss in manual review.

  • Verify the CSP header is present and being served on every page
  • Confirm critical directives (default-src, script-src, frame-ancestors) are included
  • Check that no unsafe-inline or unsafe-eval directives remain

Step 4: Review Third-Party Dependencies

Third-party scripts (analytics, chat widgets, ad networks) are the most common reason for CSP violations. Document every external origin your application needs and verify each one is still in use. Remove any that are no longer needed to reduce your attack surface.

Common CSP Issues and Solutions#

These are the misconfigurations we see most often when auditing Content Security Policies. Each one weakens or completely negates the protection CSP is designed to provide.

unsafe-inline directive

Allows inline scripts and styles, which means any injected markup can execute code. This effectively disables the protection CSP is designed to provide against cross-site scripting.

Solution

Replace unsafe-inline with nonces (unique tokens generated per request) or hashes that allowlist only known inline blocks.

Example Fix

script-src 'self' 'unsafe-inline' → script-src 'self' 'nonce-abc123'

unsafe-eval directive

Permits eval(), Function(), and similar dynamic code execution. Attackers who can control a string value anywhere in your application can turn it into executable code.

Solution

Refactor code to avoid eval() and use safer alternatives such as JSON.parse() for data processing.

Example Fix

script-src 'self' 'unsafe-eval' → script-src 'self'

Missing default-src

Without a default-src fallback, any resource type you have not explicitly listed has no restriction. The browser will allow it from any origin.

Solution

Always include default-src 'self' as a baseline. Then override specific resource types as needed.

Example Fix

Add: default-src 'self'

Missing frame-ancestors

Without frame-ancestors, other websites can embed your pages in iframes. Attackers use this for clickjacking, where they overlay invisible UI to trick users into performing unintended actions.

Solution

Add frame-ancestors 'none' to block all framing, or 'self' to allow only your own domain.

Example Fix

frame-ancestors 'none'

Overly permissive wildcard sources

Using wildcards (*) or broad schemes like https: allows resources from any matching origin. An attacker only needs to find one trusted CDN or third-party host that serves user-controlled content to bypass the policy.

Solution

List only the specific domains your application actually needs. Audit third-party scripts regularly.

Example Fix

img-src * → img-src 'self' https://cdn.example.com

Do not use CSP meta tags for frame-ancestors

The frame-ancestors directive is ignored when set via a <meta> tag. It must be delivered as an HTTP response header. The same applies to report-uri and sandbox directives.

Implementing CSP on Apache, Nginx, and Node.js#

The examples below show a practical CSP for a typical web application. Adjust the source lists to match your own domains, CDNs, and third-party services.

Apache (.htaccess or httpd.conf)

Apache
# Content Security Policy
Header always set Content-Security-Policy \
  "default-src 'self'; \
   script-src 'self' https://trusted.cdn.com; \
   style-src 'self' https://fonts.googleapis.com; \
   img-src 'self' data: https:; \
   connect-src 'self' https://api.example.com; \
   font-src 'self' https://fonts.gstatic.com; \
   frame-ancestors 'none'; \
   object-src 'none'; \
   base-uri 'self'"

Nginx (nginx.conf or site block)

Nginx
# Content Security Policy
add_header Content-Security-Policy
  "default-src 'self';
   script-src 'self' https://trusted.cdn.com;
   style-src 'self' https://fonts.googleapis.com;
   img-src 'self' data: https:;
   connect-src 'self' https://api.example.com;
   font-src 'self' https://fonts.gstatic.com;
   frame-ancestors 'none';
   object-src 'none';
   base-uri 'self'" always;

Node.js / Express (with nonce generation)

Node.js / Express
const crypto = require('crypto');

// Middleware: generate a unique nonce per request
app.use((req, res, next) => {
  // 16 random bytes encoded as base64
  res.locals.cspNonce = crypto.randomBytes(16)
    .toString('base64');

  const nonce = res.locals.cspNonce;

  res.setHeader('Content-Security-Policy', [
    "default-src 'self'",
    `script-src 'self' 'nonce-${nonce}' https://trusted.cdn.com`,
    `style-src 'self' 'nonce-${nonce}' https://fonts.googleapis.com`,
    "img-src 'self' data: https:",
    "connect-src 'self' https://api.example.com",
    "font-src 'self' https://fonts.gstatic.com",
    "frame-ancestors 'none'",
    "object-src 'none'",
    "base-uri 'self'"
  ].join('; '));

  next();
});

// In your template, reference the nonce:
// <script nonce="<%= cspNonce %>">...</script>

CSP Violation Monitoring and Reporting#

CSP violation reporting lets you collect real-world data about blocked resources from actual users. This is essential for catching issues that only appear in specific browsers, with particular ad blockers, or on pages you did not test during development.

Setting Up a Reporting Endpoint

Add the report-uri or report-to directive to your CSP. When the browser blocks a resource, it sends a JSON report to the URL you specify. The newer report-to directive uses the Reporting API and is supported in Chromium browsers, while report-uri has broader compatibility.

CSP reporting directives
# Using report-uri (broad browser support)
Content-Security-Policy: default-src 'self';
  report-uri /csp-violation-report

# Using report-to (newer Reporting API)
Content-Security-Policy: default-src 'self';
  report-to csp-endpoint
Report-To: {"group":"csp-endpoint",
  "max_age":86400,
  "endpoints":[{"url":"/csp-violation-report"}]}

Processing Violation Reports

A typical CSP report includes the violated directive, the blocked URI, the document URI, and a referrer. Here is a simple Express endpoint that receives and logs these reports.

Express endpoint
app.post('/csp-violation-report',
  express.json({ type: 'application/csp-report' }),
  (req, res) => {
    const report = req.body['csp-report'];
    console.log('CSP Violation:', {
      directive: report['violated-directive'],
      blocked: report['blocked-uri'],
      page: report['document-uri']
    });
    res.status(204).end();
  }
);
  • Filter out browser extension violations (blocked-uri often starts with "moz-extension:" or "chrome-extension:")
  • Group repeated violations by directive and blocked URI to find patterns
  • Set up alerts for sudden spikes in violations, which may indicate an attempted injection
Was this helpful?
Share

Analyze Your Content Security Policy

Scan your website to see how your CSP stacks up and get specific recommendations for improvement.