# frozen_string_literal: true module RuboCop module Cop module Rails # This cop checks for the use of output safety calls like `html_safe`, # `raw`, and `safe_concat`. These methods do not escape content. They # simply return a SafeBuffer containing the content as is. Instead, # use `safe_join` to join content and escape it and concat to # concatenate content and escape it, ensuring its safety. # # @example # user_content = "hi" # # # bad # "

#{user_content}

".html_safe # # => ActiveSupport::SafeBuffer "

hi

" # # # good # content_tag(:p, user_content) # # => ActiveSupport::SafeBuffer "

<b>hi</b>

" # # # bad # out = "" # out << "
  • #{user_content}
  • " # out << "
  • #{user_content}
  • " # out.html_safe # # => ActiveSupport::SafeBuffer "
  • hi
  • hi
  • " # # # good # out = [] # out << content_tag(:li, user_content) # out << content_tag(:li, user_content) # safe_join(out) # # => ActiveSupport::SafeBuffer # # "
  • <b>hi</b>
  • <b>hi</b>
  • " # # # bad # out = "

    trusted content

    ".html_safe # out.safe_concat(user_content) # # => ActiveSupport::SafeBuffer "

    trusted_content

    hi" # # # good # out = "

    trusted content

    ".html_safe # out.concat(user_content) # # => ActiveSupport::SafeBuffer # # "

    trusted_content

    <b>hi</b>" # # # safe, though maybe not good style # out = "trusted content" # result = out.concat(user_content) # # => String "trusted contenthi" # # because when rendered in ERB the String will be escaped: # # <%= result %> # # => trusted content<b>hi</b> # # # bad # (user_content + " " + content_tag(:span, user_content)).html_safe # # => ActiveSupport::SafeBuffer "hi hi" # # # good # safe_join([user_content, " ", content_tag(:span, user_content)]) # # => ActiveSupport::SafeBuffer # # "<b>hi</b> <b>hi</b>" class OutputSafety < Cop MSG = 'Tagging a string as html safe may be a security risk.'.freeze def on_send(node) return if non_interpolated_string?(node) return unless looks_like_rails_html_safe?(node) || looks_like_rails_raw?(node) || looks_like_rails_safe_concat?(node) add_offense(node, location: :selector) end alias on_csend on_send private def non_interpolated_string?(node) node.receiver && node.receiver.str_type? && !node.receiver.dstr_type? end def looks_like_rails_html_safe?(node) node.receiver && node.method?(:html_safe) && !node.arguments? end def looks_like_rails_raw?(node) node.command?(:raw) && node.arguments.one? end def looks_like_rails_safe_concat?(node) node.method?(:safe_concat) && node.arguments.one? end end end end end