Implementing a Content Security Policy#

Introduction to CSP#

Implementing a Content Security Policy (CSP) for a website can be a daunting and difficult task as it can break your website when done incorrectly. But a CSP can help to guard against cross-site script attacks and data injection attacks on a website as it defines which resources are all allowed to be loaded or executed. This also reduces the risk of including unauthorized third-party content to be included or posted to another site.

Using CSP#

Configuring CSP can be done by adding a HTTP response header or by adding a META-tags to the requested HTML content. The latter depends on the CMS to be executed correctly for all HTML content requested. Most sites therefore implement the HTTP response header method as all the common web servers and content delivery networks support this.

The basic syntax for a HTTP response header looks like the example below where the header is named Content-Security-Policy and can have one or more policy directive behind it.

Systax for the CSP header in enforcing mode#
Content-Security-Policy: <policy-directive>; <policy-directive>

The example above directly enforces the policy set in the Content-Security-Policy header, but this may break websites when the policy is incomplete. With the header Content-Security-Policy-Report-Only the set policy is still active, but only reports violations in the web browsers development tools or sends them to collection service as described in Collecting CSP reports.

Syntax for the CSP header in reporting-only mode#
Content-Security-Policy-Report-Only: <policy-directive>; <policy-directive>

Another possible option is to include the header as a META-tag in the header of a HTML document. This way isn’t widely used as content management systems has to support this option to generate the correct policy for every HTML document requested.

Syntax for the CSP HTML META-tag in enforcing mode#
<meta
  http-equiv="Content-Security-Policy"
  content="default-src 'self'; img-src https://*; child-src 'none';" />

Warning

Setting up a Content Security Policy (CSP) and enabling it for a website takes time. Always start with the Content-Security-Policy-Report-Only header until no violations are reported anymore or can be explained and have no impact on the functionality of the website.

Collecting CSP reports#

Starting with CSP starts by setting up the directive report-uri to collect all violations detected by web browsers. The service called Report URI is a good start for most to start with and has a Free-tier with 10.000 free reports per month which should be enough for most small websites.

After generating the URL with Report URI a minimal content security policy can be added to the _headers file so Cloudflare Pages can add the header for all content served via Cloudflare Pages.

Adding a CSP policy to Cloudflare Pages via the _headers file#
/*
  Content-Security-Policy-Report-Only: default-src 'self'; report-uri https://<id>.report-uri.com/r/d/csp/reportOnly

Looking at the example more in depth we see that line 1 is for Cloudflare Pages to select for which URLs it needs to serve which headers and here all URLs are being selected. Line 2 shows that directive report-uri is set to the reporting URL we got from Report URI which takes care of the reporting. The line also shows the HTTP header Content-Security-Policy-Report-Only for the browser to only report violations for the policy that is defined behind it in the line.

Note

All examples are written for Cloudflare Pages, but the example can be adjusted as shown below for both Apache HTTPd and Nginx.

Example configuration for Apache HTTPd#
<Location "/">
Header always set Content-Security-Policy-Report-Only "default-src 'self'; report-uri https://<id>.report-uri.com/r/d/csp/reportOnly"
</Location>
Example configuration for Nginx#
add_header Content-Security-Policy "default-src 'self'; report-uri https://<id>.report-uri.com/r/d/csp/reportOnly" always;

Extending the CSP policy#

In the previous section a basic policy was defined and all violations for that policy are reported by the browser. Over time Report URI will be filled with all violations and those can be added to the policy as they’re allowed, or solved by removing them from the site, or being accepted as valid violations that must be stopped by the browser. One of the most common policies to add is for Google Tag-Manager to allow analytics to be collected. The example below has been extended to allow Google Analytics and Adsense to work correctly.

Extending the CSP policy file _headers for Google Analytics and Adsense#
/*
  Content-Security-Policy-Report-Only: default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' https://*.googletagmanager.com; script-src-elem 'self' 'unsafe-inline' https://unpkg.com https://*.googletagmanager.com https://static.cloudflareinsights.com; img-src 'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://*.g.doubleclick.net https://*.google.com https://*.google.nl; connect-src 'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://*.g.doubleclick.net https://*.google.com https://*.google.nl https://cloudflareinsights.com; report-uri https://<id>.report-uri.com/r/d/csp/reportOnly

Enforcing CSP#

Enforcing a Content-Security-Policy isn’t required for sites to function correctly and for static websites the usefulness is questionable as it must be maintained. A lot of website currently only have their Content-Security-Policy in Report-Only mode. The example below is to enable the Content-Security-Policy and violations are submitted to a different URL to mark them as a policy violation.

Enforcing a CSP policy via the _headers file#
/*
  Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' https://*.googletagmanager.com; script-src-elem 'self' 'unsafe-inline' https://unpkg.com https://*.googletagmanager.com https://static.cloudflareinsights.com; img-src 'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://*.g.doubleclick.net https://*.google.com https://*.google.nl; connect-src 'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://*.g.doubleclick.net https://*.google.com https://*.google.nl https://cloudflareinsights.com; report-uri https://<id>.report-uri.com/r/d/csp/enforce

Note

Currently no technical requirements are known to enforce a content security policy. There could be a regulatory requirement to do so, or to reduce the risk profile. It is advised to consult local legal advice for this.

Future changes#

While the directive report-uri has been deprecated in favor of directive report-to, the new directive hasn’t been widely adopted. The lack of adoption makes it difficult to switch as Report URI doesn’t offer support yet, but also browsers don’t support the directive yet. For now it is advised to use the report-uri directive until services like Report URI offer support.