Removing inline JS from Grav for a stronger Content Security Policy

April 24, 2017

I recently migrated over to Grav for this blog, moving away from a custom setup. I'm liking the simplicity of the file system based approach, but have found some elements to be a bit lacking. For example, trying to implement a Content Security Policy where the unsafe-inline directive doesn't need to be set is a bit of a challenge out-of-the-box. In fairness, this is mostly the fault of plugins and not Grav itself (namely Highlight and Admin Panel in my setup), but Grav does have an API for adding inline JS which will require unsafe-inline, so it's not completely outside its control.

Initially, I had planned to leave inline JS in place and simply add a nonce value to the output, along with a CSP header with the nonce. However, to be a portable solution, this approach would require that Grav handled the entire CSP generation process, as outputting a header solely for this purpose would clobber any existing CSP header set by the web server, at least for the script-src directive. So, instead I opted to just replace inline JS generation with dynamic JS file creation - the following patch file shows the changes against Grav 1.2.4:

https://gist.github.com/ndavison/29708215fd5b4af2f2e2379dd2c24c96

This will modify the \Grav\Common\Assets::js() method to dump any script that would have been inline to a file instead, which is then added to the output via a standard <script src=""> tag. This is very much a hack of a solution, but it will allow me to remove unsafe-inline, at least from the front-end and using the suite of plugins I do - unfortunately the Admin Panel plugin doesn't seem to use much of the Grav API for JS output and uses inline JS in places, and other plugins probably do much the same. For these instances, there's not much you can do other than manually modify them, and/or submit bug reports against their repos (although this bug has been open against Admin Panel for a while and doesn't look to be near resolved).

Back to the Grav API, I think the best and most realistic outcome would probably be a further site configuration option within the JS pipeline settings that, when enabled, would include the inline JS that uses the Grav API inside the single JS file produced. This way it is capable of being enabled but doesn't affect how Grav currently works too much. As for the plugin problem, well, that really calls for higher scrutiny on what patterns plugins should follow - ideally, no inline JS, but if necessary, then at least use the Grav API to output it so it can be included in any future improvements to how Grav handles inline JS.