CVE-2022-24722 (Ruby, published 2022-03-02T23:15Z)
Description: value: VIewComponent is a framework for building view components in Ruby on Rails. Versions prior to 2.31.2 and 2.49.1 contain a cross-site scripting vulnerability that has the potential to impact anyone using translations with the view_component gem. Data received via user input and passed as an interpolation argument to the `translate` method is not properly sanitized before display. Versions 2.31.2 and 2.49.1 have been released and fully mitigate the vulnerability. As a workaround, avoid passing user input to the `translate` function, or sanitize the inputs before passing them.
Severity: MEDIUM (CVSS v3 base score: 6.1)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/github/view_component
Files changed:
Gemfile.lock
docs/CHANGELOG.md
lib/view_component/translatable.rb
test/sandbox/app/components/translatable_component.yml
test/view_component/translatable_test.rb
Commit message: Fix XSS vulnerability when using HTML-safe translations and interpolation arguments (#1295)



Vulnerable code:
    view_component (2.48.0)
    actioncable (7.0.1)
      actionpack (= 7.0.1)
      activesupport (= 7.0.1)
    actionmailbox (7.0.1)
      actionpack (= 7.0.1)
      activejob (= 7.0.1)
      activerecord (= 7.0.1)
      activestorage (= 7.0.1)
      activesupport (= 7.0.1)
    actionmailer (7.0.1)
      actionpack (= 7.0.1)
      actionview (= 7.0.1)
      activejob (= 7.0.1)
      activesupport (= 7.0.1)
    actionpack (7.0.1)
      actionview (= 7.0.1)
      activesupport (= 7.0.1)
    actiontext (7.0.1)
      actionpack (= 7.0.1)
      activerecord (= 7.0.1)
      activestorage (= 7.0.1)
      activesupport (= 7.0.1)
    actionview (7.0.1)
      activesupport (= 7.0.1)
    activejob (7.0.1)
      activesupport (= 7.0.1)
    activemodel (7.0.1)
      activesupport (= 7.0.1)
    activerecord (7.0.1)
      activemodel (= 7.0.1)
      activesupport (= 7.0.1)
    activestorage (7.0.1)
      actionpack (= 7.0.1)
      activejob (= 7.0.1)
      activerecord (= 7.0.1)
      activesupport (= 7.0.1)
    activesupport (7.0.1)
    i18n (1.8.11)
    loofah (2.13.0)
    mini_portile2 (2.7.1)
    nokogiri (1.13.1)
      mini_portile2 (~> 2.7.0)
    parser (3.1.0.0)
    rails (7.0.1)
      actioncable (= 7.0.1)
      actionmailbox (= 7.0.1)
      actionmailer (= 7.0.1)
      actionpack (= 7.0.1)
      actiontext (= 7.0.1)
      actionview (= 7.0.1)
      activejob (= 7.0.1)
      activemodel (= 7.0.1)
      activerecord (= 7.0.1)
      activestorage (= 7.0.1)
      activesupport (= 7.0.1)
      railties (= 7.0.1)
    railties (7.0.1)
      actionpack (= 7.0.1)
      activesupport (= 7.0.1)
    regexp_parser (2.2.0)
    rubocop-ast (1.15.1)
      parser (>= 3.0.1.1)
    sprockets (4.0.2)
    zeitwerk (2.5.3)
  net-imap
  net-pop
  net-smtp
  rails (~> 7.0.0)

Fixed code:
    view_component (2.49.0)
    actioncable (7.0.2)
      actionpack (= 7.0.2)
      activesupport (= 7.0.2)
    actionmailbox (7.0.2)
      actionpack (= 7.0.2)
      activejob (= 7.0.2)
      activerecord (= 7.0.2)
      activestorage (= 7.0.2)
      activesupport (= 7.0.2)
    actionmailer (7.0.2)
      actionpack (= 7.0.2)
      actionview (= 7.0.2)
      activejob (= 7.0.2)
      activesupport (= 7.0.2)
    actionpack (7.0.2)
      actionview (= 7.0.2)
      activesupport (= 7.0.2)
    actiontext (7.0.2)
      actionpack (= 7.0.2)
      activerecord (= 7.0.2)
      activestorage (= 7.0.2)
      activesupport (= 7.0.2)
    actionview (7.0.2)
      activesupport (= 7.0.2)
    activejob (7.0.2)
      activesupport (= 7.0.2)
    activemodel (7.0.2)
      activesupport (= 7.0.2)
    activerecord (7.0.2)
      activemodel (= 7.0.2)
      activesupport (= 7.0.2)
    activestorage (7.0.2)
      actionpack (= 7.0.2)
      activejob (= 7.0.2)
      activerecord (= 7.0.2)
      activesupport (= 7.0.2)
    activesupport (7.0.2)
    i18n (1.10.0)
    loofah (2.14.0)
    mini_portile2 (2.8.0)
    nokogiri (1.13.3)
      mini_portile2 (~> 2.8.0)
    parser (3.1.1.0)
    rails (7.0.2)
      actioncable (= 7.0.2)
      actionmailbox (= 7.0.2)
      actionmailer (= 7.0.2)
      actionpack (= 7.0.2)
      actiontext (= 7.0.2)
      actionview (= 7.0.2)
      activejob (= 7.0.2)
      activemodel (= 7.0.2)
      activerecord (= 7.0.2)
      activestorage (= 7.0.2)
      activesupport (= 7.0.2)
      railties (= 7.0.2)
    railties (7.0.2)
      actionpack (= 7.0.2)
      activesupport (= 7.0.2)
    regexp_parser (2.2.1)
    rubocop-ast (1.16.0)
      parser (>= 3.1.1.0)
    sprockets (4.0.3)
    zeitwerk (2.5.4)
  rails (= 7.0.2)
## 2.49.1

* Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments.

    *Cameron Dutro*

## 2.31.2

* Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments.

    *Cameron Dutro*

require "erb"
      if HTML_SAFE_TRANSLATION_KEY.match?(key)
        html_escape_translation_options!(options)
      end


    private

    def html_escape_translation_options!(options)
      options.each do |name, value|
        unless i18n_option?(name) || (name == :count && value.is_a?(Numeric))
          options[name] = ERB::Util.html_escape(value.to_s)
        end
      end
    end

    def i18n_option?(name)
      (@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name)
    end
  interpolated_html: "There are %{horse_count} horses in the <strong>barn</strong>!"

  def test_translate_with_html_suffix_escapes_interpolated_arguments
    translation = translate(".interpolated_html", horse_count: "<script type='text/javascript'>alert('foo');</script>")
    assert_equal(
      "There are &lt;script type=&#39;text/javascript&#39;&gt;alert(&#39;foo&#39;);&lt;/script&gt; horses in the "\
        "<strong>barn</strong>!",
      translation
    )
  end

  def test_translate_with_html_suffix_does_not_double_escape
    translation = translate(".interpolated_html", horse_count: "> 4")
    assert_equal(
      "There are &gt; 4 horses in the <strong>barn</strong>!",
      translation
    )
  end