I'm currently looking into enabling CSP ([1] via [2]) and I've some places where I cannot remove <script>
directly. In CSP you can use a nonce
as an attribute for <script>
tags to allow inline evaluation of JavaScript.
How can I pass tag attributes with :javascript
so that they will be rendered to the <script>
-tag?
Looking at the current implementation it seems as if Haml::Filters::Javascript.render_with_options
is not designed to take attributes. Is this intentional? I wasn't able to fully follow how #render_with_options
is invoked and where it's options
parameter comes from, but I'm happy to provide a PR if someone could point me in the right direction.
Ideally I'd like to write something like this (note that content_security_policy_script_nonce
is a view helper from the secure_headers
gem:
#bla
:javascript noince: content_security_policy_script_nonce
// my JavaScript
I'm currently not impacted with inline stylesheets (via <style>
) but that should be the same problem.
[1] https://csp.withgoogle.com/docs/index.html
[2] https://github.com/twitter/secureheaders
Looking at the current implementation it seems as if Haml::Filters::Javascript.render_with_options is not designed to take attributes. Is this intentional? I wasn't able to fully follow how #render_with_options is invoked and where it's options parameter comes from
See https://github.com/haml/haml/blob/v5.0.1/lib/haml/filters.rb#L180-L185.
It takes Haml::Engine#options
or its subset Haml::Buffer#options
. It doesn't suit your use case.
Ideally I'd like to write something like this
:javascript noince: content_security_policy_script_nonce
I'm reluctant to introduce such syntax because the use case is very limited. Other possible attributes are configurable by Haml::Engine#options
.
I recommend you to solve this problem with having special filters like:
module Haml
module Filters
module SecureJavascript
include Base
def render_with_options(*args)
Javascript.render_with_options(*args).sub(/\A<script([^>]*)>/, "<script\\1 nonce='#{content_security_policy_script_nonce}'>")
end
end
end
end
:securejavascript
// your javascript
I'll give the custom filter a try. Thanks a lot!
I'm just now coming around to give this another try :-/
Anyway, @k0kubun, is there a way to access view helpers? The method content_security_policy_script_nonce
is defined a a view helper method by the secure header gem.
Anyway, @k0kubun, is there a way to access view helpers?
From haml filters, probably no.
So you have 2 options:
%script{ nonce: content_security_policy_script_nonce }= js
Probably we can find another way if we read Haml internals carefully, but I don't have time to do that for now. Sorry.
The problem is, if I read the secureheader gem code correctly (and from what it does), that we need access to the request context since the nonce is per request.
Anyway, thanks again, maybe I'll go with the %script
way for now. There aren't many inline scripts left over anyway :)
I was able to add nonce
attribute to my <script>
and <style>
tags by doing these two changes.
Add a before_action filter in ApplicationController
so content_security_policy_nonce
value is saved using ActiveSupport::CurrentAttributes so it can be accessed later from within Haml
filters
# app/controllers/application_controller.rb
before_action do
Current.content_security_policy_nonce = content_security_policy_nonce
end
Override Haml filters
# config/initializers/haml_filters.rb
Haml::Filters::Javascript.module_eval do
alias_method :orig_render_with_options, :render_with_options
def render_with_options(*args)
orig_render_with_options(*args).sub(
/\A<script([^>]*)>/, "<script\\1 nonce=\"#{Current.content_security_policy_nonce}\">"
)
end
end
Haml::Filters::Css.module_eval do
alias_method :orig_render_with_options, :render_with_options
def render_with_options(*args)
orig_render_with_options(*args).sub(
/\A<style([^>]*)>/, "<style\\1 nonce=\"#{Current.content_security_policy_nonce}\">"
)
end
end
The good thing about this approach is that there's no need of creating new filters
, :javascript
and :css
add nonce
tag now.
@jairovm Does this really work for you? I have set this up but the nonce in the nonce attribute of the script tag does not change from request to request if the script tag does not contain any Ruby variable interpolation. It seems like HAML is somehow caching those "static" script blocks.
It's been working fine for me, make sure you uncomment this out in content_security_policy.rb
initializer
# If you are using UJS then enable automatic nonce generation
Rails.application.config.content_security_policy_nonce_generator = ->(_request) { SecureRandom.base64(16) }
@jairovm Thanks for the solution :)
I don't understand "uncomment this out" in your last message.
Do you mean it should look like this:
````
````
or this:
````
Rails.application.config.content_security_policy_nonce_generator = ->(_request) { SecureRandom.base64(16) }
````
Thanks :)
It should look like this
# If you are using UJS then enable automatic nonce generation
Rails.application.config.content_security_policy_nonce_generator = ->(_request) { SecureRandom.base64(16) }
馃憤馃徏
Most helpful comment
See https://github.com/haml/haml/blob/v5.0.1/lib/haml/filters.rb#L180-L185.
It takes
Haml::Engine#options
or its subsetHaml::Buffer#options
. It doesn't suit your use case.I'm reluctant to introduce such syntax because the use case is very limited. Other possible attributes are configurable by
Haml::Engine#options
.I recommend you to solve this problem with having special filters like: