How to get started with a Content Security Policy

Have you ever wondered how to put checks and balances into place for the DOM? The Content Security Policy recommendation has existed since 2012 to empower developers to create safer DOMs for their users. With a CSP a developer can for instance disallow the execution of untrusted inline JavaScript making it difficult to conduct successful Cross-Site Scripting attacks. Nowadays content security policies can be used for much more than controlling which sources are allowed for resources.

This post is based on the material used for a presentation held at Elisa’s Developer Community. A big thank you to Elisa for the invitation to present! If you work at Elisa, check out upcoming events. You will be missing out otherwise.

What is a Content Security Policy?

Content Security Policy is information about the web page that allows the browser to determine which behaviours are needed so that the browser can disallow behaviours that are not necessary. Content Security Policies are allowlists in nature–what they don’t allow is implicitly disallowed.

Let’s imagine a scenario where you are looking over a child. You are at the playground and the child wants to build a sandcastle. Out of all the gear you have with you, you choose to give the child a bucket and a shovel. You know that no other tools are required for the task. You for instance forego giving them their stuffed toy because you know that it would get dirty and you would have to clean it. Content Security Policies work along similar principles–they just apply to the browser’s sandbox.

Content Security Policy is information about the web page that allows the browser to determine which behaviours are needed so that the browser can disallow behaviours that are not necessary

Note that when introducing a Content Security Policy to a page, you are opting in to some defaults. For instance, inline execution is not allowed by default when a CSP is engaged.

Directives and Source Expressions

A content security consists of directives and source expressions or sources. Directives are stand ins for certain behaviours in the browser which source expressions configure. There are quite a few directives and source expressions to learn. You’ll find a comprehensive list from MDN.

Fetch directives make the largest group of directives. They allow you to control the sources the browser can use to load resources. Some common fetch directives are for instance script-src or font-src. When a more specific fetch directive does not exist for the resource, the browser will attempt to use the default-src directive. For instance if an image-src directive does not exist, the browser will use the value in the default-src directive.

In addition to fetch directives there are three other groups: reporting directives, document directives and navigation directives. Each group has more specific use cases we won’t cover today.

Source expressions allow you to express the limitations you want to apply to a behaviour a directive signifies. The most common source expression you’ll use will be a host-source expression. In practice you’ll use this source expression to allow paths from a host as sources for the resources your web page pulls in.

There are also multiple keyword type source expressions. Each keyword has a specific meaning which you have to learn. For instance 'self' references the origin of the DOM and 'none' disallows the directive altogether. Newer keywords like 'none-*', 'hash-*' and 'strict-dynamic' allow inline JavaScript to be configured for use in a safe way.

Example of a Content Security Policy

You can provide a Content Security Policy as a header or as a meta tag. It’s more common to use headers. Here we have a simple example which illustrates the structure of a CSP.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://example.com https://cdn.example.net;

We have applied two directives default-src and script-src. That means we are allowing scripts to be loaded from the origin of the DOM, example.com and cdn.example.net. All other fetch directives will use default-src, i.e. they can only be loaded from the origin of the DOM.

Note that defaults are also engaged because we have defined a CSP. That means inline execution is forbidden. For instance, contents of <script> won’t be executed and neither will inline styles be applied! The use of eval is also disallowed.

Recommendations for Content Security Policies

When you are first starting out with Content security policies you can follow these recommendations. Note that the tools and practices around CSPs are still evolving. These are baseline recommendations and you may find more robust options when you benchmark your own needs against what’s available.

1. Adopt Gradually by First Implementing Your CSP in Report Only Mode

By using the Content-Security-Policy-Report-Only header you can apply your CSP without enforcing it. In report-only mode, the browser will report any violations, but will not enforce the policy, e.g. no sources will be disallowed. Report only mode is a good tool for verifying that your CSP does not disallow any required resources. The reports can be used for iterating on the CSP. But it also allows you to monitor attempts to conduct attacks against your users.

If you end up collections reports, make sure that the amount of reports you receive are not so numerous as to incur unreasonable charges from your service provider.

2. Be as Strict as Possible

Prefer to be strict instead of lax. For instance, avoid allowing entire domains. Instead try to allow the specific paths your website requires.

❌ *.example.com
✅ https://www.example.com/path/to/script.js

Using specific paths may be verbose so you may consider your options in order to be pragmatic. But do note that allowing entire domains does increase attack surface by a meaningful amount. When you allow an entire domain, any part of an application running on that domain becomes a possible attack vector. If a site allows user generated content, an attacker may upload their own content and be able to inject it.

3. Use Nonces and Hashes

When you use nonces and hashes, you can allow inline execution without using the 'unsafe-inline' keyword. By using nonces and hashes, your CSP will be easier to maintain. Stay tuned as we’ll introduce nonces and hashes in more depth in the post going over advanced use cases for CSPs.

4. Clarify Responsibilities and Processes

The DOM has been a wild west in the past. Some older tools may be impossible to use with a safe CSP. At times for instance marketing may want new tracking tools that require changes to CSPs. The introduction of new features may at times necessitate changes to the CSP. You should clarify responsibilities between teams and different parts of the organisation to support the creation and maintenance of a safe and effective CSP. Processes should be adjusted to ensure that the information for building an effective CSP is available. People maintaining the application should be familiar with CSPs in order to be able to maintain them.

Challenges and Advanced use cases for Content Security Policies

Next time we’ll look at more advanced use cases and go over some common challenges with Content Security Policies. For instance: how to use the reporting feature, how to make use of nonces with static sites and how Content Security Policies based on the level two recommendation differ from level one. Follow Teamit on LinkedIn to get notified when the next part lands.