CSP Explained: A Practical Guide to Content Security Policy
By Krithi
If you've ever opened your browser's developer console and seen a wall of Content-Security-Policy violations, or inherited a codebase where someone just set default-src * and called it a day, you already know the problem. Content Security Policy is one of the most powerful browser security mechanisms available — and one of the most routinely misunderstood. This guide cuts through the noise and gives you a clear, working understanding of what CSP does, how to write a sensible policy, and how to keep it healthy over time.
What Is Content Security Policy?
Content Security Policy (CSP) is an HTTP response header that tells the browser which sources of content — scripts, stylesheets, images, fonts, frames, and more — are allowed to load on a given page. Any resource that doesn't match the policy is blocked before it can execute or render.
The core idea is straightforward: instead of relying solely on the server to serve clean content, you explicitly whitelist what's permitted. This creates a strong second line of defence against cross-site scripting (XSS), clickjacking, mixed-content injection, and data exfiltration attacks.
CSP is delivered as a response header:
` Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none' `
Or, during development and rollout, as a report-only header that logs violations without blocking anything:
` Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reports `
The browser interprets the header, enforces (or observes) the policy, and — if you've set up a reporting endpoint — sends JSON violation reports whenever something is blocked.
Why CSP Matters for Real-World Security
XSS remains one of the most common web vulnerabilities. An attacker who finds a way to inject arbitrary HTML or JavaScript into your page can steal session cookies, capture form inputs, redirect users, or silently beacon data to an external server.
A well-crafted CSP doesn't eliminate XSS bugs in your code, but it severely limits what an attacker can do with them. Even if malicious JavaScript is injected, the browser won't execute it if the script source isn't explicitly permitted. That's a meaningful reduction in blast radius.
Beyond XSS, CSP also:
- Blocks mixed content — prevents HTTP resources from loading on HTTPS pages, protecting transport-layer security.
- Prevents clickjacking — the
frame-ancestorsdirective replaces the olderX-Frame-Optionsheader and gives you more granular control.
- Limits data exfiltration —
connect-srcrestricts where JavaScript can send data viafetch,XMLHttpRequest, and WebSockets.
!Diagram showing how a browser enforces CSP directives to block unauthorised scripts and connections
CSP Directives You Actually Need to Know
The CSP specification has dozens of directives, but most policies rely on a manageable subset.
Fetch Directives
These control where resources can be loaded from:
| Directive | Controls | |---|---| | default-src | Fallback for all resource types not explicitly listed | | script-src | JavaScript, including inline scripts and eval() | | style-src | Stylesheets and inline styles | | img-src | Images and favicons | | font-src | Web fonts | | connect-src | Fetch, XHR, WebSocket, EventSource | | frame-src | sources | | object-src | Plugins (, , ) — set to 'none' unless you have a genuine need | | media-src | Audio and video | | worker-src | Web Workers and Service Workers | | manifest-src | Web App Manifests |
Navigation Directives
form-action— restricts wheresubmissions can go. Often overlooked, but important.
frame-ancestors— controls which origins can embed your page in a frame. ReplacesX-Frame-Options.
Reporting Directives
report-uri(legacy, still widely supported) — a URL to which the browser POSTs violation reports.
report-to(newer, part of the Reporting API) — references a named endpoint defined in theReporting-Endpointsheader.
Source Values and Keywords
Within each directive, you specify one or more source expressions:
'self'— same origin only. The single quotes are required.
'none'— nothing is allowed.
https://cdn.example.com— a specific origin.
https:— any HTTPS URL (broad; use sparingly).
'unsafe-inline'— allows inlineandblocks. Substantially weakens the policy.
'unsafe-eval'— allowseval(),new Function(), and similar dynamic code execution.
'nonce-— allows a specific inline script or style tagged with a matching' nonceattribute. Regenerated per request.
'sha256-— allows an inline resource whose content matches a specific SHA hash.'
The goal of a mature CSP is to avoid 'unsafe-inline' and 'unsafe-eval' entirely. If you have legacy inline scripts you can't immediately refactor, nonces or hashes give you a path to safety without a wholesale rewrite.
Building Your First Meaningful Policy
Don't try to write a perfect policy from scratch. The practical approach:
- Start in report-only mode. Deploy
Content-Security-Policy-Report-Onlywith a reasonably strict policy and areport-uriendpoint. Collect real violation data from your actual traffic. - Analyse violations. Group them by directive and source. Distinguish legitimate third-party resources (analytics, fonts, CDN assets) from genuine policy gaps.
- Refine and promote. Once violations settle to background noise, move the policy to the enforcing
Content-Security-Policyheader. - Iterate. Every time you add a new third-party integration, widget, or CDN, revisit the policy.
A reasonable starting point for a modern web application:
` Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{RANDOM}' https://www.googletagmanager.com; style-src 'self' 'nonce-{RANDOM}' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://api.example.com; frame-ancestors 'none'; object-src 'none'; base-uri 'self'; form-action 'self'; upgrade-insecure-requests; report-uri /csp-violations `
Note base-uri 'self' — this blocks tag injection, which can otherwise redirect all relative URLs. It's cheap insurance.
Common CSP Mistakes to Avoid
Wildcard sources. script-src or default-src defeats the purpose entirely. You might as well not have a policy.
Forgetting object-src. If default-src is set but object-src isn't explicitly listed, Flash and plugin-based exploits remain possible. Always set object-src 'none'.
Trusting entire CDN domains. script-src https://cdn.jsdelivr.net allows any file on that CDN — including attacker-controlled packages. Where possible, use Subresource Integrity (SRI) hashes alongside your CSP.
Deploying without a reporting endpoint. Without violation reports, you're flying blind. You won't know when a new deployment or third-party update breaks your policy.
Never revisiting the policy. CSP decays over time. Third-party scripts change their domains, new features get added, and whitelisted sources no longer serve anything. A stale policy is either too permissive or quietly breaking things for users.
Monitoring Your CSP Health Continuously
Writing a CSP is a one-time effort. Keeping it correct is an ongoing operational concern — and that's where automated monitoring pays for itself.
Uptrue's security headers monitoring checks your CSP (and other critical headers like Strict-Transport-Security, X-Frame-Options, and Permissions-Policy) on a scheduled basis and alerts you the moment a header disappears or changes unexpectedly. If a deployment accidentally strips your Content-Security-Policy header, you'll know within minutes rather than discovering it in a post-incident review.
You can also cross-reference your CSP posture alongside your SSL certificate monitoring to get a complete picture of your site's transport and content security at a glance.
🔎 Check Your Headers Right Now
Curious how your site's CSP looks to the outside world? Run a free scan using Uptrue's Security Headers Checker — no account required. You'll see exactly which headers are present, which are missing, and where your policy can be tightened.
CSP and the Broader Security Headers Picture
CSP is the most complex of the common security headers, but it doesn't operate in isolation. A complete security headers posture includes:
Strict-Transport-Security(HSTS) — forces HTTPS connections, even if a user typeshttp://.
X-Content-Type-Options: nosniff— prevents MIME-type sniffing attacks.
Referrer-Policy— controls how much referrer information is sent with outbound requests.
Permissions-Policy— restricts which browser APIs (camera, microphone, geolocation) scripts can access.
X-Frame-Options— superseded by CSP'sframe-ancestorsbut still worth setting for older browsers.
Getting all of these right — and keeping them right as your infrastructure evolves — is exactly the kind of problem that monitoring was built to solve.
Deploying CSP: Framework and Server Notes
A few quick implementation pointers:
- Nginx: Set headers in your
serverorlocationblock:add_header Content-Security-Policy "..." always;. Thealwaysflag ensures the header is sent on error responses too.
- Apache: Use
Header always set Content-Security-Policy "..."in.htaccessor your VirtualHost config.
- Next.js / Vercel: Use
next.config.jsheaders or middleware to set the header server-side with a fresh nonce on each request.
- WordPress: Plugins can inject the header, but server-level control is more reliable — plugin headers can be stripped by caching layers.
- CDNs (Cloudflare, Fastly): Worker or Transform Rules let you inject or modify headers at the edge, which is useful when you don't control the origin server directly.
Conclusion
CSP is one of the few browser security mechanisms that gives you genuine leverage against the most common class of web vulnerability. It's not a magic fix — bugs in your code still need fixing — but a well-deployed policy dramatically limits what an attacker can do when they find one.
The practical path is straightforward: start in report-only mode, collect violation data, refine the policy, and then enforce it. After that, treat CSP health as an operational metric you monitor continuously, not a checkbox you tick once.
Sign up for Uptrue to get scheduled security header checks, instant alerts on policy changes, and a single dashboard across uptime, SSL, DNS, and security — so you always know your site is serving exactly what you intended.