View escaping in Rails 3

Presented by
Greg Hurrell
@causes

Escaping is on by default

  • You no longer need to use h to escape untrusted input
  • Everything is untrusted by default, and Rails will automatically escape it
    
    # the Rails 2.x way:
    <%= h @cause.name %>
    
    # the Rails 3.x way:
    <%= @cause.name %>
                  
  • This is a security win because blacklisting is easy to get wrong; with whitelisting, you can still make mistakes but they're less likely to be security holes

"HTML-safe" strings

  • Internally Rails keeps track of whether or not a string is "HTML-safe"
  • User input (and infact any string not explicitly marked as trustworthy) is not considered safe
    
    "foo".html_safe? # => false
                  

Rails helpers

  • HTML generated by Rails helper methods like link_to is marked as HTML-safe
    
    str = link_to 'start', new_cause_url
    str.html_safe? # => true
                  

Marking content as safe

  • You can mark a string you know to be safe using html_safe:
    
    str = "<em>I'm safe!</em>".html_safe
    str.html_safe? # => true
                  
  • You can also output unsafe strings without escaping using the raw helper:
    
    <%=
      str = "<em>I contain raw HTML</em>"
      str.html_safe? # => false
      raw(str) # the HTML will be inserted without escaping
    %>
                  

Appending: safe + unsafe

  • Appending to an HTML-safe string automatically escapes the appended content
  • The resulting string is still considered HTML-safe
    
    str = 'trusted content'.html_safe
    str.html_safe? # => true
    str += "<em>untrusted</em>"
    str.html_safe? # => true
    str # => "trusted content&lt;em&gt;untrusted&lt;/em&gt;"
        # string won't be escaped when rendered
                  

Appending: unsafe + safe

  • Appending HTML-safe content to an unsafe string produces an unsafe result
    
    str = 'not trusted'
    str.html_safe? # => false
    str += "<em>trusted</em>".html_safe
    str.html_safe? # => false
    str # => "not trusted<em>trusted</em>"
        # entire string will be escaped when rendered
                

Appending: overview


> safe = '<em>foo</em>'.html_safe
=> "<em>foo</em>"
> unsafe = '<strong>bar</strong>'
=> "<strong>bar</strong>"
> (safe + unsafe)
=> "<em>foo</em>&lt;strong&gt;bar&lt;/strong&gt;"
> (safe + unsafe).html_safe?
=> true
> (unsafe + safe)
=> "<strong>bar</strong><em>foo</em>"
> (unsafe + safe).html_safe?
=> false
          

A real-world example


def breadcrumbs *crumbs
  content_tag :div, :id => 'breadcrumbs' do
    [link_to('Home', root_path), *crumbs].map do |crumb|
      crumb.html_safe? ? crumb : h(crumb)
    end.join(' &raquo; ').html_safe
  end
end
          

Observations

  • Rails' content_tag returns an HTML-safe string
  • link_to returns an HTML-safe string
  • Individual "crumbs" may safe or unsafe; note how even in Rails 3 there are still occasions where it's necessary to use h
  • Joining safe strings makes them unsafe
  • HTML-entities will get escaped unless they're in safe strings
  • Helpers we write should return HTML-safe strings

Thanks