developers

From Zero to Hero with CSP

Deploying CSP without disruptions is quite a challenge. This article offers you practical guidance on deploying CSP for modern web applications.

CSP is one of the most elaborate security policies in modern browsers. Besides acting as a second line of defense against XSS, CSP offers control over various types of content, outgoing connections, and the application's behavior. In this article, we elaborate on these features and provide you with a prioritized CSP deployment guide that will help you configure CSP in production.

Getting Started with CSP

In part 1 and part 2 of this series on CSP, we discussed the intricacies of using CSP as a second line of defense against XSS. In this third part, we look at deploying CSP in practice, starting from nothing. We also focus on additional features CSP has to offer beyond defending against XSS.

Before we dive in, let's set the scene. You have an application for which you'd like to configure a CSP policy. Awesome! Using the guidelines from the previous articles in this series, you have set up a CSP policy for your application, which brings you to a place known by every developer: "it works on my machine".

But what happens if you deploy this CSP policy in production? Will it work on your user's machines? Will it work on every browser? And more importantly, what happens if it doesn't work?

In essence, a lot of uncertainty with potentially disastrous consequences. A misconfigured CSP policy is likely to break the entire application, potentially for many users.

With that picture in mind, we're ready to dive in. Let's start by looking at one of the remarkable features of CSP: running a policy in report-only mode.

Deploying CSP in Report-Only Mode

You can tell a browser to run a CSP policy in report-only mode. This means that the browser will take your CSP policy, process it, and ensure that all application features are compatible with the policy. If the browser finds a violation, it will raise the necessary warnings and errors. However, unlike the examples we covered in the previous articles, the browser only reports the problem. The actual violation is not blocked.

Concretely, if the browser finds an unauthorized script block or script file, it will generate a warning, but it will not prevent the code from loading or executing.

To configure a report-only policy, the server sends the browser a CSP policy using the

Content-Security-Policy-Report-Only
header instead of the
Content-Security-Policy
header. The snippet below illustrates this configuration.

Content-Security-Policy-Report-Only: 
  script-src 'self';
  object-src 'none';
  base-uri 'self';
  report-uri /reporting/csp

Report-only mode is the perfect way to test a policy without breaking the application. However, we're still missing a piece of the puzzle. Generating warnings and errors in the developer tools is helpful for local development, but how can you learn about errors generated in the user's browser? That's where the

report-uri
directive comes in.

You may have noticed that the earlier sample policy contained a

report-uri
directive. This directive includes a URL, which identifies an endpoint where the browser can send reports about policy violations. The snippet below shows a sample report from one of our demo applications.

{
   "csp-report":{
      "document-uri":"https://example.com",
      "referrer":"",
      "violated-directive":"script-src-elem",
      "effective-directive":"script-src-elem",
      "original-policy":"script-src 'self'; object-src 'none'; base-uri 'self'; report-uri /reporting/csp",
      "disposition":"report",
      "blocked-uri":"inline",
      "line-number":10,
      "source-file":"https://example.com",
      "status-code":200,
      "script-sample":""
   }
}

As you can see, the browser compiles a report in JSON format. The report contains information about the location of the violation, the policy that triggered the violation, and the type of violation. These reports are sent automatically when

report-uri
is enabled.

The reporting endpoint for handling CSP reports is straightforward. It accepts an incoming POST request with a JSON body and handles the data as desired. Typically, reports are logged in a data store for later consumption. Consumption of reports can be manual (e.g., through a dashboard) or automatic (e.g., analysis scripts). Note that most security information products have built-in support for CSP endpoints.

A final interesting tidbit about reporting is that you can ask the browser to include a sample of the inline code block that caused the violation. When

'report-sample
' is added to the
script-src
directive, the browser will include the first 40 characters of the code block. This helps perform an investigation into the cause of the violation. If you can find the inline code block that matches the snippet, you know exactly where it came from. If it is legitimate, it should be authorized to load. Otherwise, CSP has likely stopped an attack, and you should investigate this further.

To summarize, report-only mode is the best way to try out your first CSP policy in the real world without causing any disruptions for your users. You can keep tweaking your policy in report-only mode until you're happy. Once everything looks good, you can deploy the policy in blocking mode using the regular

Content-Security-Policy
header.

CSP Reporting in Blocking Mode

A CSP policy that is running in blocking mode can still trigger violations. For example, when an attacker tries to exploit an XSS vulnerability, the policy is supposed to raise an error and stop the script from executing.

Note that all of this happens behind the scenes in the user's browser. But with CSP reporting, you can instruct the user's browser to send you a report about the violation. This way, you can learn about the violation and investigate the potential cause, such as a misconfiguration or an actual XSS vulnerability.

The snippet below shows a CSP policy in blocking mode, with reporting enabled.

Content-Security-Policy: 
  script-src 'self' 'report-sample';
  object-src 'none';
  base-uri 'self';
  report-uri /reporting/csp

CSP reporting is a great way to gain insights into the behavior of your application in the user's browser. However, you should not go on a wild goose chase when you see a single violation report. Reporting is valuable but not necessarily very accurate.

For example, browser extensions often modify content on a page, which can trigger a violation of your CSP policy. Unfortunately, adjusting a CSP policy to allow arbitrary extension behavior is not feasible. In fact, browser extensions often even modify CSP policies, in which case the violation is caused by a policy that's not even yours. This blog post by Dropbox gives a bit more insight into the challenges of running a CSP reporting endpoint in the real world.

Finally, the CSP reporting endpoint is unauthenticated and publicly accessible. An attacker could easily supply fake CSP reports to the endpoint to confuse the security team.

As a consequence, we recommend enabling CSP reporting and monitoring incoming reports. However, following up on reports is likely only warranted when many users produce the same report or if the report can be traced back to a potential attack vector in the application. Filtering and finetuning are essential to reduce the noise in the feed.

CSP beyond Script Code

So far, we've only talked about CSP as a second line of defense against XSS attacks. However, if you look at the CSP specifications, you'll discover that CSP can do so much more. This section looks at all the other types of content you can control with CSP.

Before we dive in, we want to point out that most of the peculiarities of handling JavaScript do not apply to other types of content. The directives for other types of content can be configured with simple expressions pointing to specific hosts, including a CDN, without negatively impacting the security of the CSP policy. The only exception is CSS code, so let's cover that first.

Stylesheets

CSP allows you to control how the browser handles CSS code in the page. The

style-src
directive applies to all CSS code, such as
<style>
blocks,
style
attributes, and remote stylesheets.

Like script code, CSS code is considered dangerous dynamic content since malicious CSS code can change the behavior of the page. Therefore, setting the

style-src
attribute enables a couple of default restrictions, such as blocking all inline CSS code.

To re-enable inline style code, the

style-src
directive supports the same mechanisms as we have discussed from script code:
'unsafe-inline'
, hashes, and nonces. In theory, you are supposed to selectively enable style code using hashes or nonces. In practice, the story is somewhat different.

Using hashes and nonces should not be that difficult if you completely control the style code. However, in many real-world applications, styling is handled in a library that is not under the control of the application developer. Not enabling inline style code typically breaks the library, and selectively re-enabling styles is often infeasible. That's why a real-world CSP policy that aims to restrict styles is generally forced to add

'unsafe-inline'
to make things work.

To summarize, restricting styles is a bit less critical than restricting JavaScript code execution. If you want to configure a

style-src
directive, aim to make it as strict as possible. However, if you end up enabling
'unsafe-inline'
, it's not the end of the world.

Images, fonts, and media

CSP also allows you to control where images, fonts, and media (audio and video) can be loaded from. The directives for these resources are, in respective order,

img-src
,
font-src
, and
media-src
.

These directives are typically configured with a list of URL-based locations. Note that it is not uncommon for images to include

data:
as a source expression or even use the wildcard
*
.

Embedded content

The

object-src
directive allows you to define valid sources for embedded content, typically included using the
<object>
,
<embed>
, or
<applet>
elements.

Note that these elements are typically used for legacy content, such as Flash files or Java applets. In modern applications, these are typically no longer needed. Therefore, it is recommended to set the

object-src
directive to the value
'none'
.

Child contexts

A document loaded in the browser can create a new browsing context, resulting in a parent-child relationship. One example is using an

<iframe>
, which creates a nested browsing context. Another example is loading a web worker, which also instantiates a child context. Both child contexts are instantiated with a URL, for which the source can be controlled with the
child-src
directive.

The recommended default value for the

child-src
directive is the value
'none'
. When the application relies on iframes or web workers, this directive should be configured with the expected sources for these children.

Setting a default

That leaves one more content directive to discuss:

default-src
. This directive can be used as a default configuration for each type of resource that is not configured with an explicit directive.

No worries, it sounds more complicated than it is. Let's take a look at an example.

default-src 'self'; img-src https://images.example.com

The policy shown above uses

default-src
as a catchall but also specifies an
img-src
directive. So for scripts, stylesheets, fonts, etc., the browser applies
default-src
. In this example, these types of content can be loaded from the application's origin. For images, there is a more specific directive (
img-src
), so the browser uses that instead. Images can only be loaded from
https://images.example.com
, not from the application's origin.

The CSP specification allows an elaborate configuration of

default-src
, including the use of hashes and nonces. However, in practice, such a configuration is not recommended. Instead,
default-src
should be set to a sane starting point, which is either
'none'
or
'self'
. For every type of resource you would like to load, you can configure specific directives with the desired expressions.

To summarize,

default-src
applies to everything that is not explicitly configured. If a more specific directive is present, only that directive is considered for that particular type of content.

Learn web security through a hands-on exploration of some of the most notorious threats.

Download the free ebookSecurity for Web Developers

Even More CSP Directives

There's a lot more to CSP (yes, really!). Apart from controlling where content is loaded from, CSP also allows you to control outgoing connections, as well as certain application behaviors. We briefly cover these directives in this section.

Note that this is not an exhaustive guide on every available CSP directive. We refer to this extensive CSP guide on the Mozilla Developer Network for more information.

Controlling outgoing connections

With CSP, you can control outgoing connections. Concretely, CSP offers two directives for this purpose:

form-action
and
connect-src
.

The

form-action
directive allows you to choose where forms can be submitted. This directive is useful to avoid form hijacking in a traditional web application. For traditional web applications, the value
'self'
makes the most sense. Single Page Applications typically do not rely on form submissions but call APIs from JavaScript code instead. SPAs should set the
form-action
directive to the value
'none'
.

The

connect-src
directive controls outgoing connections, such as XHR or Fetch requests, but also WebSocket connections and Server-Sent Events. This directive should be configured with the APIs that your application consumes. If no outgoing connections are needed, the directive can be set to the value
'none'
.

Enabling additional restrictions

CSP also offers control over behavioral features in the browser. Let's look at a few directives that you should definitely know about.

The

frame-ancestors
directive allows the application to restrict how it is loaded in a frame. Since many applications are not supposed to be loaded in a frame anywhere, they configure this directive with the value
'none'
, which is an essential defense against UI redressing attacks. When selective framing is desired, the application can use
'self'
or a list of URLs to enable selective framing. Omitting the directive from a policy allows all framing, which is the default behavior in all browsers.

The

sandbox
directive is the most restrictive directive available in CSP. It is similar to the
sandbox
attribute for
iframe
elements and is intended to be used on responses serving potentially untrusted content, such as user-provided documents. Adding the
sandbox
directive without any expressions enables all restrictions of the HTML5
sandbox
attribute on the browsing context. A sandbox disables JS execution, form submissions, plugin content, popups, etc. To re-enable selective restrictions, specific keywords can be added as values of the
sandbox
directive (e.g.,
sandbox allow-scripts
). For more information about the sandbox and its restrictions, check out this MDN documentation page.

Try out Auth0 authentication for free.

Get started →

A Prioritized CSP Deployment Guide

We're reaching the end of this three-part series on CSP, and what a journey it has been.

We started by discussing CSP's ability to act as a second line of defense against cross-site scripting. After that, we looked at using CSP in a modern JavaScript frontend application. And in this third part, we looked at many other goodies CSP has to offer, such as reporting, content directives, and behavioral control.

But where does that leave you? How do you even get started with CSP? What should you focus on first?

All good questions that we'll answer in this final section, where we provide you with a prioritized CSP deployment guide. This guide helps you get started with deploying CSP for a modern application. Of course, your mileage may vary depending on your needs.

It's mostly about XSS

The most important capability of CSP is stopping or limiting the exploitation of an XSS vulnerability. That's precisely what Google's CSP policy focuses on. So, if you're starting with CSP, forget about all the other types of content. You can always handle them later.

Your first CSP policy should configure

script-src
to fit your application. You also need to add
base-uri
and
object-src
, which can likely be configured with respectively
'self'
and
'none'
values.

Try it out

You can easily try out a CSP policy on a development version of the application. However, if you want to see your policy in action, deploy it as a report-only policy. This will give you a good idea of what you can expect.

Flip the switch to blocking mode

When you are happy with your report-only policy, you can consider transforming it into a blocking policy by changing the name of the response header. No worries if you get something wrong, you can easily remove or modify the CSP header afterward.

Congratulate yourself

If you succeeded in deploying a strict CSP policy in blocking mode without causing any side effects, you did great. You have now successfully deployed a second line of defense against XSS, which is no small feat.

Add additional defenses

Frontend applications are supposed to have protections against UI redressing attacks in place. The easiest way to enable these is by adding a

frame-ancestors
directive to your CSP policy.

Further finetune your CSP policy

If you prefer, you can keep finetuning your CSP policy to cover other types of content as well. To be honest, the benefits of doing so are quite limited compared to the secondary defense against XSS attacks. Additionally, it will make the policy a lot more fragile, so be careful when you go down this path.