Haml: Attributes for <script> tags?

Created on 26 May 2017  路  10Comments  路  Source: haml/haml

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

Most helpful comment

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

All 10 comments

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:

  • Find some API in secure header gem that is available in haml filters
  • Just don't use filter for this, render script tag like %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:

````

If you are using UJS then enable automatic nonce generation

Rails.application.config.content_security_policy_nonce_generator = ->(_request) { SecureRandom.base64(16) }

````
or this:

````

If you are using UJS then enable automatic nonce generation

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) }

馃憤馃徏

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Shamaoke picture Shamaoke  路  14Comments

dewski picture dewski  路  8Comments

yb66 picture yb66  路  4Comments

noise-machines picture noise-machines  路  4Comments

richardaday picture richardaday  路  25Comments