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.
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.
"original-policy":"script-src 'self'; object-src 'none'; base-uri 'self'; report-uri /reporting/csp",
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
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.
script-src 'self' 'report-sample';
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.
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 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.
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,
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
object-src directive allows you to define valid sources for embedded content, typically included using the
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
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
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
'self'. For every type of resource you would like to load, you can configure specific directives with the desired expressions.
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 EBOOK
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 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
form-action directive to the value
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
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.
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.
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.
A Prioritized CSP Deployment Guide
We're reaching the end of this three-part series on CSP, and what a journey it has been.
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
object-src, which can likely be configured with respectively
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.
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.