Content Security Policy: The Complete Guide for 2026

Content Security Policy, usually called CSP, is one of the most effective browser-side defenses against cross-site scripting, malicious third-party script execution, data injection, and a wide range of content loading abuses. When configured well, CSP reduces the damage an attacker can do even if some unsafe markup or script reaches the page.

CSP is not a replacement for output encoding, input validation, secure templating, sandboxing, dependency review, or HTTP-only cookies. It is a powerful second line of defense. It tells the browser which resources are allowed to load, which scripts may execute, where forms may submit, whether the page may be framed, and where violations should be reported.

For modern applications, especially those using JavaScript frameworks, CDNs, analytics tools, tag managers, and embedded widgets, CSP can be challenging to deploy. The policy language is strict, and a single missing source can break functionality. The good news is that CSP is mature, well supported, and practical when rolled out in stages.

This guide covers:

  • What CSP is and why it matters
  • How CSP is delivered
  • Every major directive and source expression
  • Nonces, hashes, and strict-dynamic
  • Reporting and rollout strategy
  • Common mistakes
  • Real examples
  • Framework guidance for Express, Next.js, Nginx, Apache, Django, Rails, Spring, and more
  • Testing with tools such as headertest.com

What is Content Security Policy?

Content Security Policy is an HTTP response header, or sometimes a <meta> tag, that instructs the browser which content sources are trusted. The browser enforces those rules before loading or executing content.

A basic CSP header looks like this:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'

That policy says:

  • By default, only load resources from the same origin
  • Only execute scripts from the same origin
  • Never load plugins such as Flash via object
  • Only allow the document base URL to be set from the same origin

CSP mainly helps against:

  • Reflected XSS
  • Stored XSS
  • DOM XSS, in many cases
  • Data exfiltration through unauthorized endpoints
  • Malicious third-party content inclusion
  • Clickjacking, via legacy CSP frame controls and modern frame controls
  • Injection of unsafe inline JavaScript
  • Some forms of CSS and media abuse

Why CSP matters in 2026

The modern frontend stack loads code from many places:

  • First-party bundles
  • CDN-hosted libraries
  • Fonts
  • Analytics
  • A/B testing tools
  • Payment widgets
  • Video embeds
  • Customer support chat
  • Ad tech
  • Micro-frontends

Each external dependency expands the attack surface. If a third-party script is compromised, or if an attacker finds an injection point, CSP can block unauthorized execution and limit outbound connections.

CSP is especially valuable when paired with:

  • Trusted Types
  • Subresource Integrity
  • Dependency pinning
  • Strong cookie settings
  • Server-side output encoding
  • Secure build pipelines

How CSP is delivered

CSP can be sent in two main ways.

1. HTTP response header

This is the preferred method.

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-rAnd0m'; object-src 'none'; base-uri 'self'

For report-only mode:

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-to csp-endpoint; report-uri https://example.com/csp-report

2. Meta tag

Useful when headers are hard to control, though less capable and not ideal for production-first deployment.

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; object-src 'none'">

Limitations of meta-delivered CSP include:

  • It applies only after the parser sees it
  • It cannot support some reporting and framing controls as reliably as headers
  • It is easier to bypass if an attacker can inject markup before the tag

Use headers whenever possible.

CSP syntax basics

A CSP policy is a semicolon-separated list of directives.

Content-Security-Policy: default-src 'self'; img-src 'self' https://images.example.com; script-src 'self' 'nonce-abc123'

Each directive has one or more source expressions.

Common source expressions include:

  • 'self' — same origin
  • 'none' — allow nothing
  • 'unsafe-inline' — allow inline script or style, dangerous
  • 'unsafe-eval' — allow eval() and similar constructs, dangerous
  • 'strict-dynamic' — trust scripts loaded by nonce or hash trusted scripts
  • 'report-sample' — include code sample in violation reports
  • https: — any HTTPS origin
  • data: — allow data: URLs
  • blob: — allow blob: URLs
  • mediastream: — allow media stream URLs
  • filesystem: — legacy and uncommon
  • https://cdn.example.com — a specific host
  • *.example.com — wildcard subdomains
  • 'nonce-<value>' — allow inline script or style with matching nonce
  • 'sha256-<base64hash>' — allow exact inline script or style by hash

Example:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-3x4mp13';
  style-src 'self' https://fonts.googleapis.com;
  font-src 'self' https://fonts.gstatic.com;
  img-src 'self' data: https:;
  connect-src 'self' https://api.example.com;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests

The most important CSP concepts

default-src

default-src is the fallback for many fetch directives when a more specific directive is not set.

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

If you define img-src, that overrides default-src for images. If you do not define font-src, fonts fall back to default-src.

A common safe baseline:

Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'

script-src

Controls JavaScript sources.

Content-Security-Policy: script-src 'self' https://cdn.example.com

This directive governs:

  • External scripts
  • Inline scripts
  • Event handler attributes like onclick
  • JavaScript URLs in many cases
  • Eval-like execution, depending on keywords

Blocking inline scripts is one of CSP’s biggest security wins.

Unsafe example:

Content-Security-Policy: script-src 'self' 'unsafe-inline' 'unsafe-eval'

Safer example using nonces:

Content-Security-Policy: script-src 'self' 'nonce-r4nd0m'

HTML:

<script nonce="r4nd0m">
  window.appConfig = { apiBase: "/api" };
</script>
<script nonce="r4nd0m" src="/static/app.js"></script>

style-src

Controls CSS sources.

Content-Security-Policy: style-src 'self' https://fonts.googleapis.com

Inline styles are often used by frameworks and component libraries. Allowing 'unsafe-inline' in style-src is less severe than in script-src, but still weakens protection and can enable CSS injection attacks.

Nonce-based style allowance:

Content-Security-Policy: style-src 'self' 'nonce-r4nd0m' https://fonts.googleapis.com

HTML:

<style nonce="r4nd0m">
  .critical { display: block; }
</style>

img-src

Controls image sources.

Content-Security-Policy: img-src 'self' data: https://images.example.com

Many sites need data: here for inline images, QR codes, or base64 placeholders. Keep it as narrow as possible.

font-src

Controls font loading.

Content-Security-Policy: font-src 'self' https://fonts.gstatic.com

If you use Google Fonts, a common pair is:

style-src 'self' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;

connect-src

Controls network connections initiated by script APIs.

This includes:

  • fetch()
  • XMLHttpRequest
  • WebSocket
  • EventSource
  • sendBeacon
  • navigator.connect-style APIs where applicable

Example:

Content-Security-Policy: connect-src 'self' https://api.example.com wss://ws.example.com

This directive is critical for limiting data exfiltration.

media-src

Controls audio and video sources.

Content-Security-Policy: media-src 'self' https://media.example.com

object-src

Controls plugins loaded via <object>, <embed>, and <applet>.

Almost all modern sites should set:

Content-Security-Policy: object-src 'none'

This is one of the highest-value directives.

child-src

Historically controlled nested browsing contexts and workers. In modern CSP, more specific directives such as frame-src and worker-src are preferred. Some legacy behavior still exists, so you may see it in older policies.

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

For modern deployments, prefer:

  • frame-src
  • worker-src

frame-src

Controls what the page may embed in frames.

Content-Security-Policy: frame-src 'self' https://www.youtube.com https://player.vimeo.com

Useful for embedded video, payment widgets, and dashboards.

frame-ancestors

Controls who may frame your page. This is one of the primary anti-clickjacking controls.

Content-Security-Policy: frame-ancestors 'none'

Or allow only your own origin:

Content-Security-Policy: frame-ancestors 'self'

Or specific partners:

Content-Security-Policy: frame-ancestors 'self' https://partner.example.com

Unlike many fetch directives, frame-ancestors does not fall back to default-src.

It is better than old X-Frame-Options, though many sites send both for compatibility.

worker-src

Controls worker, shared worker, and service worker script sources.

Content-Security-Policy: worker-src 'self' blob:

Some build tools and apps use blob: workers. If possible, avoid broad allowances unless required.

manifest-src

Controls web app manifest sources.

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

base-uri

Controls the allowed <base> URL.

Content-Security-Policy: base-uri 'self'

This helps prevent attackers from changing how relative URLs resolve.

form-action

Controls where forms may submit.

Content-Security-Policy: form-action 'self' https://payments.example.com

This is valuable for preventing silent form redirection to attacker-controlled endpoints.

Controls where the document may navigate itself in some cases. Browser support has historically been mixed, so verify current support before relying on it as a primary control.

Content-Security-Policy: navigate-to 'self'

sandbox

Applies restrictions similar to the iframe sandbox attribute.

Content-Security-Policy: sandbox allow-forms allow-scripts

This is mostly used for highly restricted content contexts.

plugin-types

Legacy directive restricting plugin MIME types. Rarely relevant today.

Content-Security-Policy: plugin-types application/pdf

require-trusted-types-for

Requires Trusted Types for dangerous DOM injection sinks in supported browsers.

Content-Security-Policy: require-trusted-types-for 'script'

This is a major hardening control for modern JavaScript apps.

trusted-types

Defines allowed Trusted Types policy names.

Content-Security-Policy: trusted-types default dompurify myAppPolicy

Trusted Types deserves its own full guide, but it fits naturally with CSP.

report-uri

Legacy reporting directive that sends violation reports to a URL.

Content-Security-Policy: report-uri https://example.com/csp-report

Still commonly used for compatibility.

report-to

Modern reporting directive using the Reporting API.

Content-Security-Policy: report-to csp-endpoint
Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://example.com/csp-report"}]}

Many deployments send both report-uri and report-to.

upgrade-insecure-requests

Instructs the browser to rewrite HTTP subresource requests to HTTPS.

Content-Security-Policy: upgrade-insecure-requests

Useful during HTTPS migrations and mixed content cleanup.

block-all-mixed-content

Blocks mixed content. It has become less central as browsers enforce strict mixed-content handling, but it may still appear in policies.

Content-Security-Policy: block-all-mixed-content

script-src-elem, script-src-attr, style-src-elem, style-src-attr

These finer-grained directives allow separate control over elements and attributes.

script-src-elem

Controls scripts in <script> elements.

Content-Security-Policy: script-src-elem 'self' https://cdn.example.com

script-src-attr

Controls inline event handler attributes such as onclick.

Content-Security-Policy: script-src-attr 'none'

This is very useful for blocking handler attributes even if other script allowances exist.

style-src-elem

Controls styles from <style> and <link rel="stylesheet">.

Content-Security-Policy: style-src-elem 'self' https://fonts.googleapis.com

style-src-attr

Controls style="" attributes.

Content-Security-Policy: style-src-attr 'none'

These directives are excellent for tightening modern apps.

Source expressions in detail

'self'

Allows same-origin resources.

script-src 'self'

'none'

Allows nothing. Best used as the sole value in a directive.

object-src 'none'

Scheme sources

Allow any origin for a scheme.

img-src https:

This is broad. Prefer specific hosts when possible.

Host sources

script-src https://cdn.example.com
img-src https://*.examplecdn.com

Be careful with wildcards. *.example.com trusts every subdomain.

data:

Allows inline data URLs.

img-src 'self' data:

Avoid data: in script-src and use caution elsewhere.

blob:

Often needed for workers, media, or generated downloads.

worker-src 'self' blob:

'unsafe-inline'

Allows inline scripts or styles. This is one of the most dangerous allowances in CSP.

script-src 'self' 'unsafe-inline'

Avoid in script-src whenever possible.

'unsafe-eval'

Allows dynamic code evaluation.

script-src 'self' 'unsafe-eval'

Used by some dev tools, template compilers, older frameworks, and source map tooling. Remove in production if at all possible.

'wasm-unsafe-eval'

Allows WebAssembly compilation/evaluation in contexts where needed.

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

Use only if your app truly requires it.

'nonce-...'

Allows specific inline scripts or styles with the matching nonce attribute. The nonce must be unpredictable and unique per response.

Content-Security-Policy: script-src 'self' 'nonce-abc123'

HTML:

<script nonce="abc123">
  window.__BOOTSTRAP__ = {"user":"alice"};
</script>

'sha256-...', 'sha384-...', 'sha512-...'

Allows exact inline content whose hash matches.

Content-Security-Policy: script-src 'self' 'sha256-SRf8+...'

Good for static inline snippets that rarely change.

'strict-dynamic'

A powerful keyword for nonce- or hash-based script trust. When used with a trusted nonce or hash, scripts loaded by those trusted scripts are also allowed, and host allowlists are de-emphasized in supporting browsers.

Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'self'

This is one of the best modern CSP patterns for script-heavy apps.

'unsafe-hashes'

Allows specific inline event handlers or style attributes by hash in certain scenarios. Better than broad inline allowance, but still niche and easy to misuse.

'report-sample'

Includes a sample of violating code in reports.

Content-Security-Policy: script-src 'self' 'report-sample'; report-uri https://example.com/csp-report

Nonces vs hashes

Both are used to allow inline code without enabling all inline code.

Nonces

Best for dynamic pages.

  • Generated fresh for each response
  • Added to the header
  • Added to allowed <script> and <style> tags

Server example in Node.js:

import crypto from "crypto";

function generateNonce() {
  return crypto.randomBytes(16).toString("base64");
}

Header:

Content-Security-Policy: script-src 'self' 'nonce-AbCdEf123'; style-src 'self' 'nonce-AbCdEf123'

HTML:

<script nonce="AbCdEf123">
  window.csrfToken = "token-value";
</script>

Hashes

Best for static inline snippets.

Example script:

<script>
  window.appVersion = "2026.03.29";
</script>

Generate a SHA-256 hash of the exact contents, then add it to the policy:

Content-Security-Policy: script-src 'self' 'sha256-Base64EncodedHashHere'

Any whitespace change changes the hash.

Which should you use?

  • Use nonces for server-rendered dynamic pages
  • Use hashes for static pages or fixed inline snippets
  • Use strict-dynamic with nonces for advanced modern deployments

Minimal baseline policy

A practical baseline for many sites:

Content-Security-Policy:
  default-src 'self';
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';
  form-action 'self';
  upgrade-insecure-requests

Strong modern policy for app pages

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic';
  style-src 'self' 'nonce-{RANDOM}';
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.example.com wss://ws.example.com;
  frame-src https://www.youtube.com;
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  require-trusted-types-for 'script';
  report-uri https://example.com/csp-report;
  report-to csp-endpoint;
  upgrade-insecure-requests

Static marketing site policy

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com;
  style-src 'self' https://fonts.googleapis.com;
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://www.google-analytics.com;
  frame-src https://www.youtube.com;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';
  form-action 'self'

Report-only rollout policy

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' https://cdn.example.com 'report-sample';
  style-src 'self' https://fonts.googleapis.com;
  img-src 'self' data: https:;
  connect-src 'self' https://api.example.com;
  object-src 'none';
  base-uri 'self';
  report-uri https://example.com/csp-report

Common CSP mistakes

1. Using 'unsafe-inline' in script-src

This defeats one of the biggest benefits of CSP.

2. Using broad wildcards

script-src https: *

This is usually too permissive.

3. Forgetting object-src 'none'

A simple, high-value protection often omitted.

4. Forgetting base-uri 'self'

Attackers can abuse <base> to rewrite relative URLs.

5. Relying only on allowlists for modern script-heavy apps

Nonce + strict-dynamic is often more robust than giant host lists.

6. Not accounting for connect-src

Apps may break because API, WebSocket, analytics, or beacon endpoints are missing.

7. Applying one policy to every route

Different routes often need different policies. Admin pages, public pages, docs pages, and embedded widgets may have different requirements.

8. Ignoring reports

Report-only mode is useful only if someone reviews the data.

9. Using meta tags as the main deployment path

Headers are stronger and more reliable.

10. Reusing nonces across responses

Nonce values should be unique per response.

CSP and third-party services

Third-party integrations are the biggest source of CSP complexity.

Typical services that need explicit sources:

  • Google Analytics
  • Google Tag Manager
  • Stripe
  • PayPal
  • YouTube
  • Vimeo
  • Intercom
  • Zendesk
  • Sentry
  • Hotjar
  • Segment
  • Cloudflare Insights

Example with Stripe:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://js.stripe.com;
  frame-src https://js.stripe.com https://hooks.stripe.com;
  connect-src 'self' https://api.stripe.com;
  img-src 'self' data: https:;
  style-src 'self' 'unsafe-inline';
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none'

Always verify the latest integration requirements from the vendor because domains change.

Reporting and monitoring

CSP reporting helps you discover:

  • Broken resource loads
  • Unexpected third-party behavior
  • Injection attempts
  • Misconfigured routes

report-uri example endpoint

Express example:

import express from "express";

const app = express();

app.use(express.json({ type: ["application/json", "application/csp-report"] }));

app.post("/csp-report", (req, res) => {
  console.log("CSP report:", JSON.stringify(req.body, null, 2));
  res.status(204).end();
});

report-to example

Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://example.com/csp-report"}]}
Content-Security-Policy: default-src 'self'; report-to csp-endpoint; report-uri https://example.com/csp-report

Reports can be noisy. Expect:

  • Browser extensions causing violations
  • Old cached assets
  • Dev-only tools
  • Third-party scripts making unexpected requests

Filter and aggregate before alerting.

How to deploy CSP safely

Phase 1: Inventory everything

List all resource types and origins used by the application:

  • Scripts
  • Styles
  • Images
  • Fonts
  • API endpoints
  • WebSockets
  • Frames
  • Workers
  • Manifests
  • Form destinations

Phase 2: Start with report-only

Deploy:

Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'self'; report-uri https://example.com/csp-report

Then tighten based on observed behavior.

Phase 3: Remove inline script and eval

Refactor:

  • Inline event handlers like onclick
  • Inline <script> blocks
  • eval()
  • new Function()
  • Dangerous template compilers in production

Phase 4: Add nonces or hashes

Move to a strict script policy.

Phase 5: Enforce

Switch from Content-Security-Policy-Report-Only to Content-Security-Policy.

Phase 6: Maintain

Update CSP whenever:

  • Vendors change domains
  • New services are added
  • Build tooling changes asset loading
  • Framework behavior changes

Framework and server examples

Express.js with Helmet

Helmet makes CSP setup much easier.

npm install helmet
import express from "express";
import helmet from "helmet";
import crypto from "crypto";

const app = express();

app.use((req, res, next) => {
  res.locals.cspNonce = crypto.randomBytes(16).toString("base64");
  next();
});

app.use(
  helmet({
    contentSecurityPolicy: {
      useDefaults: false,
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: [
          "'self'",
          (req, res) => `'nonce-${res.locals.cspNonce}'`,
          "'strict-dynamic'",
        ],
        styleSrc: ["'self'", (req, res) => `'nonce-${res.locals.cspNonce}'`],
        imgSrc: ["'self'", "data:", "https:"],
        fontSrc: ["'self'", "https://fonts.gstatic.com"],
        connectSrc: ["'self'", "https://api.example.com"],
        objectSrc: ["'none'"],
        baseUri: ["'self'"],
        formAction: ["'self'"],
        frameAncestors: ["'none'"],
        upgradeInsecureRequests: [],
      },
    },
  })
);

app.get("/", (req, res) => {
  const nonce = res.locals.cspNonce;
  res.send(`
    <!doctype html>
    <html>
      <head>
        <style nonce="${nonce}">body{font-family:sans-serif}</style>
      </head>
      <body>
        <h1>Hello</h1>
        <script nonce="${nonce}">
          console.log("CSP nonce works");
        </script>
      </body>
    </html>
  `);
});

app.listen(3000);

Nginx

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

For report-only:

add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri https://example.com/csp-report" always;

Apache

Header always set Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'"

Next.js

Next.js often requires careful nonce handling, especially with server rendering and inline hydration data.

Middleware/header example

// next.config.js
const securityHeaders = [
  {
    key: "Content-Security-Policy",
    value: [
      "default-src 'self'",
      "script-src 'self' 'unsafe-inline' 'unsafe-eval'",
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "font-src 'self' https://fonts.gstatic.com",
      "connect-src 'self' https://api.example.com",
      "object-src 'none'",
      "base-uri 'self'",
      "frame-ancestors 'none'",
    ].join("; "),
  },
];

module.exports = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: securityHeaders,
      },
    ];
  },
};

That example is easy to start with, but not ideal. A stronger setup uses per-request nonces and framework support for applying them to scripts. In modern Next.js apps, the exact implementation depends on whether you use the App Router, custom server, edge middleware, and how third-party scripts are injected. The core goal is:

  • Generate a nonce per request
  • Put it in the CSP header
  • Pass it to every allowed script and style tag
  • Remove 'unsafe-inline' from script-src

React

React itself does not require unsafe inline script execution, but many apps inject bootstrap state or use third-party packages that do. Watch for:

  • dangerouslySetInnerHTML
  • Inline state hydration blocks
  • Dev tooling requiring 'unsafe-eval'
  • CSS-in-JS libraries inserting style tags

Nonce those tags where supported.

Angular

Angular has strong built-in XSS defenses, but CSP can still be useful. Older Angular workflows sometimes relied on unsafe eval in development. Production builds should avoid it. Pair Angular with Trusted Types when possible.

Example header:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{RANDOM}';
  style-src 'self' 'nonce-{RANDOM}';
  object-src 'none';
  base-uri 'self';
  require-trusted-types-for 'script'

Vue

Vue production builds generally work well with CSP, though template compilation at runtime and some plugins may require looser settings. Prefer precompiled templates and avoid runtime compilation in production.

Django

Using django-csp is common.

pip install django-csp
INSTALLED_APPS = [
    # ...
    "csp",
]

MIDDLEWARE = [
    # ...
    "csp.middleware.CSPMiddleware",
]

CONTENT_SECURITY_POLICY = {
    "DIRECTIVES": {
        "default-src": ["'self'"],
        "script-src": ["'self'"],
        "style-src": ["'self'", "https://fonts.googleapis.com"],
        "font-src": ["'self'", "https://fonts.gstatic.com"],
        "img-src": ["'self'", "data:", "https:"],
        "object-src": ["'none'"],
        "base-uri": ["'self'"],
        "frame-ancestors": ["'none'"],
        "form-action": ["'self'"],
    }
}

For nonce-based policies, generate a nonce per request and expose it to templates.

Ruby on Rails

Rails has CSP support built in.

# config/initializers/content_security_policy.rb
Rails.application.configure do
  config.content_security_policy do |policy|
    policy.default_src :self
    policy.script_src  :self
    policy.style_src   :self, "https://fonts.googleapis.com"
    policy.font_src    :self, "https://fonts.gstatic.com"
    policy.img_src     :self, :https, :data
    policy.object_src  :none
    policy.base_uri    :self
    policy.frame_ancestors :none
    policy.form_action :self
  end

  # config.content_security_policy_report_only = true
end

Nonce support is also available in Rails for inline tags.

Spring Security

http
  .headers(headers -> headers
    .contentSecurityPolicy(csp -> csp
      .policyDirectives("default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'")
    )
  );

For more advanced nonce-based policies, generate the nonce in a filter and expose it to templates.

ASP.NET Core

app.Use(async (context, next) =>
{
    context.Response.Headers["Content-Security-Policy"] =
        "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'";
    await next();
});

Libraries such as NWebsec can simplify advanced setups.

Laravel

In Laravel, CSP is often set through custom middleware.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class ContentSecurityPolicy
{
    public function handle(Request $request, Closure $next)
    {
        $response = $next($request);

        $response->headers->set(
            'Content-Security-Policy',
            "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'"
        );

        return $response;
    }
}

Static site generators and CDNs

For Hugo, Jekyll, Astro, Eleventy, Gatsby, and similar tools, CSP is usually delivered at the CDN, reverse proxy, or hosting layer:

  • Netlify headers file
  • Vercel config
  • Cloudflare rules
  • Nginx/Apache
  • S3 + CloudFront response headers policy

Example _headers file for Netlify:

/*
  Content-Security-Policy: default-src 'self'; img-src 'self' data: https:; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

Testing CSP

You should test CSP in several ways:

  • Browser developer tools
  • Manual page walkthroughs
  • Automated integration tests
  • Violation reporting
  • Header scanners

A quick way to inspect and validate your headers is headertest.com. It helps confirm whether the CSP header is present and highlights related security header issues.

Browser testing workflow

  1. Open the page in a browser
  2. Open DevTools Console
  3. Look for CSP violation messages
  4. Check the Network tab for blocked resources
  5. Test major user flows:
    • Login
    • Checkout
    • Search
    • Video playback
    • Embedded widgets
    • Analytics events
    • WebSocket features

Example Playwright check

import { test, expect } from "@playwright/test";

test("CSP header is set", async ({ page }) => {
  const response = await page.goto("https://example.com");
  const csp = response.headers()["content-security-policy"];
  expect(csp).toContain("default-src 'self'");
  expect(csp).toContain("object-src 'none'");
});

CSP bypass considerations

CSP is strong, but not magic.

Potential weaknesses include:

  • Allowing 'unsafe-inline'
  • Allowing 'unsafe-eval'
  • Overly broad host allowlists
  • Trusting compromised third-party origins
  • JSONP endpoints on allowed domains
  • Script gadgets in trusted code
  • Browser differences
  • DOM XSS in code paths not well constrained by policy
  • Missing Trusted Types in complex apps

If you allow a powerful third-party script, that script can often load more code, read page data, and act with the privileges of your page. CSP reduces risk but does not make third-party code safe.

CSP and Subresource Integrity

Subresource Integrity, or SRI, verifies that a fetched script or stylesheet matches an expected cryptographic hash.

Example:

<script
  src="https://cdn.example.com/library.min.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GhEXAMPLE"
  crossorigin="anonymous"></script>

CSP and SRI work well together:

  • CSP restricts where resources may load from
  • SRI verifies the exact file content

Use both when loading static third-party assets from CDNs.

CSP and Trusted Types

Trusted Types protects dangerous DOM injection sinks such as:

  • innerHTML
  • outerHTML
  • insertAdjacentHTML
  • eval-adjacent script creation patterns in supported browsers

Example:

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default dompurify

This is especially useful in large single-page apps where DOM XSS is a major concern.

A practical hardened policy template

For many modern applications, this is a strong starting point:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic' 'report-sample';
  script-src-attr 'none';
  style-src 'self' 'nonce-{RANDOM}' https://fonts.googleapis.com;
  style-src-attr 'none';
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.example.com wss://ws.example.com;
  frame-src https://www.youtube.com https://js.stripe.com;
  worker-src 'self' blob:;
  manifest-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  require-trusted-types-for 'script';
  report-uri https://example.com/csp-report;
  report-to csp-endpoint;
  upgrade-insecure-requests

Adjust per route and per application need.

Final checklist

Before enforcing CSP, verify that you have:

  • Set object-src 'none'
  • Set base-uri 'self'
  • Set frame-ancestors 'none' or a tight allowlist
  • Defined form-action
  • Removed inline script where possible
  • Removed eval-like behavior where possible
  • Added nonces or hashes for remaining inline code
  • Reviewed all third-party domains
  • Configured connect-src
  • Enabled report-only mode first
  • Collected and reviewed violation reports
  • Tested in real browsers
  • Verified the deployed header with headertest.com

A well-designed CSP takes effort, but it pays off. It limits the blast radius of injection flaws, makes unsafe patterns visible, and raises the bar significantly for attackers targeting modern web applications.