Practical prevention of web shenanigans with Content Security Policy

Save a bunch of time by leveraging known CSP settings for Stripe, YouTube, Twitter and other popular embedded JavaScript apps

By Mike on 1st Sep 2015

An attacker visits a site and registers a <script> tag as their username. When a regular user visits the site and sees the attacker's username, the script tag is inserted into the page and the regular user's session is compromised by the attacker. The attacker can now grab passwords, payment information, and other private data from the regular user. Shenanigans occur.

This is Cross Site Scripting, or XSS.

Modern templating languages escape variables by default to prevent this, i.e.: {{ someVariable }} escapes someVariable by default. However:

A better solution is Content Security Policy (also known as BATSHIELD), created by Mozilla. It's a well supported HTTP header that says which origins the site allows for images, scripts, styles and other types of content. Anything else is blocked by the browser. If you're not familiar with CSP, read this excellent introduction from Mike West from the Chrome team (also known as my quest.

Our own site recently adopted CSP. Our goals were as follows:

The basics were easy:

Some parts were more difficult: here's how we handled them.

CSP requirements for third party APIs

Third party APIs are a common part of the modern web. They're used for payment processing, font hosting, database as a service apps, showing happy tweets from customers, and a lot more besides.

So a large chunk of the work for CSP isn't in creating a policy for your own code: it's discovering, merging and managing policy requirements for third party APIs.

Our site uses the following third party APIs. Most did not have official documentation on CSP:

API Documented CSP Policy
Google Fonts No documented policy
Mixpanel No documented policy
Ractive.js Documented policy
Stripe Documented policy
Twitter oembed API No documented policy, but some CSP notes
Typekit Documented policy
Stormpath No documented policy

We patched the ractive docs ourselves shortly before this article. Otherwise, only Stripe and Typekit provided full policies, detailing the individual settings required for their services. Typekit uses inline styles for font events, but explained the requirement clearly in their documentation. We're happy to go without font events, however the CSP violation would still appear as on the console, so we've allowed inline styles until we've finished talking to Typekit.

Stripe specifies an exact, conservative policy, which is great, although they forgot a domain - they use q.stripe.com for error reporting. Their CSP requirements are also slightly hidden in an article about PCI DSS. On the other hand, their competitor Braintree just has a list of domains and wouldn't provide info about which domains are used for scripts, images etc. when asked.

So we built the CSP policy for each third party API, starting with official policies where possible, and then testing with CSP's report-only mode.

However we still wanted to keep the individual third party API policy requirements separate from our base policy. For example, we're moving from Google Fonts straight over to Typekit. When we get rid of Google Fonts, we want to be able to simply remove Google Fonts from our CSP policy.

We wanted something like this:

var policy = cspByAPI(basePolicy, ['twitter', 'mixpanel', 'googleFonts', 'stripe', 'typekit', 'ractive', 'stormpath']);

I.e., produce a merged, sorted, non-redundant policy from the named entities. It didn't exist, so we wrote it.

It's not particularly earth-shattering code - however we do think it represents an excellent common-sense approach to handling CSP for modern websites. It should also save you a bunch of manual testing and research if you use any of the libraries mentioned.

Official policies are used wherever they're made available, and all are tested in a production app. Pull requests are welcome if you'd like to add policies for additional APIs, and if you'd like to port to Python / Ruby / Elixir / Go / whatever else you're more than welcome.

Including server variables in the browser

A common technique to include data from the backend into the front end is to include something like this in a backend template:

<script>
    var serverVars = {{{ serverVars }}}
<script>

Where {{{ serverVars }}} is a JSON-encoded set of variables.

Doing this with CSP requires the use of script-src unsafe-inline, which defeats a large part of the purpose of using CSP.

Instead, we made a non-executable <script> tag, eg:

In our backend template:

<script class="server-vars" type="application/x-configuration">
  {{{ serverVars }}}
</script>

The <script> tag here is not JavaScript, it's just data.

Then in a script tag on our server:

var serverVarsElement = document.querySelector('.server-vars')
if ( serverVarsElement ) {
    window.serverVars = JSON.parse(serverVarsElement.textContent);
}

This allowed us to get backend data into the frontend without inline JavaScript.

Handling CSP violation notifications

Browsers that encounter CSP violations will POST the details of the violation to the configured CSP report URI. We originally just logged these, then collected logs directly from Linux's journalctl, but a colleague mentioned report-uri.io, a new web service for collecting and summarizing CSP log information.

report-uri.io is new but very promising: it's a great way to:

With report-uri.io we were able to find things we'd missed in our original policy (StormPath uses bootstrap) as well as find common violations - now we've finished our policy, violations are mainly from browser extensions.

The future

Some thoughts after implementing CSP:

We hope that was helpful and good luck with your CSP endeavors!

Mike MacCana, founder at CertSimple.

CertSimple makes EV HTTPS fast and painless.

An EV HTTPS certificate verifies the company behind your website. But getting verified is a slow painful process. CertSimple provides EV HTTPS certificates 40x faster than other vendors. We check your company registration, network details, physical address and flag common errors before you pay us, provide verification steps specific for your company, update in realtime during the process, and even check your infrastructure to help you set up HTTPS securely.
Verify your site now!