Content Security Policy Errors: Complete Fix Guide
Quick answer
💡A Content Security Policy error appears in the browser console as Refused to load the script because it violates the following Content Security Policy directive. The fix depends on the resource type: add the domain to the relevant directive (script-src, style-src, img-src), use a nonce or hash instead of unsafe-inline for inline scripts and styles, and test changes in Content-Security-Policy-Report-Only mode before enforcing them in production.
Error symptoms
- ✕
Refused to load the script 'https://cdn.example.com/app.js' because it violates CSP directive script-src - ✕
Refused to apply inline style because it violates CSP directive style-src 'self' - ✕
Refused to load the image 'https://images.example.com/photo.jpg' because it violates img-src - ✕
Refused to connect to 'https://api.example.com' because it violates connect-src directive - ✕
Refused to frame 'https://embed.example.com' because an ancestor violates frame-ancestors - ✕
Refused to evaluate a string as JavaScript because 'unsafe-eval' is not allowed in script-src
Common causes
- •A third-party script tag added to a template without updating script-src in the CSP header
- •Inline styles or onclick handlers used in HTML without adding a nonce or hash to style-src or script-src
- •A new CDN domain added for assets without corresponding img-src or font-src entry
- •Framework-generated inline scripts (Next.js, Angular) not covered by nonce injection
- •Google Analytics or Tag Manager blocked because connect-src or script-src excludes their domains
- •An iframe embed blocked because frame-ancestors does not permit the embedding domain
When it happens
- •After enabling CSP headers for the first time on an existing application with many inline scripts
- •After adding a new analytics, chat, or payment widget without updating the CSP policy
- •After a framework upgrade that changes how inline scripts are injected into the HTML document
- •During A/B testing when new script tags are injected dynamically via tag managers
- •After migrating from HTTP to HTTPS when some resource URLs still use http:// scheme
Examples and fixes
Using unsafe-inline in script-src defeats most of the XSS protection that CSP provides. Replace it with a per-request nonce.
Replacing unsafe-inline with a cryptographic nonce
❌ Wrong
# HTTP response header — unsafe-inline defeats XSS protection
Content-Security-Policy: default-src 'self'; \
script-src 'self' 'unsafe-inline' https://cdn.example.com;
<!-- HTML template -->
<script>
var config = { apiUrl: "https://api.example.com" };
</script>✅ Fixed
# Server generates a random nonce per request
# Node.js: const nonce = crypto.randomBytes(16).toString('base64');
Content-Security-Policy: default-src 'self'; \
script-src 'self' 'nonce-RANDOM_BASE64_HERE' https://cdn.example.com;
<!-- HTML template — nonce injected server-side -->
<script nonce="RANDOM_BASE64_HERE">
var config = { apiUrl: "https://api.example.com" };
</script>The unsafe-inline keyword allows any inline script on the page to execute, which nullifies CSP's primary defence against reflected and stored XSS. An attacker who can inject an inline script can execute arbitrary JavaScript. A nonce is a cryptographically random value generated fresh on every HTTP response. Only script tags carrying the correct nonce attribute will execute. An injected script cannot know the nonce value because it changes on every page load, so XSS payloads are blocked even if they appear in the HTML source.
Enforcing a new CSP policy without testing first breaks real functionality. Use report-only mode to collect violations without blocking anything.
Using report-only mode to audit CSP before enforcing
❌ Wrong
# Enforcing immediately without testing
# This will break real users until all sources are whitelisted
Content-Security-Policy: default-src 'self'; \
script-src 'self'; \
style-src 'self'; \
img-src 'self'
# Result: analytics, fonts, CDN assets all blocked immediately✅ Fixed
# Step 1: Collect violations without blocking (report-only)
Content-Security-Policy-Report-Only: default-src 'self'; \
script-src 'self'; \
style-src 'self'; \
img-src 'self'; \
report-uri /api/csp-reports
# Step 2: Review /api/csp-reports for blocked-uri values
# Step 3: Add legitimate sources to directives
# Step 4: Switch to enforcing Content-Security-Policy headerThe Content-Security-Policy-Report-Only header sends the same CSP policy to the browser but instructs it to report violations without blocking any resources. This lets you discover what your policy would break before rolling it out to real users. The report-uri directive points to an endpoint that receives violation reports as JSON POST bodies. Review these reports over several days across all user flows, add legitimate sources to the policy, then switch the header name from Content-Security-Policy-Report-Only to Content-Security-Policy to begin enforcement.
How CSP Directives Block Resources
Content Security Policy is an HTTP response header (Content-Security-Policy) that tells the browser which origins are permitted to load each type of resource for a given page. The browser enforces this allowlist strictly — any resource not explicitly permitted is blocked, regardless of whether it would be safe. This is intentional design, not a bug. CSP is the primary browser-level defence against cross-site scripting (XSS) attacks, as described in OWASP's XSS Prevention Cheat Sheet.
Each directive controls a different resource type. script-src governs JavaScript files and inline scripts. style-src governs CSS files and inline styles. img-src governs images, including base64 data URIs if you use data:. connect-src governs XMLHttpRequest, fetch, WebSocket, and EventSource connections. font-src governs web font files. frame-src and frame-ancestors control which frames a page can embed and in which frames it can be embedded (the latter is the CSP replacement for the X-Frame-Options header).
The default-src directive acts as a fallback for all directives not explicitly defined. If you set default-src 'self', any resource type not explicitly listed elsewhere is restricted to same-origin. This means adding a third-party image CDN requires an explicit img-src entry even if script-src already allows the CDN domain — directives do not inherit from each other.
The most common cause of CSP errors is adding a new third-party resource — an analytics widget, a payment iframe, a CDN-hosted font — without updating the corresponding CSP directive. Modern web applications routinely load resources from dozens of domains, and the CSP policy must accurately model all of them. Automated CSP audit tools like Mozilla Observatory or the CSP Evaluator from Google can help identify directive gaps.
SPAs (single-page applications) and frameworks like Next.js, Nuxt, and Angular often inject inline scripts into the HTML document for bootstrapping, hydration, or configuration passing. These inline scripts are blocked by a strict script-src 'self' policy because inline scripts are not from an external origin. The solution is nonce injection (generating a random nonce per request and applying it to both the script tag and the CSP header) or hash-based allowlisting.
Reading CSP Violation Reports and Browser Console Errors
Every CSP violation produces a console error in the browser's developer tools with the exact directive that was violated and the blocked URI. For example: Refused to load the script 'https://analytics.example.com/tracker.js' because it violates the following Content Security Policy directive: "script-src 'self' https://cdn.example.com". The message tells you precisely which directive needs updating and for which origin.
For systematic diagnosis across your entire application, deploy the Content-Security-Policy-Report-Only header with a report-uri or report-to endpoint. The browser sends a JSON payload to your endpoint for every violation it encounters. The payload includes the blocked-uri (the URL that was blocked), the violated-directive (which directive it violated), the source-file (which file triggered the load), and the original-policy (the full policy string). Aggregate these over days to build a complete picture of what a strict CSP would block before you enforce it.
The report-uri directive (older, widely supported) sends individual POST requests as application/csp-report. The newer report-to directive uses the Reporting API and allows batching. Libraries like report-uri.com or your own endpoint can collect and display these reports. For initial diagnosis, even a simple Express endpoint that logs req.body is sufficient: app.post('/csp-reports', express.json({type: 'application/csp-report'}), (req, res) => { console.log(req.body); res.sendStatus(204); }).
The Google CSP Evaluator (available at csp-evaluator.withgoogle.com) takes a CSP string and returns a security analysis, flagging issues like unsafe-inline, unsafe-eval, missing frame-ancestors, and overly broad wildcards. Run your production CSP through it to identify weaknesses even if no violations are currently occurring.
For server-side rendering frameworks, use the Chrome DevTools Network tab to inspect the Content-Security-Policy or Content-Security-Policy-Report-Only response header on your page's main document request. Verify the nonce value in the header matches the nonce attribute on your inline script tags — if they differ (common after caching or server configuration issues), inline scripts will be blocked.
Fixing CSP Errors: Directives, Nonces, and Hashes
The most straightforward fix is adding the blocked origin to the appropriate directive. If script-src 'self' is blocking https://cdn.example.com/app.js, add https://cdn.example.com to script-src. Prefer full origin allowlisting (scheme + host + optional port) over subdomain wildcards like *.example.com, which permit any subdomain including potentially compromised ones. If you must use a wildcard, never combine it with unsafe-inline or unsafe-eval.
For inline scripts that cannot be moved to external files, choose between nonces and hashes. A nonce is a random value generated per response: crypto.randomBytes(16).toString('base64') in Node.js. Add it to the Content-Security-Policy header as 'nonce-YOURVALUE' and set nonce="YOURVALUE" on the script element. Nonces must be unique per response — cached nonces are a security vulnerability since any inline script on the page can use the same nonce. Nonces work well for server-rendered pages where the CSP header and HTML are generated together per request.
Hashes work better for static inline scripts that do not change between requests. Compute the SHA-256, SHA-384, or SHA-512 hash of the exact script content (no surrounding tags): echo -n 'var x = 1;' | openssl dgst -sha256 -binary | openssl base64. Add 'sha256-HASHVALUE' to script-src. The browser computes the hash of the inline script's content and compares it to the header. Any change to the script content (including whitespace) invalidates the hash, making this approach more brittle for dynamically generated scripts.
For frame-ancestors (clickjacking prevention), use frame-ancestors 'self' to prevent your pages from being embedded in any frame on another origin, or frame-ancestors 'self' https://trusted-partner.com to allow a specific partner to embed your pages. This supersedes the older X-Frame-Options header (DENY/SAMEORIGIN) and provides finer-grained control. OWASP recommends deploying both headers during the transition period since older browsers may not support frame-ancestors.
Security warning: never use unsafe-inline combined with a wildcard source like *.example.com. This combination allows any script loaded from any subdomain to inject further inline scripts, creating a CSP policy that provides no real XSS protection. Similarly, unsafe-eval allows eval(), new Function(), and setTimeout with string arguments — all common vectors for injected code execution. These keywords should be treated as temporary workarounds and removed as soon as the underlying code is refactored.
Edge Cases: Nonce Caching, Data URIs, WebSockets
Nonces and caching are fundamentally incompatible. A nonce must be unique per HTTP response so that an attacker cannot reuse a previously observed nonce to execute an injected script. CDN caching and reverse-proxy caching both cache the full HTTP response including headers. If your page is served from cache, all requests for that cached page will receive the same nonce, which reintroduces the vulnerability that nonces are designed to prevent. For pages that require nonces, set Cache-Control: private, no-store or ensure your CDN has cache bypass rules for authenticated or personalized pages.
Data URI images (src="data:image/png;base64,...") are blocked by CSP unless you explicitly include data: in img-src. The same applies to blob: URIs used by modern file upload libraries and video players. Add data: or blob: to the specific directive that applies. However, adding data: to script-src is dangerous — it allows script execution via data: URIs, which is a common XSS injection technique. Only add data: to img-src, style-src, and media-src where it is semantically expected.
WebSocket connections are governed by connect-src, not a separate WebSocket directive. To allow WebSocket connections, add the ws:// or wss:// origin to connect-src. For example, connect-src 'self' wss://realtime.example.com. Note that the protocol scheme matters: wss:// is required for secure WebSocket connections and should be used exclusively in production to prevent mixed content.
Service workers have special CSP handling. The service worker registration origin must be covered by script-src, and the service worker itself must be same-origin with the page that registers it. Content scripts loaded inside a service worker context inherit the page's CSP. If your service worker fetches remote scripts or generates dynamic code, those operations are also subject to CSP restrictions.
React and other virtual DOM frameworks do not use innerHTML or eval for normal operation, but some patterns do trigger CSP violations. React's dangerouslySetInnerHTML does not trigger script-src violations because the browser does not execute scripts injected via innerHTML by default. However, frameworks that use eval-based template compilation (older versions of Vue.js in runtime-only mode, lodash template) will be blocked by CSP without unsafe-eval. Migrate to pre-compiled templates to remove the eval dependency entirely.
CSP Configuration Mistakes That Weaken Security
The most damaging mistake is deploying Content-Security-Policy-Report-Only and never transitioning to the enforcing Content-Security-Policy header. Report-only mode collects data but does not protect users. Many teams deploy report-only as a first step and then deprioritize the enforcement migration. Set a firm deadline — typically two weeks after all report-only violations have been addressed — to switch to enforcement.
Using overly broad wildcards undermines the purpose of CSP. A script-src https: policy allows JavaScript from any HTTPS URL on the internet, which means any XSS payload that can load a remote script bypasses the protection. Restrict to specific origins. If you need to allow many subdomains of a domain you control, a wildcard like *.yourdomain.com is acceptable. Third-party wildcards like *.googleapis.com are much riskier since you do not control all subdomains under that domain.
Forgetting to update CSP when adding third-party integrations is a systemic problem. Every time a new analytics tool, A/B testing script, live chat widget, or payment gateway is added to the application, CSP must be updated. Establish a process where CSP updates are part of the ticket or pull request checklist for any change that adds external resources. Automate CSP header generation from a centralized configuration file rather than hardcoding the header string in multiple places.
Using the deprecated report-uri directive instead of the modern report-to directive is not a security mistake but limits your reporting capability. The Reporting API (report-to) allows batching, network-error logging, and integration with dedicated reporting services. However, browser support for report-to is still incomplete (Safari has partial support). Include both report-uri and report-to in the same policy during the transition period.
Not including object-src 'none' in your policy leaves a gap. Flash, Java applets, and other plugin-based content (loaded via object, embed, and applet tags) are governed by object-src, which falls back to default-src. Explicitly setting object-src 'none' prevents any plugin-based execution, which is important since Flash and Java plugins have a long history of critical security vulnerabilities.
Building a Strong CSP Policy for Production
Start every new application's CSP with the strictest possible policy and relax it as needed, rather than starting permissive and tightening. A good starting point for a modern SPA is: default-src 'none'; script-src 'self' 'nonce-{nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'. Then add specific origins as your application requires them, with justification for each addition documented in code.
Integrate CSP header generation into your build process or infrastructure-as-code. Store the list of permitted origins in a configuration file (for example, a JSON or YAML file committed to your repository). Generate the CSP header string from this configuration at build time or at server startup. This makes auditing easy — you can review CSP changes in pull requests alongside the code changes that require them.
Use the Subresource Integrity (SRI) attribute on script and link tags for third-party resources. SRI adds an integrity attribute containing a hash of the expected file content: script src="https://cdn.example.com/lib.js" integrity="sha384-HASHVALUE" crossorigin="anonymous". The browser verifies the hash before executing or applying the resource. This prevents supply chain attacks where a CDN serves a modified file. SRI works alongside CSP — CSP controls which origins can serve resources, SRI verifies the content of those resources.
For applications serving multiple user roles or tenants, consider per-route or per-tenant CSP policies. A public marketing page may have different legitimate third-party needs than an authenticated dashboard. A single global CSP policy that satisfies the most permissive page will be weaker than route-specific policies. Express middleware and NGINX map blocks can both apply CSP headers conditionally based on request path.
OWASP's Content Security Policy Cheat Sheet recommends treating CSP as a layered defence, not a standalone fix. CSP reduces the impact of XSS by limiting what injected scripts can do and load, but it does not prevent XSS injection in the first place. Combine CSP with output encoding, parameterized queries, HTTPOnly cookies, and regular dependency auditing for a comprehensive defence-in-depth posture.
Quick fix checklist
- ✓Read the exact CSP violation message in the browser console to identify the blocked URI and directive
- ✓Deploy Content-Security-Policy-Report-Only with a report-uri endpoint to collect all violations before enforcing
- ✓For inline scripts, replace unsafe-inline with a per-request nonce or SHA-256 hash
- ✓Add legitimate third-party origins to specific directives (script-src, img-src, connect-src) rather than using wildcards
- ✓Add frame-ancestors 'self' or 'none' to prevent clickjacking, replacing X-Frame-Options
- ✓Add object-src 'none' and base-uri 'self' to every policy to close plugin and base-tag injection gaps
- ✓Run the policy through csp-evaluator.withgoogle.com to check for security weaknesses
- ✓Transition from report-only to enforcing header within two weeks of resolving all violation reports
Related guides
Frequently asked questions
What is the difference between script-src and default-src in CSP?
default-src is a fallback directive that applies to any resource type not covered by a more specific directive. script-src specifically governs JavaScript. If you define script-src explicitly, it overrides default-src for scripts and default-src no longer affects script loading. Always define both default-src as a restrictive base and then specific directives for resources that need different permissions.
Is unsafe-inline in style-src as dangerous as in script-src?
unsafe-inline in style-src is less immediately dangerous than in script-src because CSS alone cannot exfiltrate data the way JavaScript can. However, CSS injection can still be used in CSS injection attacks that selectively hide or reveal page content, and it contributes to a weaker security posture. Replace it with nonces or hashes for inline styles where possible, especially on pages handling sensitive data.
How does a CSP nonce work to allow inline scripts securely?
A nonce is a random base64 string generated per HTTP response (never reused). The server places it in both the CSP header as 'nonce-VALUE' and the script element as nonce="VALUE". The browser executes only inline scripts whose nonce attribute matches the header value. Since the nonce changes on every page load, an attacker cannot reuse a nonce from a captured response to execute an injected script.
What is Content-Security-Policy-Report-Only and when should I use it?
Content-Security-Policy-Report-Only sends the same policy as CSP but instructs the browser to report violations without blocking any resources. Use it to audit what a new or updated CSP policy would break before rolling it out to real users. Pair it with a report-uri endpoint to collect violation data. Switch to the enforcing Content-Security-Policy header once all legitimate sources are whitelisted.
Does CSP protect against all XSS attacks?
No. CSP reduces the impact of XSS by limiting what injected scripts can execute and load, but it does not prevent XSS injection. A weak CSP (using unsafe-inline or broad wildcards) may provide no protection at all. CSP is a defence-in-depth measure and should be combined with output encoding, input validation, and HTTPOnly/Secure cookie flags as recommended by OWASP.
How do I allow Google Analytics without breaking my CSP?
Add https://www.google-analytics.com and https://www.googletagmanager.com to script-src. Add https://www.google-analytics.com to connect-src to allow the tracking beacon requests. If using Google Tag Manager with custom HTML tags, also add https://tagmanager.google.com to script-src. For Google Fonts, add https://fonts.googleapis.com to style-src and https://fonts.gstatic.com to font-src. Always use Content-Security-Policy-Report-Only first to capture all required domains before enforcing.
What is frame-ancestors and how is it different from X-Frame-Options?
frame-ancestors is a CSP directive that specifies which origins can embed your page in an iframe, frame, or object. It supersedes the older X-Frame-Options header and offers more granular control: you can list multiple trusted origins rather than just self or none. For clickjacking protection, use frame-ancestors 'none' to disallow all framing or frame-ancestors 'self' to allow only same-origin framing. Keep X-Frame-Options for legacy browser compatibility.
Why is my Next.js or React app breaking with a strict CSP?
Next.js injects inline scripts for hydration and runtime configuration. With a strict script-src 'self' policy, these inline scripts are blocked. Next.js 13+ supports nonce injection via the nonce prop on Script components and custom _document files. For older versions, use the experimental strictNextHead flag or configure a custom server that injects nonces into both the CSP header and the rendered HTML on every request.
All tools run in your browser. Your data never leaves your device. Last updated: 2026-05-06.