How I failed to implement CSP
Lessons learned from adding Content Security Policy to a simple site
I keep hearing about CSP (Content Security Policy) and how great it is. It offers fine control over what is allowed on the site and what connections can be made by the users’ browsers. Its primary purpose is to give a tool to developers to prevent malicious scripts from doing any damage and to limit what trusted ones are allowed to do.
It’s great in theory and I was sold from the moment I first read about it.
The next logical step is to implement it for this site, which should be easy, right?
While I could’ve written a CSP policy by hand, I decided to use Scott Helme’s report-uri CSP wizard which can generate it for me.
The service utilizes two features of CSP:
- It supports a report-only mode that does not prevent the offending resource from loading but still reports the violation to a reporting URL.
- And it can block everything with a simple policy:
Content-Security-Policy-Report-Only: default-src 'none'; form-action 'none'; frame-ancestors 'none'; report-uri <url>
The wizard then collects the reports and lists the rules needed.
After deployment, just wait for the users to crawl the site and you’ll have a policy made for you that allows everything that is needed. This sounds the easiest way to get up and running.
Problem #1: False positives
After running the wizard for a few days, I got a ton of such rules (99 and still counting):
These rules are obviously not required by this site, but the visitors’ browsers’ loaded them while browsing.
What are these?
These are browser extensions that load some additional stuff on every page.
Should I allow them? Nope, these are false positives. But having them clutter the wizard makes it hard to spot what is really needed.
For example, at one point
code.jquery.com appeared on the list. Should I allow or block it? I don’t think I added a feature that loads jQuery from a remote site, but there is a chance I forgot something. Blocking it would then break a feature. But it’s more likely to be a false positive.
Problem #2: Third-party plugins
You might have noticed that there is Facebook chat below the posts. It is loaded in an IFrame, and it loads the resources (images, scripts, styles) it needs.
What rules are needed for Facebook chat to work?
I could just allow everything that seems related to Facebook, or I could just inspect what my browser reports when I open a page.
There are two problems with both approaches.
First, can I be sure that the same rules are required for all users? What if Facebook uses different domains for some users, for example if they are located in a specific geographic region, or have a setting that provides them a different experience?
And second, how do I know if the rules required are constant? What if Facebook decides to use a new CDN URL or loads some scripts from a different domain, which in turn breaks functionality on my site?
Similar problems with SRI
There is a similar security feature, called SRI (Subresource Integrity). In a nutshell, it works by defining the resource’s expected content’s hash and the browser refuses to load if the content does not match it.
For third-party dependencies that do not use versioned assets, you can not be sure they don’t change it without notice. Hardcoding the hash would break functionality in that case.
CSP is great for sites that control everything they serve. In that case, after some trial and error, the required rules can be collected and since they don’t change unless the site itself changes, they won’t break functionality.
But as soon as you include something from a third-party, this premise breaks.
If a security feature breaks functionality, the first reaction is to disable it. With CSP, while you control what third-parties can do on your site, you also open the possibility for them to break it. After the first 3 a.m. call that “something is broken”, you’ll value reliability much more than security.