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'— alloweval()and similar constructs, dangerous'strict-dynamic'— trust scripts loaded by nonce or hash trusted scripts'report-sample'— include code sample in violation reportshttps:— any HTTPS origindata:— allowdata:URLsblob:— allowblob:URLsmediastream:— allow media stream URLsfilesystem:— legacy and uncommonhttps://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()XMLHttpRequestWebSocketEventSourcesendBeaconnavigator.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-srcworker-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.
navigate-to
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-dynamicwith nonces for advanced modern deployments
Recommended CSP patterns
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'fromscript-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
- Open the page in a browser
- Open DevTools Console
- Look for CSP violation messages
- Check the Network tab for blocked resources
- 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:
innerHTMLouterHTMLinsertAdjacentHTMLeval-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.