Dogfooding Chronicles: Never Be Content With Your Content (Security Policies)
Put simply, a CSP lets a browser know which content sources are to be trusted — and which aren’t. And when there is a CSP violation, browsers can submit the error to a
report-uri. What’s more, if you have Sentry, these violation reports are integrated into your application’s monitoring dashboards.
During our most recent penetration test, we found that some of the CDN domains in our allow list were hosting potentially dangerous scripts. One of our recommendations from this audit was to refine and improve our CSP rules.
Content Security Policies have two modes. The first mode enforces and actively blocks resource loading and execution, while the second collects the errors that would happen if the rules were active. This mode is set via the invaluable
Content-Security-Policy-Report-Only header that defines your CSP rules.
When I combined this ‘report-only’ feature with Discover queries, I was able to visualize all the errors coming from
report-only mode. Now I could view the impact of fixes in real time, and see which rules were being broken — without disrupting customers.
report-only mode worked well, I did encounter some hiccups in Safari, as it’s been known to jumble its report-only and enforced modes. This meant I was getting errors for sources that should be allowed. To verify that these errors were caused by Safari bugs, I used our staging environment to test Safari with only the new rules being enforced. And using Dashboards, I was able to fully view Safari’s impact on our CSP errors.
Like reading a good detective novel, once I solved this problem, another, more confounding problem bubbled up to take its place. Our Jira integration was having trouble integrating with our CSP. Because our Jira integration includes an embedded ‘glance view’, I could view Sentry data alongside other fields like assignee, priority and labels. After reviewing these embedded views, I discovered the issue: outdated libraries that relied on
eval() expressions which are not allowed in our CSP rules.
While upgrading these libraries to newer versions resolved a large number of errors, it didn’t solve them all. And though I was unable to reproduce these last few errors, I was able to use Discover’s tag breakdowns to narrow things back down to — you guessed it — a Safari-specific issue:
The newest iteration of CSP has been in production for years, and is about to be fully released. Which is why we have defined rules for all three levels to get the broadest — and sturdiest — coverage possible. Doing so has both simplified our CSP configuration and improved our resilience to cross-site scripting. Which brings me to my last tip: it’s important to define directives in the order of their levels. Browsers will often stop parsing a directive when they find something they don’t know, so it’s important that there are specific directives for all three CSP levels.
Developing a Content Security Policy isn’t about being fussy — it’s about being functional. Data and code from untrusted sources should not have the same privileges as the trusted programmer’s code. By implementing a well-defined CSP, you can mitigate attacks, control assets, and breathe easier.