Why Your CSP Nonce Is Breaking Your Inline Scripts (And How to Fix It)?

August 1, 2025 (4mo ago)

Jump to FAQs

As a developer focused on building secure and efficient web applications, I often find myself deep in the weeds of browser security. One of the most common—and initially confusing—hurdles developers face when implementing a Content Security Policy (CSP) is the sudden flood of console errors about inline scripts and styles being blocked, even when 'unsafe-inline' is present.

If you've ever seen this error, you're in the right place:

Refused to execute inline script because it violates the following Content Security Policy directive... Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list.

Let's demystify this. It's not a bug; it's a critical security feature working exactly as designed.

What is Content Security Policy (CSP)?

Think of CSP as a security guard for your website. You provide it with a strict list of rules (a policy) that tells the browser which sources are allowed to load resources like JavaScript, CSS, fonts, and images. Its primary job is to mitigate and report certain types of attacks, most notably Cross-Site Scripting (XSS).

A simple script-src policy might look like this:

script-src 'self' https://trusted-cdn.com;

This tells the browser: "Only execute scripts that come from my own domain ('self') or https://trusted-cdn.com."

The Heart of the Conflict: nonce vs. 'unsafe-inline'

The problem arises when we mix two specific directives:

  1. 'unsafe-inline': This is the lenient directive. It tells the browser, "It's okay to execute any script found inside a <script>...</script> tag or any style inside a <style>...</style> tag directly in the HTML." As the name implies, it's considered unsafe because if an attacker can inject HTML into your page, they can easily inject a malicious script.

  2. 'nonce-rAnd0mStr1ng': This is a much stricter and more secure directive. A "nonce" (number used once) is a unique, randomly generated string that your server produces for every single HTTP response. You then add this same nonce as an attribute to your legitimate inline script and style tags.

    Your CSP header would look like this:

    script-src 'self' 'nonce-rAnd0mStr1ng';
    

    And your HTML would contain:

    <script nonce="rAnd0mStr1ng">
      // This is a trusted, server-generated inline script
      console.log("This will run!");
    </script>
     
    <script>
      // This script was NOT generated by the server and will be blocked
      alert("This will NOT run!");
    </script>

Here is the golden rule: When the browser sees a nonce (or a hash) in a policy, it completely ignores the 'unsafe-inline' directive for that policy.

Why? Because the two directives are fundamentally opposed. One says, "Trust anything inline," while the other says, "Trust only the inline code that has this secret one-time password." The browser prioritizes the stricter, more secure rule.

Common Violations You're Probably Seeing

When you enable a nonce-based CSP, you’ll suddenly discover just how much inline code is in your application. The violations usually fall into these categories:

The Fix: Choosing Your Strategy

You have two main paths forward.

Strategy 1: The Secure & Recommended Path - Embrace the Nonce

This is the best practice. It involves making your application fully compliant with your strict policy.

1. Add the Nonce to All Legitimate Inline Tags:

Your server-side code (e.g., in your Laravel master layout) must add the generated nonce to every <script> and <style> tag it renders. In Laravel, you can use the @csp_nonce Blade directive if you're using a package that provides it, or simply inject the variable yourself.

// In a middleware
$nonce = base64_encode(random_bytes(16));
View::share('csp_nonce', $nonce);
// Then set the header with $nonce
<style nonce="{{ $csp_nonce }}">
  .important-notice {
    color: #b62f24;
  }
</style>
 
<script nonce="{{ $csp_nonce }}">
  // Your trusted JS code
</script>

2. Refactor Inline Event Handlers:

This is non-negotiable. You must move away from onclick.

Before:

<button id="refreshBtn" onclick="refreshCaptcha()">Refresh</button>

After:

<button id="refreshBtn">Refresh</button>
 
<script nonce="{{ $csp_nonce }}">
  document.addEventListener("DOMContentLoaded", function () {
    const refreshButton = document.getElementById("refreshBtn");
    if (refreshButton) {
      refreshButton.addEventListener("click", function refreshCaptcha() {
        // Your logic to refresh the captcha
      });
    }
  });
</script>

3. Eliminate Inline style Attributes:

Move all styling into CSS classes defined in a file or a nonced <style> tag.

Strategy 2: The Pragmatic (But Less Secure) Fallback - Remove the Nonce

If you're dealing with legacy code or third-party libraries that you can't easily change, making everything nonce-compliant can be a massive undertaking. In this case, you can opt for a less secure policy.

Simply remove the 'nonce-...' from your CSP header.

Your policy would change from this:

script-src 'self' 'nonce-rAnd0mStr1ng' 'unsafe-inline' https:;

To this:

script-src 'self' 'unsafe-inline' https:;

⚠️ Security Warning: This makes your application vulnerable to inline script injection if an XSS flaw exists anywhere. By doing this, you're accepting the risk that 'unsafe-inline' brings. This should be a temporary measure or a last resort.

Conclusion

Implementing a strong Content Security Policy is a huge step toward securing your web applications. The conflict between nonce and 'unsafe-inline' is a fundamental concept designed to prevent you from accidentally weakening your own rules.

While it can be challenging, the recommended path is always to embrace the nonce and refactor your code to be compliant. It forces cleaner separation of concerns and results in a far more robust and secure application.

Stay secure and happy coding!

Discuss this post:

Frequently Asked Questions

Why does my inline JavaScript stop working when I add a nonce to my CSP?

When a nonce is present in your CSP policy, the browser completely ignores the 'unsafe-inline' directive. This means only inline scripts with the matching nonce attribute will execute, and all other inline scripts will be blocked for security reasons.

What's the difference between using 'unsafe-inline' and nonce in CSP?

'unsafe-inline' allows all inline scripts to run, making your site vulnerable to XSS attacks. A nonce is a unique, randomly generated string that only allows specific inline scripts marked with that nonce to execute, providing much better security.

How do I fix 'onclick' and other inline event handlers with CSP nonce?

You need to remove inline event handlers like 'onclick' and replace them with proper event listeners in JavaScript. Move the event handling code into a script tag with the nonce attribute and use addEventListener() instead.

Can I use both nonce and 'unsafe-inline' in the same CSP policy?

While you can include both in your policy, the browser will ignore 'unsafe-inline' when a nonce is present. The nonce takes precedence as it's the more secure option.

Should I remove the nonce from my CSP if I have too many inline script violations?

While removing the nonce is easier short-term, it significantly reduces your security. The recommended approach is to refactor your code to be nonce-compliant, even though it requires more effort initially.

How do I generate a secure nonce for my CSP policy?

Generate a cryptographically secure random string for each request. In PHP/Laravel, you can use base64_encode(random_bytes(16)) to create a unique nonce. Never reuse nonces across requests.