Tuesday, Apr 10, 2018
I recently learned about Mozilla Observatory security site scanner. It examines your markup and HTML headers and scores your site with a letter grade. I was a bit chagrined to see our https://www.aerobatic.com receive a failing F grade 😞. As a web company promoting best practices we should do better, time to hit the books and improve that mark.
First it’s worth getting to understand a bit about Content Security Policy (CSP). Here’s how the MDN summarizes it:
Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft to site defacement or distribution of malware.
Content-Security-Policy http header it is possible to specify a set of rules for the browser dicating what content sources are valid. The policy can specify different rules depending on the resource type, i.e. scripts, stylesheets, images, etc. You can also specify whether inline script and styles are allowed to be executed. The more strictly you lock this down, the more secure your site, and the better grade Observatory will grant.
The strictest possible CSP is
"Content-Security-Policy": "default-src 'self'". This states that all resource types can only be loaded from the same the same domain as the website itself (sub-domain included), and no inline scripts, styles, or dynamic script evaluation are allowed. If you aren’t loading resources from any 3rd party domains or using 3rd party libraries, this gold standard should be readily achievable. However as soon as you start to utilize things like Google Analytics, Google Fonts, 3rd party CDN hosted scripts or stylesheets such as cdnjs, or other client-side widgets (Disqus, Buffer, and so on) the story gets more complicated quickly. Google Analytics, in particular, presents some challenges which I’ll discuss further considering its ubuiquitousness.
The MDN article on CSP is an excellent resource for learning more.
plugins: - name: http-headers options: "Content-Security-Policy": "default-src 'self'"
Content-Security-Policy is far from the only security related HTTP header. Additional ones include:
You could certainly configure each of these headers individually in the
http-headers declaration. However there is a shortcut
security property that you can set to
true which will provide the recommended values for all of these. You can always set the property to true and explicitly override individual header values. In fact the
security property will specify a default CSP value also, but this is the one that most likely needs to be fine-tuned for your website.
plugins: - name: http-headers options: security: true "Content-Security-Policy": "default-src 'self'"
As mentioned earlier, making GA play nicely with CSP is not terribly straightforward. This deep dive goes into much greater detail than I’ll cover here, but here’s the basic ingredients for a GA friendly CSP:
data:scheme-src in the
.jsfile to avoid needing to enable
Here’s what the resulting CSP looks like:
default-src 'none'; img-src 'self' data: https://www.google-analytics.com https://www.google.com https://stats.g.doubleclick.net; script-src 'self' 'unsafe-eval' https://www.google-analytics.com; style-src 'self'; font-src 'self'; connect-src 'self'; form-action 'self'; base-uri 'self'; frame-ancestors 'self'
unsafe-inline in the
If you are loading assets off a separate CDN domain, you should consider utilizing one that offers sub-resource integrity. This mitigates the possibility of a compromised script getting executed by ensuring that the contents of the file match a cryptographic hash specified in the
<link> tag. Cdnjs allows you to copy an HTML tag snippet that has the
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="></script>
You can boost your Observatory score if all your external domain resources utilize a sub-resource integrity. Unfortunately Google Fonts and Google Analytics both do not support SRI.
For our www.aerobatic.com site, here is the configuration in our
aerobatic.yml that we landed on.
plugins: - name: http-headers options: security: true "Content-Security-Policy": "default-src 'none'; img-src 'self' data: https://www.google-analytics.com https://www.google.com https://stats.g.doubleclick.net; script-src 'self' 'unsafe-eval' https://www.google-analytics.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net; font-src 'self' https://fonts.gstatic.com https://cdnjs.cloudflare.com; connect-src 'self' https://*.algolianet.com https://*.algolia.net; form-action 'self'; base-uri 'self'; frame-ancestors 'self'"
With these changes we were able to raise our Observatory score from that shameful F to an A-. Not perfect, but pretty respectable. https://observatory.mozilla.org/analyze/www.aerobatic.com
I’d encourage you to test your own site on https://observatory.mozilla.org. With just a few lines of YAML, you can likely make a big improvement in your score. It’s a good idea to experiment with different
Content-Security-Policy header values on a test stage first. Then deploy it to production when you are confident that your policy does not break any functionality.
$ aero deploy --stage test