Content Security Policy (Ruby on Rails)

Ministry of Justice Digital & Technology
Just Tech
Published in
5 min readSep 18, 2018

by Kath Pobee-Norris (Software Development Profession)

Recently, we made changes to our codebase in regards to Content Security Policy (CSP). This article starts with a minuscule intro to CSP, then talks about why we decided to change our approach, what we changed, some unintended consequences and how we fixed things; it then ends with some observations and learnings.

“gray computer monitor turned on” by Zany Jadraque on Unsplash

Cross-site scripting (XSS) is one of the most common forms of attacks on the web. It’s achieved by someone injecting malicious code into a page, e.g. through a search feature, and the browser executing it. There are a number of ways this can be done, so it’s important that only the code you want is actually executed and you filter out anything else that might have been added along the way.

Content Security Policy is a standard introduced to provide additional security against certain types of attacks. One of these is the prevention of XSS attacks by specifying the domains the browser should trust. A compatible browser will then only execute the scripts from the trusted sources, ignoring everything else. There is a lot more information about it here: Content Security Policy (CSP) — HTTP | MDN

I am currently working on a Rails project that up until recently had been using the Secure Headers gem to manage our CSP; a one-stop-shop for security-related headers. However, as of Rails 5.2, you are now able to use their own inbuilt CSP!

The format of the Rails configuration was very similar to that in the Secure Headers gem, and so only a few tweaks to the syntax were required. Here is an example of what our original code looked like:

And this is a snippet from the revised Rails CSP:

We also have Sentry setup to alert of us of any errors, but we only want this active in development and production; not testing. Therefore we have an extra block of code that checks whether the sentry_js_dsn is present, and if so, adds the host to the connect_src.

This is the original code:

This is the revised code:

Note: There is an option to set a global default policy and then override certain elements depending on need; we just have the global policy. For more information you can check out Securing Rails Applications — Ruby on Rails Guides

After moving to Rails 5.2 all the tests that had initially been failing when I had got some of the syntax wrong were now passing again, so I pushed the code and created a pull request. A couple of my colleagues reviewed it, gave it a 👍, it got merged, I checked it on staging and we deployed it to production. Job done! ✅

Unfortunately I had introduced a bug into the code and this caused one major problem for our users. As part of our service, staff are able to click a link to preview an email; the changes I had made meant that staff clicking on the link were faced with an error page. Unfortunately it took for a user to contact us via Zendesk to alert us to the issue. As soon as we received notification that something was going wrong a colleague and I started the bug hunt. I hopped onto staging and was able to preview the email perfectly fine, and the same in production, but anyone else in the team (and staff using the site) got the error. They also got this in the console:

Refused to connect to ‘[prison_visits_site]’ because it violates the following Content Security Policy directive: “connect-src sentry.service.dsd.io”.

So immediately I knew it had to do with the changes I’d made and within a couple of minutes I had that classic facepalm moment; I’d inadvertently changed the behaviour in the if/else statement regarding Sentry. As I said, some of you may have already noticed it, but here was problem:

I had changed this:

config.csp[:connect_src] << URI.parse(sentry_js_dsn).host`

to this:

host = URI.parse(sentry_js_dsn).hostconfig.connect_src host

Whereas the original code added the host to the array my new code was overwriting the config.connect_src so “self” got deleted! As I still wasn’t getting the error I requested a colleague change the code to this:

host = URI.parse(sentry_js_dsn).hostconfig.connect_src :self, host

Before testing it and checking the console; it immediately fixed the problem so we got it out to staging where my colleague was able to check it again; still no errors! 🎉 Therefore we were able to deploy it production and let the member of staff know that the issue should be resolved, but to contact us if not.

Thankfully this fix was the right one, and we haven’t had any further Zendesk tickets from staff raising this as a problem. It was a healthy reminder that whilst a particular task might seem innocuous, that it can be easy to inadvertently change the behaviour of a thing, and cause unnecessary issues for your users. To this day I still don’t know why I was the only one who didn’t experience the issue, as hopefully I might have spotted the problem before we deployed it.

Photo by Emma Matthews on Unsplash

Finally, I was also able to gain a bit of confidence in my problem solving abilities throughout this episode. I am still relatively early on into my developer career, having just passed my one year anniversary. As I’m sure most people working in this industry know, or have experienced, it can be overwhelming at times just knowing that you can’t ever know everything. There is also the classic Imposter Syndrome, where you spend far too many minutes during the work day with sweaty palms wondering when someone is going to figure out you don’t belong and escort you from the building. One of the best things about working here is the effort people make to support you, not assign blame and create an environment where it’s ok not to know everything, and where people can make mistakes. As soon as we knew there was a problem we all jumped into action, and once I’d figured out it was my changes that had caused the issue I immediately stuck up my hand, owned the error, created the solution and we got the fix out ASAP. Rather than feeling low confidence or worried about job security, there was a real sense of something akin to pride; not causing the issue obviously, but being able to quickly spot what had gone wrong and find a solution. I am not complacent, and know that I will come up against much tougher tests than this, but what I think is important is the ability to take the wins, hold on to those little bits of confidence that come from these situations, and build up resilience for those more difficult moments. The hope being that next time you’ll already be starting from a better foundation.

~~~~~~~~~~~~~~~~~~~~

If you enjoyed this article, please feel free to hit the👏 clap button and leave a response below. You also can follow us on Twitter, read our other blog or check us out on LinkedIn.

If you’d like to come and work with us, please check current vacancies on our job board (filter on Organisation::Ministry of Justice, Job Role::Digital)!

~~~~~~~~~~~~~~~~~~~~

--

--

Ministry of Justice Digital & Technology
Just Tech

We design, build and support user-centred digital and technology services for the justice system.