I’m a big proponent of the content security policy paradigm (CSP) supported by modern browsers. In fact I’m so keen on them I even wrote a Pluralsight course: Introduction to Browser Security Headers. (Sidenote: I’m enormously happy with how well this course has been received, seems there’s an appetite for securing our things after all!)
Now if you’re not sure what all the fuss is about, have a quick read of my launch blog post for the course. This gives a pretty good overview of why CSP (among other headers) is awesome. Writing all this content about how the headers work should mean I know what I’m doing with them, right? Yeah, about that…
I wanted to share this to demonstrate that whilst awesome, it’s easy to break things you never expected. Keep in mind that the whole idea of a CSP is that you whitelist the sources of various types of data that you know to be good. If you forget to whitelist something in the CSP header, a browser that recognises the header simply won’t load it. Let me demonstrate by showing you this image of the Have I been pwned (HIBP) status page:
Now two things should stick out at you here:
- You’re looking at the page loaded in Safari on my iPad
- You’re not looking at much – there’s a big white box on the screen where New Relic graphs should be appearing
This is curious as it worked fine on my machine (famous last words), or at least it worked fine on my PC. Then someone gave me a bit of “dude, your site’s broken” and after the obligatory “it’s not me, it’s you” discussion, I decided to poke around further.
I started by proxying the iPad through Fiddler (there’s a course on that too!) then I loaded up the status page on the device. Here’s what I found:
It all starts to go wrong with the three highlighted requests. These are CSP violation reports which the browser sends back to the report-uri path declared in the CSP header when something goes wrong. That’s three reports, three missing New Relic graphs, something’s starting to smell here…
Now the neat thing about CSP violation reports is that they explain what went wrong because they contain a request body like so:
In this case we can see that the New Relic URL https://rpm.newrelic.com is being blocked on the page at https://haveibeenpwned.com/Status because it’s violating the “default-src 'self'” directive. Now this is odd because here’s how those graphs are embedded in the page:
<iframe src="https://rpm.newrelic.com/public/charts/loQTOR6Umg7" scrolling="no" frameborder="no"></iframe>
The reason it’s odd is because the New Relic URL is the source of an iframe and that was already whitelisted in my CSP using this directive:
So what’s going on? As with all tricky CSP things, I turned to Scott Helme and it turns out that he has the very aptly titled blog post Safari doesn't like CSP. Scott talks about a number of idiosyncrasies with Apple’s browser in there and he wasn't overly surprised when he heard about what my iPad was doing. As it turns out though, the solution was very easy:
Uh, ok then, why? Firstly, here’s what the frame-src directive actually does:
Oh but hang on, it’s deprecated, let’s use child-src instead. Oh no, now it’s broken in Safari, let’s use frame-src. Damn, it’s deprecated, let’s use…
The answer is to use both child-src and frame-src and live with the code stench of a redundant declaration including the deprecated directive. Dammit. It’s annoying, but this fixes it right up and now it looks like this:
This is one of many idiosyncrasies with CSPs, but here’s the real message I want to leave you with: CSP can break your things even if you configure it “correctly”. My CSP aligned fully with the spec but because Safari doesn’t recognise the agreed standard, it broke. Usually I tell people not to worry if you add a CSP and then someone comes by with a browser that doesn’t recognise it because it will just ignore it. But you can actually break your website due to a screwy browser implementation and even though this was a minor inconvenience in my case, I’m not real happy that it happened in the first place.
One sage piece of advice here: monitor your CSP reports. I log the reports but I don’t actively monitor them. Scott actually has a service to do this for you for free over at report-uri.io. You send your reports there then he logs them and makes them available for review. The only reason I hadn’t been using his service for HIBP is that I was reluctant to send people’s data to an external service, even though it’s just violation reports that shouldn’t happen and don’t disclose anything beyond what you see in the violation report above (and the user’s IP address and request headers, neither of which Scott logs according to his FAQ).
But now I’m reconsidering because I should have caught this problem earlier, so what do you think – should I send HIBP reports to report-uri.io? Leave your thoughts in the comments, if there’s enough support insofar as people don’t see it as a privacy issue, I’ll roll over to Scott’s service and hopefully head future issues like this off a lot earlier.