VYPR
High severityNVD Advisory· Published Mar 2, 2022· Updated Apr 23, 2025

Cross-site Scripting in view_component

CVE-2022-24722

Description

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.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Cross-site scripting (XSS) vulnerability in ViewComponent's `translate` method allows injection of malicious HTML via user-supplied interpolation arguments.

Vulnerability

ViewComponent versions prior to 2.31.2 and 2.49.1 contain a cross-site scripting (XSS) vulnerability in the translate helper method. When user-supplied data is passed as an interpolation argument to translate, it is not properly sanitized before being rendered in the view. This affects any application using translations with the view_component gem. [1]

Exploitation

An attacker can inject arbitrary HTML or JavaScript by providing malicious input that is used as an interpolation argument in a translate call. No special network position is required beyond the ability to submit data that reaches the vulnerable component. The attacker does not need authentication if the component is publicly accessible. The user interaction is limited to the attacker submitting the payload; the victim views the page containing the translated output. [1]

Impact

Successful exploitation allows an attacker to execute arbitrary JavaScript in the context of the victim's browser session, leading to potential data theft, session hijacking, or defacement. The impact is limited to the client side, but can affect any user who views the compromised component. [1]

Mitigation

The vulnerability is fully mitigated in versions 2.31.2 and 2.49.1, released on 2022-03-02. Users should upgrade to these versions or later. As a workaround, avoid passing user input to the translate function, or sanitize inputs before passing them. [1][2]

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
view_componentRubyGems
>= 2.31.0, < 2.31.22.31.2
view_componentRubyGems
>= 2.32.0, < 2.49.12.49.1

Affected products

2

Patches

1
3f82a6e62578

Fix XSS vulnerability when using HTML-safe translations and interpolation arguments (#1295)

https://github.com/github/view_componentCameron DutroMar 2, 2022via ghsa
5 files changed · +116 69
  • docs/CHANGELOG.md+12 0 modified
    @@ -53,6 +53,12 @@ title: Changelog
     
         *Bernie Chiu*
     
    +## 2.49.1
    +
    +* Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments.
    +
    +    *Cameron Dutro*
    +
     ## 2.49.0
     
     * Fix path handling for evaluated view components that broke in Ruby 3.1.
    @@ -659,6 +665,12 @@ title: Changelog
     
         *Joel Hawksley*
     
    +## 2.31.2
    +
    +* Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments.
    +
    +    *Cameron Dutro*
    +
     ## 2.31.1
     
     * Fix `DEPRECATION WARNING: before_render_check` when compiling `ViewComponent::Base`
    
  • Gemfile.lock+66 69 modified
    @@ -1,74 +1,74 @@
     PATH
       remote: .
       specs:
    -    view_component (2.48.0)
    +    view_component (2.49.0)
           activesupport (>= 5.0.0, < 8.0)
           method_source (~> 1.0)
     
     GEM
       remote: https://rubygems.org/
       specs:
    -    actioncable (7.0.1)
    -      actionpack (= 7.0.1)
    -      activesupport (= 7.0.1)
    +    actioncable (7.0.2)
    +      actionpack (= 7.0.2)
    +      activesupport (= 7.0.2)
           nio4r (~> 2.0)
           websocket-driver (>= 0.6.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)
    +    actionmailbox (7.0.2)
    +      actionpack (= 7.0.2)
    +      activejob (= 7.0.2)
    +      activerecord (= 7.0.2)
    +      activestorage (= 7.0.2)
    +      activesupport (= 7.0.2)
           mail (>= 2.7.1)
           net-imap
           net-pop
           net-smtp
    -    actionmailer (7.0.1)
    -      actionpack (= 7.0.1)
    -      actionview (= 7.0.1)
    -      activejob (= 7.0.1)
    -      activesupport (= 7.0.1)
    +    actionmailer (7.0.2)
    +      actionpack (= 7.0.2)
    +      actionview (= 7.0.2)
    +      activejob (= 7.0.2)
    +      activesupport (= 7.0.2)
           mail (~> 2.5, >= 2.5.4)
           net-imap
           net-pop
           net-smtp
           rails-dom-testing (~> 2.0)
    -    actionpack (7.0.1)
    -      actionview (= 7.0.1)
    -      activesupport (= 7.0.1)
    +    actionpack (7.0.2)
    +      actionview (= 7.0.2)
    +      activesupport (= 7.0.2)
           rack (~> 2.0, >= 2.2.0)
           rack-test (>= 0.6.3)
           rails-dom-testing (~> 2.0)
           rails-html-sanitizer (~> 1.0, >= 1.2.0)
    -    actiontext (7.0.1)
    -      actionpack (= 7.0.1)
    -      activerecord (= 7.0.1)
    -      activestorage (= 7.0.1)
    -      activesupport (= 7.0.1)
    +    actiontext (7.0.2)
    +      actionpack (= 7.0.2)
    +      activerecord (= 7.0.2)
    +      activestorage (= 7.0.2)
    +      activesupport (= 7.0.2)
           globalid (>= 0.6.0)
           nokogiri (>= 1.8.5)
    -    actionview (7.0.1)
    -      activesupport (= 7.0.1)
    +    actionview (7.0.2)
    +      activesupport (= 7.0.2)
           builder (~> 3.1)
           erubi (~> 1.4)
           rails-dom-testing (~> 2.0)
           rails-html-sanitizer (~> 1.1, >= 1.2.0)
    -    activejob (7.0.1)
    -      activesupport (= 7.0.1)
    +    activejob (7.0.2)
    +      activesupport (= 7.0.2)
           globalid (>= 0.3.6)
    -    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)
    +    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)
           marcel (~> 1.0)
           mini_mime (>= 1.1.0)
    -    activesupport (7.0.1)
    +    activesupport (7.0.2)
           concurrent-ruby (~> 1.0, >= 1.0.2)
           i18n (>= 1.6, < 2)
           minitest (>= 5.1)
    @@ -120,13 +120,13 @@ GEM
           temple (>= 0.8.0)
           tilt
         html_tokenizer (0.0.7)
    -    i18n (1.8.11)
    +    i18n (1.10.0)
           concurrent-ruby (~> 1.0)
         io-wait (0.2.1)
         jbuilder (2.11.5)
           actionview (>= 5.0.0)
           activesupport (>= 5.0.0)
    -    loofah (2.13.0)
    +    loofah (2.14.0)
           crass (~> 1.0.2)
           nokogiri (>= 1.5.9)
         mail (2.7.1)
    @@ -135,7 +135,7 @@ GEM
         matrix (0.4.2)
         method_source (1.0.0)
         mini_mime (1.1.2)
    -    mini_portile2 (2.7.1)
    +    mini_portile2 (2.8.0)
         minitest (5.6.0)
         net-imap (0.2.3)
           digest
    @@ -153,11 +153,11 @@ GEM
           net-protocol
           timeout
         nio4r (2.5.8)
    -    nokogiri (1.13.1)
    -      mini_portile2 (~> 2.7.0)
    +    nokogiri (1.13.3)
    +      mini_portile2 (~> 2.8.0)
           racc (~> 1.4)
         parallel (1.21.0)
    -    parser (3.1.0.0)
    +    parser (3.1.1.0)
           ast (~> 2.4.1)
         pry (0.14.1)
           coderay (~> 1.1)
    @@ -167,35 +167,35 @@ GEM
         rack (2.2.3)
         rack-test (1.1.0)
           rack (>= 1.0, < 3)
    -    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)
    +    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)
           bundler (>= 1.15.0)
    -      railties (= 7.0.1)
    +      railties (= 7.0.2)
         rails-dom-testing (2.0.3)
           activesupport (>= 4.2.0)
           nokogiri (>= 1.6)
         rails-html-sanitizer (1.4.2)
           loofah (~> 2.3)
    -    railties (7.0.1)
    -      actionpack (= 7.0.1)
    -      activesupport (= 7.0.1)
    +    railties (7.0.2)
    +      actionpack (= 7.0.2)
    +      activesupport (= 7.0.2)
           method_source
           rake (>= 12.2)
           thor (~> 1.0)
           zeitwerk (~> 2.5)
         rainbow (3.1.1)
         rake (13.0.6)
    -    regexp_parser (2.2.0)
    +    regexp_parser (2.2.1)
         rexml (3.2.5)
         rubocop (1.13.0)
           parallel (~> 1.10)
    @@ -206,8 +206,8 @@ GEM
           rubocop-ast (>= 1.2.0, < 2.0)
           ruby-progressbar (~> 1.7)
           unicode-display_width (>= 1.4.0, < 3.0)
    -    rubocop-ast (1.15.1)
    -      parser (>= 3.0.1.1)
    +    rubocop-ast (1.16.0)
    +      parser (>= 3.1.1.0)
         rubocop-github (0.16.2)
           rubocop (<= 1.13.0)
           rubocop-performance (<= 1.11.0)
    @@ -232,7 +232,7 @@ GEM
           temple (>= 0.7.6, < 0.9)
           tilt (>= 2.0.6, < 2.1)
         smart_properties (1.17.0)
    -    sprockets (4.0.2)
    +    sprockets (4.0.3)
           concurrent-ruby (~> 1.0)
           rack (> 1, < 3)
         sprockets-rails (3.2.2)
    @@ -259,7 +259,7 @@ GEM
           webrick (~> 1.7.0)
         yard-activesupport-concern (0.0.1)
           yard (>= 0.8)
    -    zeitwerk (2.5.3)
    +    zeitwerk (2.5.4)
     
     PLATFORMS
       ruby
    @@ -274,11 +274,8 @@ DEPENDENCIES
       haml (~> 5)
       jbuilder (~> 2)
       minitest (= 5.6.0)
    -  net-imap
    -  net-pop
    -  net-smtp
       pry (~> 0.13)
    -  rails (~> 7.0.0)
    +  rails (= 7.0.2)
       rake (~> 13.0)
       rubocop-github (~> 0.16.1)
       simplecov (~> 0.18.0)
    
  • lib/view_component/translatable.rb+19 0 modified
    @@ -1,5 +1,6 @@
     # frozen_string_literal: true
     
    +require "erb"
     require "set"
     require "i18n"
     require "action_view/helpers/translation_helper"
    @@ -70,6 +71,10 @@ def translate(key = nil, **options)
           key = key&.to_s unless key.is_a?(String)
           key = "#{i18n_scope}#{key}" if key.start_with?(".")
     
    +      if HTML_SAFE_TRANSLATION_KEY.match?(key)
    +        html_escape_translation_options!(options)
    +      end
    +
           if key.start_with?(i18n_scope + ".")
             translated =
               catch(:exception) do
    @@ -107,5 +112,19 @@ def html_safe_translation(translation)
             translation.html_safe # rubocop:disable Rails/OutputSafety
           end
         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
       end
     end
    
  • test/sandbox/app/components/translatable_component.yml+2 0 modified
    @@ -3,6 +3,8 @@ en:
     
       hello_html: "Hello from <strong>sidecar translations</strong>!"
     
    +  interpolated_html: "There are %{horse_count} horses in the <strong>barn</strong>!"
    +
       html: "hello <em>world</em>!"
     
       from:
    
  • test/view_component/translatable_test.rb+17 0 modified
    @@ -58,6 +58,23 @@ def test_translate_marks_translations_with_a_html_suffix_as_safe_html
         assert_predicate translate(".hello_html"), :html_safe?
       end
     
    +  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
    +
       def test_translate_uses_the_helper_when_no_sidecar_file_is_provided
         # The cache needs to be kept clean for TranslatableComponent, otherwise it will rely on the
         # already created i18n_backend.
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.