-
Good: Rails includes built-in tools to generate a CSP
-
Great: That CSP encourages disallowing unsafe evaluation of inline JS
-
Incredible: Rails includes
javascript_tag(nonce: true)
helper so you can include nonced inline JS -
WTF If you use all these tools together with Turbolinks none of the nonces work.
If you want UJS, Turbolinks, and other inline nonced JS to work you need to do the following:
- Change Nonce generation so that nonces do not change for turbolinks requests (as the DOM is not updated)
# In config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy_nonce_generator = -> (request) do
# use the same csp nonce for turbolinks requests
if request.env['HTTP_TURBOLINKS_REFERRER'].present?
request.env['HTTP_X_TURBOLINKS_NONCE']
else
SecureRandom.base64(16)
end
- Inject a header into turbolinks requests so the above nonce generation code works
// Somewhere in /app/javascript
document.addEventListener("turbolinks:request-start", function(event) {
var xhr = event.data.xhr;
xhr.setRequestHeader("X-Turbolinks-Nonce", $("meta[name='csp-nonce']").prop('content'));
});
- Because nonces can only be accessed via their IDL attribute after the page loads (for security reasons), they need to be read via JS and added back as normal attributes in the DOM before the page is cached otherwise on cache restoration visits, the nonces won’t be there!
// Somewhere in /app/javascript
document.addEventListener("turbolinks:before-cache", function() {
$('script[nonce]').each(function(index, element) {
$(element).attr('nonce', element.nonce)
})
})
All of this is outlined in the following Turbolinks issue https://github.com/turbolinks/turbolinks/issues/430
As a Rails user, I expect that as long as I stay on the well-trodden path, the tools should work together harmoniously. The CSP nonce generation should work with Turbolinks and UJS out of the box.
I suspect many Rails apps do not enable a Content Security Policy and that makes me sad, and makes me question if it should be on by default. IMO, the best time to create a solid CSP is at the start of a new app, not later on in the apps lifecycle when you have code that is dependent on insecure practices.
As an experienced dev, I came up with a solution so that I could keep Turbolinks in my project and use JS nonces with a CSP, but I could see a new person swearing off Turbolinks after the experience, assuming it just isn’t supported enough to remain in the project, especially since it is conflicting with a critical security feature like CSP.