CentralCSP
FeaturesCSP builder

Nonces and unsafe-inline

How the CSP builder handles inline scripts and styles, and how to move your policy from unsafe-inline to a nonce without breaking your pages.

Last update:

Inline code is the most common reason a Content Security Policy (CSP) stays weak. This page explains what the CSP builder does when browsers report inline scripts or styles, and how to replace 'unsafe-inline' with a nonce.

Why unsafe-inline is a problem

A cross-site scripting (XSS) attack injects a <script> block or an event handler such as onclick="…" into your page. 'unsafe-inline' allows every inline script, so the injected one runs just like your own. The policy then gives little protection against the attack it exists to stop.

A nonce is a random value your server generates for each response. It goes in the policy and in the nonce attribute of each script tag you serve. The browser runs only the inline scripts that carry it, and an attacker cannot guess it.

What the builder does with inline code

When browsers report blocked inline code, the builder suggests the keyword that would allow it:

  • Inline scripts, event handlers, or inline styles give 'unsafe-inline' in the directive that blocked them.
  • eval() or new Function() gives 'unsafe-eval'.

Both start added, because your pages run that code today and a policy that blocks it would break them. Both carry a risk flag, and their details panel shows the Needed today, worth removing callout with the change to make in your code.

When the policy you start from already uses a nonce, the builder works differently:

  • The nonce comes back as the placeholder 'nonce-{RANDOM}', since each response needs its own value.
  • A script-src or script-src-elem with a nonce gains 'strict-dynamic', labeled Recommended. Scripts that your nonced scripts load are then trusted too, so their hosts no longer need listing.
  • A reported 'unsafe-inline' in the same kind of directive starts rejected, with the Rejected because your policy uses a nonce callout. Browsers ignore 'unsafe-inline' wherever a nonce is present, so adding it would change nothing.

The builder adds a nonce only when the policy you start from has one. It cannot add one for you, because your server has to generate it.

Move from unsafe-inline to a nonce

To replace 'unsafe-inline' with a nonce:

  1. On your server, generate a new unguessable value for each response, such as 16 random bytes encoded in base64.
  2. Add nonce="<value>" to every <script> tag your pages serve, inline or not. With 'strict-dynamic', a script tag without the nonce stops loading.
  3. Replace inline event handlers, such as onclick="…", with addEventListener calls in a script file. An event handler cannot carry a nonce.
  4. Add 'nonce-<value>' to the script-src of the report-only policy you already send, and deploy it.
  5. Wait until the reports cover a full period after your deploy, then open the CSP builder on that period.
  6. On the Starting policy step, select the live policy. It carries the Uses nonces chip.
  7. On the Review sources step, check that 'unsafe-inline' in your script directives shows as rejected, and that the rows reported from inline event handlers are gone.
  8. On the Deploy step, copy the headers and deploy them, replacing {RANDOM} with the value of each response.

Your pages now run only the scripts that carry the nonce. If a row still reports inline code, select it and check Code involved and Pages where it happened to find the tag or handler you missed.

The deployed header and the matching script tag look like this example:

Content-Security-Policy-Report-Only: script-src 'nonce-rAnd0m4bc123' 'strict-dynamic' 'report-sample' 'report-sha256'; object-src 'none'; base-uri 'self'; report-to centralcsp
<script nonce="rAnd0m4bc123" src="/js/app.js"></script>

A fixed nonce protects nothing. If the value is the same on every response, an attacker can read it and reuse it.

Inline styles

Styles follow the same rules in style-src. To remove 'unsafe-inline' from styles, move <style> blocks into a stylesheet or give them the nonce, and replace style="…" attributes with CSS classes. A style attribute cannot carry a nonce. Styles set from JavaScript through element.style keep working.

The risk of inline styles is lower than for scripts, so you can keep 'unsafe-inline' in style-src while you remove it from your script directives first.

When a nonce is not possible

A site served as static files, with no server code per response, cannot generate a nonce. In that case:

  • Move inline scripts into files served from your site, so your own origin covers them.
  • For an inline script that must stay, allow its hash instead. Refer to Hashes and nonces.
  • Keep 'unsafe-inline' only as long as a page still needs it, and track it with its risk flag.

Only older browsers read this value

When your policy has script-src-elem or script-src-attr, modern browsers read those for inline code and skip script-src. An 'unsafe-inline' left in script-src then only matters to older browsers, and its details panel shows the Only older browsers read this value callout. Check the risk on the script-src-elem and script-src-attr rows instead. The same applies to style-src-elem and style-src-attr.

Next steps

On this page