A Content Security Policy (CSP) is a set of instructions for browsers to follow when loading up your website, delivered as part of your website's HTTP Response Header.
This is a widely supported security standard that can help you prevent injection-based attacks by fine-tuning what resources a browser is allowed to load on your website.
It specifies exactly where the browser is allowed to load resources from, and it's an effective way of blocking anything malicious loading from elsewhere should instructions to do so somehow make their way into your website.
A browser simply does what it's told, and has no idea whether a script is malicious in nature or not. When creating a CSP, you can customize exactly where things like JS, CSS, fonts, or pretty much anything at all, are and aren't allowed to load from.
CSP's and GridPane
GridPane includes several security headers for every website on the platform. These prevent cross-site (XSS) scripting and clickjacking to keep your website secure.
For clickjacking protection we use the catch-all X-Frame-Options header. This header is an all or nothing header, Google killed it when they made Chrome ignore it's granular configuration:
It is now considered a legacy header, and has been superceded by the Content Security Policy Header (CSP). The Content Security Policy header provides for fine granular control over the security policy not just for iFrames, but all content types.
GridPane does comes with a gp-cli command to easily enable a partially configured CSP header, but we do not enable this header by default. It requires affirmative action from a user to enable it.
So the question is why? Why are we implementing the outdated legacy header to stop Clickjacking protection, instead of defaulting to enable the CSP header, which provides for granular clickjacking protection and so much more?
This is primarily for 3 reasons: -
- X-Frame-Options is a catch-all to ensure iframe clickjacking protection on site builds. Enabling it by default is highly unlikely to cripple any sites.
- CSP headers have no one size fits all configuration, these need to be customized on a website by website basis to actually provide any real security
- If we did implement one by default, it would have to be wide open to avoid crippling sites and swamping support, therefore, it wouldn't provide any real security at all. Even worse, it may give our users a false sense of security, and that goes against everything we're about as a company.
A CSP header will dictate where you can load fonts and analytics from, it will affect map and video embeds, code embeds, and a whole lot more. We can't create a CSP that takes into account everyone's specific requirements.
We want you to use this header, but for it to be effective and actually increase your security it must be configured individually for each of your sites depending on their needs.
A CSP is more than just a box to check
You can check your security headers "grade" on websites such as https://securityheaders.com/.
It's important to note that this isn't the same thing as something like a GTMetrix score (which is inconsequential), this has a real impact on your website's security and should be taken seriously.
You can set up a CSP that provides zero security whatsoever and still get an A+ grade rating. It's comically easy. Here's an example:
default-src http: I've added here allows any type of resource from any source, to be loaded by the browser over HTTP. It's the exact opposite of secure. Here's that same websites security header grade:
Please do not do this. If you're going to take the time to set up a CSP, do it the right way and create a real one that enhances your website's security. Our default CSP can easily be customized, and this article will walk you through how to do this.
Creating your Content Security Policy config
We have our very handy GP-CLI to assist in the creation of your CSP configuration file. For this, you'll need to SSH into your server. Please see the following articles to get started:
Generate your SSH Key:
Add your SSH Key to GridPane:
Connect to your server:
The following two commands are self-explanatory - one will create your CSP file, the other will disable it.
To enable your CSP, run the
-csp-header-on command below, switching out "site.url" for your websites domain name:
gp site site.url -csp-header-on
You can then disable it at any time with:
gp site site.url -csp-header-off
The default Content Security Policy
The above commands will create and activate a CSP for your website.
The file it creates is located in
The default CSP configuration is as follows: -
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' data:;" always;
What it all means
This defines the loading policy for all resources where the resource type dedicated directive is not defined (it's the fallback). "Self" refers to your website's domain.
script-src 'self' 'unsafe-inline' 'unsafe-eval';
This guards the browser mechanisms that can fetch HTTP Requests and only allows for requests to be made from your website.
Only images hosted on your website are allowed.
style-src 'self' 'unsafe-inline';
Only CSS hosted on your website itself is allowed to be loaded, and this includes the use of inline CSS.
font-src 'self' data:;
Allows fonts to be loaded from any URI.
Always ensures that Nginx sends the header regardless of the response code.
This command also creates a Feature Policy. This header protects your site from third parties using APIs that have security and privacy implications, and also from your own team adding outdated APIs or poorly optimized images. Learn more here:
Your CSP on GridPane is added using the "add_header" Nginx directive. Here's how the formatting looks:
add_header name "directive1 value; directive2 value; directive3 value;" [always];
Only one directive is needed to create a CSP. A directive can only be used once - any additional attempts to use the same directive will not work. For example:
add_header Content-Security-Policy "default-src 'self'; default-src https://website.com;" always;
default-src https://website.com; will be ignored. The correct way to format this is as follows:
add_header Content-Security-Policy "default-src 'self' https://website.com;" always;
Creating and testing your own Content Security Policies
We recommend you read up on the header and its configuration using the following resources, in addition to this article.
The following are some example CSP's that you can use as a starting point to customize your own.
There are a few values that need an explanation: -
'self' This matches the current origin i.e. your domain name (but not subdomains).
'unsafe-inline' This allows the use of inline JS and CSS.
'unsafe-eval' This allows the use of mechanisms like eval().
'none' This prevents the browser from loading this type of resource.
Each of the above requires the quotes.
Setting the default
default-src value is the first thing we need to look at. Typically
'self' is enough for most websites. As explained earlier, this defines the loading policy for all resources where the resource type dedicated directive is not defined and refers to your website's address.
You can also use your websites domain name, for example:
add_header Content-Security-Policy "default-src https://website.com;" always;
And use a wildcard to include subdomains with:
add_header Content-Security-Policy "default-src https://*.website.com;" always;
Example 1: Allowing Google Fonts
Let's modify the default CSP to be a little stricter and close down the
font-src directive to only load fonts from our website and Google fonts. We need to change the value from
'self' data: to
'self' fonts.gstatic.com. We also need to ensure that we can load the stylesheet by adding
fonts.googleapis.com to the
Our edited CSP looks as follows:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src 'self' fonts.gstatic.com;" always;
Example 2: Allowing Google Fonts and YouTube video embeds
We can further expand our CSP to allow video embeds from YouTube by adding it to our
style-src and the
child-src. This will allow JS and iframes specifically from youtube.com to lead correctly on your website. The code has been highlighted in bold below:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.youtube.com/; child-src https://www.youtube.com/; connect-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src 'self' fonts.gstatic.com;" always;
Check out the further reading section at the bottom of this article for more great resources and examples.
Testing a Content Security Policy
Before you set your CSP live on your website, you may wish to test it to confirm it won't accidentally cause any problems. You may also wish to do this on a staging site and leave your production website alone for the time being until you're sure your CSP is ready to go.
Once you've created your policy, instead of adding it with
Content-Security-Policy, you can add it with
Content-Security-Policy-Report-Only. Using this, your browser report on your CSP, but not enforce it.
Checking for errors
To check for errors, head on over to your website. Open your browser's developer tools and navigate to the Console tab. Errors will look like the following, and detail the cause so you can modify your CSP accordingly and retest:
Add your customizations to your server
Step 1. Open your CSP config
SSH into your server and run the following command (switching out "site.url" for your websites domain name) to open up your CSP config:
Step 2. Paste your custom CSP
Paste your custom header into your file, replacing the line highlighted in the image below:
It begins with
add_header Content-Security-Policy. Delete the whole line, paste your own in. Confirm it's all correct.
If you're testing your CSP, instead of using
Content-Security-Policy, replace this with
Content-Security-Policy-Report-Only. For example:
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src 'self' fonts.gstatic.com;" always;
Ctrl+O and then press enter to save the file. Then Ctrl+X to exit nano.
Step 3. Check and reload Nginx
We now need to test our Nginx syntax with:
If there are no errors present, reload Nginx with the following command:
gp ngx reload
Step 4. Check your CSP on your website
First, open up your website in an incognito window. Next, right-click and choose "Inspect", select the "Network" tab, and then reload the page. The result will look similar to the image below.
Click on your URL on the left-hand side to open up the box on the right, then down until you see the "Response Headers" section. Here you will be able to see your Content-Security-Policy as outlined in the image below:
All about Content Security Policies:
Setting your CSP up for Google Tag Manager:
Further thoughts from Google on creating a CSP:
Mozilla's CSP guidance: