Fixing unsafe-inline

TL;DR: Avoid using unsafe-inline. Instead, move inline scripts into separate files, or utilize the hash/nonce mechanisms.

The CSP (content security policy) is a low-hanging fruit - an easy way to improve security significantly.

To fully benefit from CSP, it's better to adhere to the default rules and refrain from allowing 'unsafe-inline'. It is called "unsafe" for a reason!

What are inline scripts?

What are inline scripts? It's any JavaScript appearing directly in the body of your HTML page.

For instance, inline event handlers look like this:

<button onclick="alert('Button clicked!')">Click Me!</button>

Or, here's a script that changes color upon mouseover:

<p onmouseover="'red'">Hover over me!</p>

Even the scripts inside the <script> tag are considered "inline" because they are within the HTML bode of your page:

document.addEventListener("load", () => {
  // more code

Wrong way to fix the error

What if you encounter an error like this?

Refused to execute inline script because it violates the following Content Security Policy directive: "default-src 'self'". Either the 'unsafe-inline' keyword, a hash, or a nonce ('nonce-...') is required to enable inline execution.

A common mistake many developers make is to add the ['unsafe-inline']( directive).

default-src 'self'; script-src 'self' 'unsafe-inline'

Yes, it's an easy thing to do, but it's not a real solution. It doesn't fix the problem but simply hides it and suppresses the warning.

Why block inline scripts?

So, why is allowing inline scripts is a bad idea?

The main purpose of CSP is to mitigate XSS Attacks. If 'unsafe-inline' is enabled, malicious inline scripts injected into the webpage could run, leading to security threats.

The whole idea of using CSP is undermined.

Move JavaScripts into its own files

The best solution in terms of security and maintainability is to move any inline JavaScript into its files.

For example, use addEventListener:

document.addEventListener('click', () => alert("Button clicked!"))

Using hash

But what if you really need to use inline scripts?

The Content Security Policy allows specifying a hash or a nonce that uniquely identify the scripts, thus making the browser allow those script only and refusing all the others.

Here's an example of using a hash

Imagine you have an inline script:

alert("Hello, world!")

We can calculate its hash using a simple CLI command:

echo -n "alert('Hello, CSP!');" | openssl dgst -sha256 -binary | openssl base64
#=> 01RsA/xEak42pBoMWnMM/OfOi29OmSdMxC0UBetjy/Q=

Now simply include that in the content security policy:

default-src 'self'; script-src 'self' 'sha256-01RsA/xEak42pBoMWnMM/OfOi29OmSdMxC0UBetjy/Q='

And then, that script will be executed by the browser.

Using a nonce

The other way, which is somewhat similar, is to use a nonce.

A nonce is any unique string that identifies the script. It needs to be specified both within the CSP and in the script itself.

For example,

default-src 'self'; script-src 'self' 'nonce-myallowedscript'

And then the script:

<script nonce="myallowedscript">
  alert('Hello, world.');

What's next

Note that both hash and nonce methods, while relatively simple, still add some overhead. For example, you need to update the hash every time the script is changed, etc.

So the best solution is not to use inline scripts at all. If you have some - move them into their own files. And then make sure to only allow loading script from your own domain.

To read more about the topic, MDN is always a great source.

Collect And Analyze
CSP Reports in Real-Time ✨✨✨