VYPR
Low severity2.3NVD Advisory· Published Mar 23, 2026· Updated Apr 16, 2026

CVE-2026-33168

CVE-2026-33168

Description

Action View provides conventions and helpers for building web pages with the Rails framework. Prior to versions 8.1.2.1, 8.0.4.1, and 7.2.3.1, when a blank string is used as an HTML attribute name in Action View tag helpers, the attribute escaping is bypassed, producing malformed HTML. A carefully crafted attribute value could then be misinterpreted by the browser as a separate attribute name, possibly leading to XSS. Applications that allow users to specify custom HTML attributes are affected. Versions 8.1.2.1, 8.0.4.1, and 7.2.3.1 contain a patch.

Affected products

1

Patches

3
63f5ad83edaa

Skip blank attribute names in Action View tag helpers

https://github.com/rails/railsMike DalessioMar 16, 2026via ghsa
3 files changed · +39 2
  • actionview/CHANGELOG.md+5 0 modified
    @@ -1,3 +1,8 @@
    +*   Skip blank attribute names in tag helpers to avoid generating invalid HTML.
    +
    +    *Mike Dalessio*
    +
    +
     ## Rails 8.1.2 (January 08, 2026) ##
     
     *   Fix `file_field` to join mime types with a comma when provided as Array
    
  • actionview/lib/action_view/helpers/tag_helper.rb+5 2 modified
    @@ -237,16 +237,19 @@ def tag_options(options, escape = true) # :nodoc:
               output = +""
               sep    = " "
               options.each_pair do |key, value|
    +            next if key.blank?
    +
                 type = TAG_TYPES[key]
                 if type == :data && value.is_a?(Hash)
                   value.each_pair do |k, v|
    -                next if v.nil?
    +                next if k.blank? || v.nil?
    +
                     output << sep
                     output << prefix_tag_option(key, k, v, escape)
                   end
                 elsif type == :aria && value.is_a?(Hash)
                   value.each_pair do |k, v|
    -                next if v.nil?
    +                next if k.blank? || v.nil?
     
                     case v
                     when Array, Hash
    
  • actionview/test/template/tag_helper_test.rb+29 0 modified
    @@ -107,6 +107,27 @@ def test_tag_options_accepts_blank_option
         assert_equal "<p included=\"\" />", tag("p", included: "")
       end
     
    +  def test_tag_options_rejects_blank_key
    +    assert_equal "<p />", tag("p", "" => "value")
    +    assert_equal "<p />", tag("p", nil => "value")
    +    assert_equal '<p class="a" />', tag("p", "" => "value", "class" => "a")
    +    assert_equal '<p class="a" />', tag("p", nil => "value", "class" => "a")
    +  end
    +
    +  def test_tag_options_rejects_blank_data_key
    +    assert_equal "<p />", tag("p", data: { "" => "value" })
    +    assert_equal "<p />", tag("p", data: { nil => "value" })
    +    assert_equal '<p data-x="y" />', tag("p", data: { "" => "value", "x" => "y" })
    +    assert_equal '<p data-x="y" />', tag("p", data: { nil => "value", "x" => "y" })
    +  end
    +
    +  def test_tag_options_rejects_blank_aria_key
    +    assert_equal "<p />", tag("p", aria: { "" => "value" })
    +    assert_equal "<p />", tag("p", aria: { nil => "value" })
    +    assert_equal '<p aria-x="y" />', tag("p", aria: { "" => "value", "x" => "y" })
    +    assert_equal '<p aria-x="y" />', tag("p", aria: { nil => "value", "x" => "y" })
    +  end
    +
       def test_tag_builder_options_accepts_blank_option
         assert_equal "<p included=\"\"></p>", tag.p(included: "")
       end
    @@ -205,6 +226,14 @@ def test_tag_with_dangerous_unknown_attribute_name
                      tag("the-name", { COMMON_DANGEROUS_CHARS => "the value" }, false, false)
       end
     
    +  def test_tag_with_blank_attribute_name_generates_valid_markup
    +    # https://hackerone.com/reports/3078929
    +    html = tag("img", "src" => "/nonexistent.png", "" => "/onerror=alert(1)")
    +    fragment = Nokogiri::HTML5::DocumentFragment.parse(html)
    +    attrs = fragment.at_css("img").attribute_nodes.map(&:name)
    +    assert_equal [ "src" ], attrs
    +  end
    +
       def test_tag_builder_with_dangerous_unknown_attribute_name
         escaped_dangerous_chars = "_" * COMMON_DANGEROUS_CHARS.size
         assert_equal "<the-name #{escaped_dangerous_chars}=\"the value\"></the-name>",
    
0b6f8002b52b

Skip blank attribute names in Action View tag helpers

https://github.com/rails/railsMike DalessioMar 16, 2026via ghsa
3 files changed · +39 2
  • actionview/CHANGELOG.md+5 0 modified
    @@ -1,3 +1,8 @@
    +*   Skip blank attribute names in tag helpers to avoid generating invalid HTML.
    +
    +    *Mike Dalessio*
    +
    +
     ## Rails 7.2.3 (October 28, 2025) ##
     
     *   Fix `javascript_include_tag` `type` option to accept either strings and symbols.
    
  • actionview/lib/action_view/helpers/tag_helper.rb+5 2 modified
    @@ -263,16 +263,19 @@ def tag_options(options, escape = true)
               output = +""
               sep    = " "
               options.each_pair do |key, value|
    +            next if key.blank?
    +
                 type = TAG_TYPES[key]
                 if type == :data && value.is_a?(Hash)
                   value.each_pair do |k, v|
    -                next if v.nil?
    +                next if k.blank? || v.nil?
    +
                     output << sep
                     output << prefix_tag_option(key, k, v, escape)
                   end
                 elsif type == :aria && value.is_a?(Hash)
                   value.each_pair do |k, v|
    -                next if v.nil?
    +                next if k.blank? || v.nil?
     
                     case v
                     when Array, Hash
    
  • actionview/test/template/tag_helper_test.rb+29 0 modified
    @@ -107,6 +107,27 @@ def test_tag_options_accepts_blank_option
         assert_equal "<p included=\"\" />", tag("p", included: "")
       end
     
    +  def test_tag_options_rejects_blank_key
    +    assert_equal "<p />", tag("p", "" => "value")
    +    assert_equal "<p />", tag("p", nil => "value")
    +    assert_equal '<p class="a" />', tag("p", "" => "value", "class" => "a")
    +    assert_equal '<p class="a" />', tag("p", nil => "value", "class" => "a")
    +  end
    +
    +  def test_tag_options_rejects_blank_data_key
    +    assert_equal "<p />", tag("p", data: { "" => "value" })
    +    assert_equal "<p />", tag("p", data: { nil => "value" })
    +    assert_equal '<p data-x="y" />', tag("p", data: { "" => "value", "x" => "y" })
    +    assert_equal '<p data-x="y" />', tag("p", data: { nil => "value", "x" => "y" })
    +  end
    +
    +  def test_tag_options_rejects_blank_aria_key
    +    assert_equal "<p />", tag("p", aria: { "" => "value" })
    +    assert_equal "<p />", tag("p", aria: { nil => "value" })
    +    assert_equal '<p aria-x="y" />', tag("p", aria: { "" => "value", "x" => "y" })
    +    assert_equal '<p aria-x="y" />', tag("p", aria: { nil => "value", "x" => "y" })
    +  end
    +
       def test_tag_builder_options_accepts_blank_option
         assert_equal "<p included=\"\"></p>", tag.p(included: "")
       end
    @@ -205,6 +226,14 @@ def test_tag_with_dangerous_unknown_attribute_name
                      tag("the-name", { COMMON_DANGEROUS_CHARS => "the value" }, false, false)
       end
     
    +  def test_tag_with_blank_attribute_name_generates_valid_markup
    +    # https://hackerone.com/reports/3078929
    +    html = tag("img", "src" => "/nonexistent.png", "" => "/onerror=alert(1)")
    +    fragment = Nokogiri::HTML5::DocumentFragment.parse(html)
    +    attrs = fragment.at_css("img").attribute_nodes.map(&:name)
    +    assert_equal [ "src" ], attrs
    +  end
    +
       def test_tag_builder_with_dangerous_unknown_attribute_name
         escaped_dangerous_chars = "_" * COMMON_DANGEROUS_CHARS.size
         assert_equal "<the-name #{escaped_dangerous_chars}=\"the value\"></the-name>",
    
c79a07df1e88

Skip blank attribute names in Action View tag helpers

https://github.com/rails/railsMike DalessioMar 16, 2026via ghsa
3 files changed · +39 2
  • actionview/CHANGELOG.md+5 0 modified
    @@ -1,3 +1,8 @@
    +*   Skip blank attribute names in tag helpers to avoid generating invalid HTML.
    +
    +    *Mike Dalessio*
    +
    +
     ## Rails 8.0.4 (October 28, 2025) ##
     
     *   Restore `add_default_name_and_id` method.
    
  • actionview/lib/action_view/helpers/tag_helper.rb+5 2 modified
    @@ -250,16 +250,19 @@ def tag_options(options, escape = true)
               output = +""
               sep    = " "
               options.each_pair do |key, value|
    +            next if key.blank?
    +
                 type = TAG_TYPES[key]
                 if type == :data && value.is_a?(Hash)
                   value.each_pair do |k, v|
    -                next if v.nil?
    +                next if k.blank? || v.nil?
    +
                     output << sep
                     output << prefix_tag_option(key, k, v, escape)
                   end
                 elsif type == :aria && value.is_a?(Hash)
                   value.each_pair do |k, v|
    -                next if v.nil?
    +                next if k.blank? || v.nil?
     
                     case v
                     when Array, Hash
    
  • actionview/test/template/tag_helper_test.rb+29 0 modified
    @@ -107,6 +107,27 @@ def test_tag_options_accepts_blank_option
         assert_equal "<p included=\"\" />", tag("p", included: "")
       end
     
    +  def test_tag_options_rejects_blank_key
    +    assert_equal "<p />", tag("p", "" => "value")
    +    assert_equal "<p />", tag("p", nil => "value")
    +    assert_equal '<p class="a" />', tag("p", "" => "value", "class" => "a")
    +    assert_equal '<p class="a" />', tag("p", nil => "value", "class" => "a")
    +  end
    +
    +  def test_tag_options_rejects_blank_data_key
    +    assert_equal "<p />", tag("p", data: { "" => "value" })
    +    assert_equal "<p />", tag("p", data: { nil => "value" })
    +    assert_equal '<p data-x="y" />', tag("p", data: { "" => "value", "x" => "y" })
    +    assert_equal '<p data-x="y" />', tag("p", data: { nil => "value", "x" => "y" })
    +  end
    +
    +  def test_tag_options_rejects_blank_aria_key
    +    assert_equal "<p />", tag("p", aria: { "" => "value" })
    +    assert_equal "<p />", tag("p", aria: { nil => "value" })
    +    assert_equal '<p aria-x="y" />', tag("p", aria: { "" => "value", "x" => "y" })
    +    assert_equal '<p aria-x="y" />', tag("p", aria: { nil => "value", "x" => "y" })
    +  end
    +
       def test_tag_builder_options_accepts_blank_option
         assert_equal "<p included=\"\"></p>", tag.p(included: "")
       end
    @@ -205,6 +226,14 @@ def test_tag_with_dangerous_unknown_attribute_name
                      tag("the-name", { COMMON_DANGEROUS_CHARS => "the value" }, false, false)
       end
     
    +  def test_tag_with_blank_attribute_name_generates_valid_markup
    +    # https://hackerone.com/reports/3078929
    +    html = tag("img", "src" => "/nonexistent.png", "" => "/onerror=alert(1)")
    +    fragment = Nokogiri::HTML5::DocumentFragment.parse(html)
    +    attrs = fragment.at_css("img").attribute_nodes.map(&:name)
    +    assert_equal [ "src" ], attrs
    +  end
    +
       def test_tag_builder_with_dangerous_unknown_attribute_name
         escaped_dangerous_chars = "_" * COMMON_DANGEROUS_CHARS.size
         assert_equal "<the-name #{escaped_dangerous_chars}=\"the value\"></the-name>",
    

Vulnerability mechanics

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

References

10

News mentions

0

No linked articles in our index yet.