October 14, 2026
A Field Guide to Content Security Policy (CSP)
What is CSP (and what is it not)?

By Osara Bandara
3 min read
What is CSP (and what is it not)?
At its core, CSP is a list of who the browser is allowed to trust delivered as an HTTP response header, enforced by the browser on the document it came with.
That framing matters because of what CSP is not,
- It is not input validation.
- It does not sanitize anything.
- It does not stop an attacker from injecting a string into your page.
Instead, It is a second line of defense. when an injected script attempts to run, the browser checks whether your policy allows it, and if not blocks execution. Your XSS vulnerability still exists. It just no longer executes.
Basic Syntax
Directives are separated by semicolons. Each directive names a resource type and lists the sources allowed for it.
Content-Security-Policy:
default-src 'none';
script-src 'self';
img-src 'self' data:Content-Security-Policy:
default-src 'none';
script-src 'self';
img-src 'self' data:There is a second header, Content-Security-Policy-Report-Only, which uses identical syntax but logs violations to a reporting endpoint instead of blocking resources. Every CSP rollout should start there.
Understanding how directives fall into four primary groups makes the spec much easier to manage.
1. Fetch directives: where resources may come from
These control where resources can be fetched from. Crucially, all fetch directives fall back to default-src when unspecified.
script-srcβ JavaScript (inline scripts, workers, andeval)style-srcβ CSS (<style>blocks and inline attributes)img-srcβ Images and faviconsfont-srcβ Web fonts (@font-face)connect-srcβ XHR,fetch, WebSockets,EventSource, andsendBeaconmedia-srcβ<audio>,<video>, and<track>object-srcβ<object>,<embed>, and legacy pluginsframe-srcβ Permitted origins for<iframe>elementsworker-srcβ Dedicated, shared, and service workersmanifest-srcβ Web app manifests
Setting default-src 'none' creates a strict deny-first posture. Every resource type is blocked until explicitly allowed.
2. Document and navigation directives: what the page may do
Note: These directives do not fall back to default-src.
base-uriβ Constrains<base href>. An injected<base>tag can quietly redirect all relative script URLs to an attacker's domain.base-uri 'none'closes this vector.form-actionβ Constrains form submission targets.frame-ancestorsβ Defines who can embed your page in a frame (the modern replacement forX-Frame-Options).sandboxβ Applies iframe-style restrictions directly to the document.
3. Reporting
report-uri <url>β Deprecated but still widely supported.report-to <group>β The modern spec, wired to theReporting-Endpointsheader. Sending both ensures compatibility across browsers.
4. Hardening extras
upgrade-insecure-requestsβ Automatically rewriteshttp://subresource requests to https://.require-trusted-types-for 'script'β Requires DOM injection sinks (likeinnerHTMLandeval) to go through named policy functionsβthe strongest anti-XSS tool available in modern browsers.
Source expressions: the vocabulary
Values inside a directive come from a small set of expressions:
'none'blocks everything. It must be the only value.'self'means the document's own origin: exact scheme, host, and port.'https://example.com'-*.example.com,https:are host and scheme matches.'unsafe-inline'allows inline<script>and<style>, which is the thing CSP exists to prevent.'unsafe-eval'allowseval,new Function, and string-argument timers.'nonce-...'allows an inline block that carries a matchingnonceattribute.'sha256-...'allows an inline block whose exact content hashes to that value.'strict-dynamic'trusts scripts loaded by an already-trusted script.
Now, the deep dive
1. default-src Has Blind Spots
Because base-uri, form-action, frame-ancestors, and sandbox do not fall back to default-src, a policy consisting solely of default-src 'none' will still allow an injected <base> tag or permit third-party framing.
2. Nonces Silently Disable 'unsafe-inline'
When a browser sees a nonce or a hash, it intentionally ignores 'unsafe-inline' in that directive for backward compatibility. However, if a build pipeline automatically injects nonces into a directive where you intentionally preserved 'unsafe-inline', it will break inline styles or scripts without an explicit syntax error.
3. Host Allowlists Are Mostly Ineffective
An empirical security study by Google researchers (Weichselbaum et al., ACM CCS) analyzing over 1.6 million hosts and 26,011 unique policies found that 94.68% of host-based script policies were bypassable, often via JSONP endpoints or unpatched library copies hosted on trusted CDNs.
- Takeaway: Move away from domain allowlists and adopt nonce-based policies with
'strict-dynamic'.
4. style-src and Inline Attributes
CSP Level 3 splits script and style directives into element and attribute variants (e.g., style-src-elem vs. style-src-attr). Because inline style="" attributes cannot carry a nonce, frameworks relying on inline styles (like React or jQuery) often require style-src-attr 'unsafe-inline'. Splitting the directive lets you allow attribute styles without loosening <style> element protections.
5. Caching HTML Breaks Nonces
A nonce is only secure if it is unique per HTTP response. Serving HTML containing nonces through a CDN or shared edge cache allows attackers to read and reuse the cached nonce.
- Rule: If you cache HTML at the edge, use hashes instead of nonces.
6. Violation Report Noise
Browser extensions routinely inject scripts into pages, triggering CSP violation reports. Filter out schemes like chrome-extension://, moz-extension://, and safari-extension:// at your ingestion endpoint to keep your security metrics clean.
Summary
Content Security Policy is often misunderstood as a primary defense, but its true power lies in defense-in-depth. By assuming XSS will eventually happen and restricting what an attacker can actually do with an injection, CSP turns catastrophic vulnerabilities into harmless browser warnings.
Start with default-src 'none', leverage nonces with 'strict-dynamic', test thoroughly in Report-Only mode, and give your reporting endpoints time to baseline before flipping the switch to enforcement.