The most common way to set a Content Security Policy on your site is to deliver it as a HTTP response header, but that's not always possible. On hosted platforms like GitHub Pages, Ghost Pro or WordPress it's not always easy or even possible to set a HTTP response header. The alternative is to set it via a meta tag but there are some limitations to that, limitations that we'd like to remove.


Content Security Policy

For those of you familiar with Content Security Policy (CSP) you will know that it's typically delivered as a HTTP response header:

Content-Security-Policy: default-src 'self'; upgrade-insecure-requests; report-uri

This is a rather basic policy, yes, but it's exactly what you'd expect to see from a CSP. We have a default-src set for loading content, upgrade-insecure-requests to take care of mixed content and report-uri to enable reporting and send reports to Report URI for real-time monitoring of exactly what's happening on the site. Great! That is unless you're on a hosted service that doesn't allow you to set HTTP response headers, a service exactly like GitHub Pages.

Hosted platforms

There are quite a few scenarios out there where you may have your site hosted and not be able to set HTTP response headers on your pages. GitHub Pages and Ghost Pro are just a couple of the large examples out there and with both of these you can only control the HTML in the page and can't run application code or change server config to set response headers. This was a limitation considered in the CSP specification and you can set a CSP using a meta tag instead of a response header.

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; upgrade-insecure-requests">

So we now have our CSP set in a meta tag instead of a response header, which is great, but did you notice what was missing? The CSP doesn't contain the report-uri directive because it's not allowed according to the spec.

Remove all occurrences of report-uri, frame-ancestors, and sandbox directives from directive-set.

The original thoughts around this limitation were to prevent an attacker that could inject the meta tag themselves from being able to create a flood of reports using the report-uri directive. For a similar reason you can't declare a CSP-Report-Only meta tag either, as their only real purpose was to send reports. Times have moved on though and we also have a script interface to interact with violations now and I raised two bugs against the spec to bring some new features in CSP 3. The first one was to allow report-uri in meta tags so that these policies could report and the second one was to allow the declaration of CSPRO in meta tags. The first issue was closed and won't be coming, which does make sense, but more importantly we will be getting CSPRO in meta tags in CSP 3! This means we can safely deploy and test a policy before enforcing it and use the new script interface to handle reporting. It's a win all around.

Report URI JS

Getting started with Report URI JS is really easy and only requires two lines of HTML! The first is the config you want Report URI JS to use and comes in the form of JSON, like so.

<script type="text/json" id="csp-report-uri">
    "keys" : ["blockedURI", "columnNumber", "disposition", "documentURI", "effectiveDirective", "lineNumber", "originalPolicy", "referrer", "sample", "sourceFile", "statusCode", "violatedDirective"],
    "reportUri" : "https://{subdomain}"

The keys array defines the values you'd like taken from the SecurityPolicyViolation event and placed into the CSP report that's sent and the reportUri value is where you'd like the CSP report sent. All you need to do is update this with your own subdomain and the reports will be sent straight into your account.

The second line that's required is to load the library itself.

<script src="" integrity="sha256-/u7ebXQXcESMpl6YCvyBEqs83Wt+JpsaMvO8sXFbIH0=" crossorigin="anonymous"></script>

Of course we recognise the significance of loading script from our CDN so the script tag has SRI enabled to protect you and your visitors and it's hosted via Cloudflare with very aggressive cache settings to make it as fast as possible. Once you've added these two lines that's it, the browser will now start sending reports from your site. We recommend adding these two lines, along with your CSP meta tag, as early as possible in your <head> because the CSP will only apply to elements present after the policy itself.


If you'd like to see this in action, and just how simple it is to setup, we've setup a demo page right here where you can go and take a look:

Future Directions

Right now Report URI JS is fairly simple and is designed to replicate the browser's native reporting functionality where it's not available, but we're not going to stop there. Our vision is for more and more users to want to switch to using Report URI JS instead of using the browser's native capabilities as we add more features. Those features will include:

  • Filtering: Why send a report when it will be discarded by the filters set on your Report URI account? Stop the report from being sent in the first place.
  • Sanitisation: We already sanitise certain values from reports at our edge but you may want to do that on the client to prevent them being disclosed to us.
  • Normalisation: Ensure that reports stack nicely in your account by normalising them before they're dispatched. You're free to adapt any field in the report.
  • Discarding: Don't want a report? Don't want to waste your quota? Simply throw it away before sending! You can discard a report based on any criteria you like.
  • Downsampling: An early CSP deployment can be very noisy and consume a lot of your quota. Downsample reports randomly to save quota but still have full coverage.

If you wanted to leverage these features all you'd have to do is remove the report-uri directive from your CSP to prevent the browser firing reports and then load our library following the steps above to handle the events instead. We're hoping to quickly mature the library and add new features as soon as we can so keep an eye out for announcements!

Browser Support

I'm happy to say that browser support is pretty great and we have support in Chromium (Chrome et al.), Edge, Safari and the next version of Firefox too! That's pretty much full support across the board but even so, the support we have now is more than enough. With such large coverage already in place you can be fairly sure that any issues on your site will be reported by the majority of your users who already have support.

Here's the official repo: