# Nonces and unsafe-inline (/en/docs/platform/features/csp-builder/nonces-and-unsafe-inline)



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 [#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'`](/en/blog/unsafe-inline-csp) 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](/en/docs/web-security/policies/content-security-policy/values/csp-hashes-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 [#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'`](/en/blog/strict-dynamic-csp), 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 [#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:

```http
Content-Security-Policy-Report-Only: script-src 'nonce-rAnd0m4bc123' 'strict-dynamic' 'report-sample' 'report-sha256'; object-src 'none'; base-uri 'self'; report-to centralcsp
```

```html
<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 [#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 [#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](/en/docs/web-security/policies/content-security-policy/values/csp-hashes-nonce).
* 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 [#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 [#next-steps]

* [Review table](/en/docs/platform/features/csp-builder/review-sources)
* [Remove a risky value](/en/docs/platform/features/csp-builder/remove-risky-values)
* [CSP nonce setup](/en/blog/csp-nonce-setup)
* [Hashes and nonces](/en/docs/web-security/policies/content-security-policy/values/csp-hashes-nonce)
