Self-experimentation with CSP

Last week we landed a big update to the MDN guide page about Content Security Policy (CSP), a web security feature that has a reputation for being complicated and difficult to deploy.

It started with a discussion in the Security Web Applications Community Group about how we can help CSP gain more adoption among web developers. Florian asked whether our site, https://openwebdocs.org, has a CSP, and embarrassingly, it didn't. We thought perhaps that by trying to add one, we might get some insight into the difficulties web developers face when trying to deploy their own.

Strict CSP

Netlify generously host openwebdocs.org under their Open Source Plan. So I went there, and found a very interesting post: How I learned to stop worrying and love the Content Security Policy, which describes how Netlify supports a style of CSP which uses a nonce (number used once) to determine whether scripts are allowed to execute, rather than a CSP that lists acceptable hosts. The general idea is that every time a document is served, the server generates a random value, and embeds this value both in the CSP header and in the nonce attribute of any <script> tags. The browser checks that the value in the header matches the attribute value, and refuses to execute the script unless they match. Because an attacker can't predict the nonce, they can't add a valid nonce attribute into any script tags they manage to inject.

A big attraction of this approach is that it's much easier to implement than listing acceptable hosts: the Netlify article notes that just adding Google Analytics to your site requires you to add 187 domains to the list.

Next, I found that although both web.dev and OWASP recommend this style, which they call a strict CSP, MDN doesn't even mention it. So we filed an issue and started updating the docs.

Does OWD need a CSP?

While we were discussing how to add a CSP to the OWD website, Estelle asked a really good question: does our site need a CSP? In our pull request to update the CSP guide, Hamish had a similar comment: how, specifically, does a CSP protect websites from XSS?

Our current documentation tended to go like this:

  1. XSS is bad
  2. CSP protects you from XSS
  3. So you should have a CSP

We need to go deeper than this. We ought to explain when and how XSS can cause specific problems for sites, and exactly where a CSP can help: where the features of a CSP meet up with the prongs of an XSS attack. We ought to explain how the threats and corresponding protections might be different for different sites. For example:

Our CSP guide update made some steps towards this, but there's more work to do here: in particular we need better documentation of XSS, to cover the different ways an XSS attack can be made and the different tools that can be used to counter it: not only CSP, but tools like trusted types and output encoding.

Auto-CSP

The Netlify feature also connects with an idea that was mentioned in the TPAC session on security docs and which recently landed in Angular: tool support to deploy a CSP automatically based on a configuration setting. We'd like to include this in our CSP documentation and again, we'd like to talk about not just its existence but the specific web app architectures for which it is appropriate.

For example: in a strict CSP, the server typically uses a template to insert nonces into the script tags it intends to include in the document it serves:

<script nonce="{{nonce}}">
  // ...
</script>

The server's explicit intention is important here: it means that even if an attacker tricks the server into inadvertently including a malicious script (for example, in a reflected XSS attack), then the injected script won't contain the nonce, and it won't execute.

By definition, though, auto-CSP doesn't understand the server's intention. Netlify's dynamic CSP runs in the CDN, and automatically injects the nonce into every <script> tag it finds in the document that the server provided. If this contains scripts that the server inadvertently included, they will be given the nonce, and will be allowed to execute.

What Netlify's CSP does is protect against client-side script injection, where the front-end code inadvertently inserts a malicious script by calling an unsafe DOM API like document.write(). So this kind of CSP is appropriate for a web app that uses client-side rendering.

So... does OWD have a CSP yet?

Not yet! We're technical writers, so as soon as we learn something new, we have to make sure all the docs are updated first. But our practical investigations have already generated improvements to MDN's security documentation, and as we've seen in this post, there's much more to come.