CVE-2011-1497
Description
A cross-site scripting vulnerability flaw was found in the auto_link function in Rails before version 3.0.6.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Cross-site scripting vulnerability in the auto_link function in Ruby on Rails before 3.0.6 allowed attackers to inject arbitrary JavaScript via unsanitized user input.
Vulnerability
A cross-site scripting (XSS) vulnerability exists in the auto_link helper function provided by Action Pack in Ruby on Rails versions prior to 3.0.6. The function did not sanitize user-supplied input and incorrectly marked the output as HTML-safe, allowing injection of arbitrary HTML and JavaScript [1] [4].
Exploitation
An attacker can exploit this vulnerability by providing crafted input containing malicious HTML or JavaScript (e.g., `) that is later processed by auto_link. The function would generate a link but preserve the injected script, which then executes in the context of any user viewing the page. No authentication or special network position is required; the attacker only needs to submit the malicious input to a field or parameter that is later displayed using auto_link` [4].
Impact
Successful exploitation allows an attacker to execute arbitrary JavaScript in the victim's browser, leading to session hijacking, cookie theft, defacement, or phishing attacks. The attack runs with the same permissions as the victim user and can affect any page that renders user-controlled data through the vulnerable auto_link call [4].
Mitigation
The vulnerability is fixed in Ruby on Rails version 3.0.6, released on April 5, 2011 [4]. Users should upgrade to at least this version. As a workaround before upgrading, developers must wrap auto_link() calls with sanitize() to ensure output is properly escaped. For example: <%= sanitize(auto_link(some_user_input)) %>. If the input is known to be safe, raw() can be used, but caution is advised [4].
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.
| Package | Affected versions | Patched versions |
|---|---|---|
actionpackRubyGems | >= 3.0.0.rc, < 3.0.6 | 3.0.6 |
Affected products
2- Rails/Railsdescription
Patches
261ee3449674cdo not return html safe strings from auto_link
2 files changed · +13 −7
actionpack/lib/action_view/helpers/text_helper.rb+2 −2 modified@@ -299,7 +299,7 @@ def simple_format(text, html_options={}, options={}) # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>. # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>." def auto_link(text, *args, &block)#link = :all, html = {}, &block) - return ''.html_safe if text.blank? + return '' if text.blank? options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter unless args.empty? @@ -503,7 +503,7 @@ def auto_link_urls(text, html_options = {}, options = {}) end content_tag(:a, link_text, link_attributes.merge('href' => href), !!options[:sanitize]) + punctuation.reverse.join('') end - end.html_safe + end end # Turns all email addresses into clickable links. If a block is given,
actionpack/test/template/text_helper_test.rb+11 −5 modified@@ -315,14 +315,20 @@ def generate_result(link_text, href = nil, escape = false) end end - def test_auto_link_should_be_html_safe + def test_auto_link_should_not_be_html_safe email_raw = 'santiago@wyeworks.com' link_raw = 'http://www.rubyonrails.org' - assert auto_link(nil).html_safe? - assert auto_link('').html_safe? - assert auto_link("#{link_raw} #{link_raw} #{link_raw}").html_safe? - assert auto_link("hello #{email_raw}").html_safe? + assert !auto_link(nil).html_safe?, 'should not be html safe' + assert !auto_link('').html_safe?, 'should not be html safe' + assert !auto_link("#{link_raw} #{link_raw} #{link_raw}").html_safe?, 'should not be html safe' + assert !auto_link("hello #{email_raw}").html_safe?, 'should not be html safe' + end + + def test_auto_link_email_address + email_raw = 'aaron@tenderlovemaking.com' + email_result = %{<a href="mailto:#{email_raw}">#{email_raw}</a>} + assert !auto_link_email_addresses(email_result).html_safe?, 'should not be html safe' end def test_auto_link
ab764ecbfea3Makes text_helper methods sanitize the input if the input is not safe or :safe => true option is not provided
2 files changed · +118 −22
actionpack/lib/action_view/helpers/text_helper.rb+22 −16 modified@@ -74,6 +74,7 @@ def truncate(text, *args) options.reverse_merge!(:length => 30) + text = sanitize(text) unless text.html_safe? || options[:safe] text.truncate(options.delete(:length), options) if text end @@ -105,6 +106,7 @@ def highlight(text, phrases, *args) end options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>') + text = sanitize(text) unless text.html_safe? || options[:safe] if text.blank? || phrases.blank? text else @@ -244,13 +246,14 @@ def word_wrap(text, *args) # def textilize(text, *options) options ||= [:hard_breaks] + text = sanitize(text) unless text.html_safe? || options.delete(:safe) if text.blank? "" else textilized = RedCloth.new(text, options) textilized.to_html - end + end.html_safe end # Returns the text with all the Textile codes turned into HTML tags, @@ -271,8 +274,8 @@ def textilize(text, *options) # # textilize_without_paragraph("Visit the Rails website "here":http://www.rubyonrails.org/.) # # => "Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>." - def textilize_without_paragraph(text) - textiled = textilize(text) + def textilize_without_paragraph(text, *options) + textiled = textilize(text, options) if textiled[0..2] == "<p>" then textiled = textiled[3..-1] end if textiled[-4..-1] == "</p>" then textiled = textiled[0..-5] end return textiled @@ -295,8 +298,9 @@ def textilize_without_paragraph(text) # # markdown('') # # => '<p><img src="http://rubyonrails.com/images/rails.png" alt="The ROR logo" title="Ruby on Rails" /></p>' - def markdown(text) - text.blank? ? "" : BlueCloth.new(text).to_html + def markdown(text, options = {}) + text = sanitize(text) unless options[:safe] + (text.blank? ? "" : BlueCloth.new(text).to_html).html_safe end # Returns +text+ transformed into HTML using simple formatting rules. @@ -320,14 +324,15 @@ def markdown(text) # # simple_format("Look ma! A class!", :class => 'description') # # => "<p class='description'>Look ma! A class!</p>" - def simple_format(text, html_options={}) + def simple_format(text, html_options={}, options={}) + text = '' if text.nil? start_tag = tag('p', html_options, true) - text = h(text) + text = sanitize(text) unless text.html_safe? || options[:safe] text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br text.insert 0, start_tag - text.safe_concat("</p>") + text.html_safe.safe_concat("</p>") end # Turns all URLs and e-mail addresses into clickable links. The <tt>:link</tt> option @@ -368,7 +373,7 @@ def simple_format(text, html_options={}) # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>. # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>." def auto_link(text, *args, &block)#link = :all, html = {}, &block) - return '' if text.blank? + return ''.html_safe if text.blank? options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter unless args.empty? @@ -378,9 +383,9 @@ def auto_link(text, *args, &block)#link = :all, html = {}, &block) options.reverse_merge!(:link => :all, :html => {}) case options[:link].to_sym - when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], &block), options[:html], &block) + when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], options, &block), options[:html], &block) when :email_addresses then auto_link_email_addresses(text, options[:html], &block) - when :urls then auto_link_urls(text, options[:html], &block) + when :urls then auto_link_urls(text, options[:html], options, &block) end end @@ -544,7 +549,7 @@ def set_cycle(name, cycle_object) # Turns all urls into clickable links. If a block is given, each url # is yielded and the result is used as the link text. - def auto_link_urls(text, html_options = {}) + def auto_link_urls(text, html_options = {}, options = {}) link_attributes = html_options.stringify_keys text.gsub(AUTO_LINK_RE) do scheme, href = $1, $& @@ -566,21 +571,22 @@ def auto_link_urls(text, html_options = {}) link_text = block_given?? yield(href) : href href = 'http://' + href unless scheme - content_tag(:a, link_text, link_attributes.merge('href' => href)) + punctuation.reverse.join('') + content_tag(:a, link_text, link_attributes.merge('href' => href), !(options[:safe] || text.html_safe?)) + punctuation.reverse.join('') end - end + end.html_safe end # Turns all email addresses into clickable links. If a block is given, # each email is yielded and the result is used as the link text. - def auto_link_email_addresses(text, html_options = {}) + def auto_link_email_addresses(text, html_options = {}, options = {}) text.gsub(AUTO_EMAIL_RE) do text = $& if auto_linked?($`, $') - text + text.html_safe else display_text = (block_given?) ? yield(text) : text + display_text = sanitize(display_text) unless options[:safe] mail_to text, display_text, html_options end end
actionpack/test/template/text_helper_test.rb+96 −6 modified@@ -45,19 +45,42 @@ def test_simple_format_should_be_html_safe assert simple_format("<b> test with html tags </b>").html_safe? end - def test_simple_format_should_escape_unsafe_input - assert_equal "<p><b> test with unsafe string </b></p>", simple_format("<b> test with unsafe string </b>") + def test_simple_format_should_sanitize_unsafe_input + assert_equal "<p><b> test with unsafe string </b></p>", simple_format("<b> test with unsafe string </b><script>code!</script>") end - def test_simple_format_should_not_escape_safe_input + def test_simple_format_should_not_sanitize_input_if_safe_option + assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :safe => true) + end + + def test_simple_format_should_not_sanitize_safe_input assert_equal "<p><b> test with safe string </b></p>", simple_format("<b> test with safe string </b>".html_safe) end + def test_truncate_should_be_html_safe + assert truncate("Hello World!", :length => 12).html_safe? + end + def test_truncate assert_equal "Hello World!", truncate("Hello World!", :length => 12) assert_equal "Hello Wor...", truncate("Hello World!!", :length => 12) end + def test_truncate_should_sanitize_unsafe_input + assert_equal "Hello World!", truncate("Hello <script>code!</script>World!", :length => 12) + assert_equal "Hello Wor...", truncate("Hello <script>code!</script>World!!", :length => 12) + end + + def test_truncate_should_not_sanitize_input_if_safe_option + assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!", :length => 12, :safe => true) + assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!", :length => 12, :safe => true) + end + + def test_truncate_should_not_sanitize_safe_input + assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!".html_safe, :length => 12) + assert_equal "Hello <sc...", truncate("Hello <script>code!</script>World!!".html_safe, :length => 12) + end + def test_truncate_should_use_default_length_of_30 str = "This is a string that will go longer then the default truncate length of 30" assert_equal str[0...27] + "...", truncate(str) @@ -93,7 +116,11 @@ def test_truncate_multibyte end end - def test_highlighter + def test_highlight_should_be_html_safe + assert highlight("This is a beautiful morning", "beautiful").html_safe? + end + + def test_highlight assert_equal( "This is a <strong class=\"highlight\">beautiful</strong> morning", highlight("This is a beautiful morning", "beautiful") @@ -117,6 +144,27 @@ def test_highlighter assert_equal ' ', highlight(' ', 'blank text is returned verbatim') end + def test_highlight_should_sanitize_unsafe_input + assert_equal( + "This is a <strong class=\"highlight\">beautiful</strong> morning", + highlight("This is a beautiful morning<script>code!</script>", "beautiful") + ) + end + + def test_highlight_should_not_sanitize_input_if_safe_option + assert_equal( + "This is a <strong class=\"highlight\">beautiful</strong> morning<script>code!</script>", + highlight("This is a beautiful morning<script>code!</script>", "beautiful", :safe => true) + ) + end + + def test_highlight_should_not_sanitize_safe_input + assert_equal( + "This is a <strong class=\"highlight\">beautiful</strong> morning<script>code!</script>", + highlight("This is a beautiful morning<script>code!</script>".html_safe, "beautiful") + ) + end + def test_highlight_with_regexp assert_equal( "This is a <strong class=\"highlight\">beautiful!</strong> morning", @@ -163,7 +211,7 @@ def test_highlight_with_html highlight("<p class=\"beautiful\">This is a beautiful morning, but also a beautiful day</p>", "beautiful") ) assert_equal( - "<p>This is a <strong class=\"highlight\">beautiful</strong> <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a <strong class=\"highlight\">beautiful</strong> day</p>", + "<p>This is a <strong class=\"highlight\">beautiful</strong> <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a <strong class=\"highlight\">beautiful</strong> day</p>", highlight("<p>This is a beautiful <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a beautiful day</p>", "beautiful") ) end @@ -286,7 +334,17 @@ def generate_result(link_text, href = nil) %{<a href="#{CGI::escapeHTML href}">#{CGI::escapeHTML link_text}</a>} end - def test_auto_linking + def test_auto_link_should_be_html_safe + email_raw = 'santiago@wyeworks.com' + link_raw = 'http://www.rubyonrails.org' + + assert auto_link(nil).html_safe? + assert auto_link('').html_safe? + assert auto_link("#{link_raw} #{link_raw} #{link_raw}").html_safe? + assert auto_link("hello #{email_raw}").html_safe? + end + + def test_auto_link email_raw = 'david@loudthinking.com' email_result = %{<a href="mailto:#{email_raw}">#{email_raw}</a>} link_raw = 'http://www.rubyonrails.com' @@ -378,6 +436,21 @@ def test_auto_linking assert_equal %(<p>#{link10_result} Link</p>), auto_link("<p>#{link10_raw} Link</p>") end + def test_auto_link_should_sanitize_unsafe_input + link_raw = %{http://www.rubyonrails.com?id=1&num=2} + assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw) + end + + def test_auto_link_should_sanitize_unsafe_input + link_raw = %{http://www.rubyonrails.com?id=1&num=2} + assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw, :safe => true) + end + + def test_auto_link_should_not_sanitize_safe_input + link_raw = %{http://www.rubyonrails.com?id=1&num=2} + assert_equal %{<a href="http://www.rubyonrails.com?id=1&num=2">http://www.rubyonrails.com?id=1&num=2</a>}, auto_link(link_raw.html_safe) + end + def test_auto_link_other_protocols ftp_raw = 'ftp://example.com/file.txt' assert_equal %(Download #{generate_result(ftp_raw)}), auto_link("Download #{ftp_raw}") @@ -587,7 +660,12 @@ def test_cycle_no_instance_variable_clashes assert_equal(%w{Specialized Fuji Giant}, @cycles) end + # TODO test textilize_without_paragraph and markdown if defined? RedCloth + def test_textilize_should_be_html_safe + assert textilize("*This is Textile!* Rejoice!").html_safe? + end + def test_textilize assert_equal("<p><strong>This is Textile!</strong> Rejoice!</p>", textilize("*This is Textile!* Rejoice!")) end @@ -600,6 +678,18 @@ def test_textilize_with_options assert_equal("<p>This is worded <strong>strongly</strong></p>", textilize("This is worded <strong>strongly</strong>", :filter_html)) end + def test_textilize_should_sanitize_unsafe_input + assert_equal("<p>This is worded <strong>strongly</strong></p>", textilize("This is worded <strong>strongly</strong><script>code!</script>")) + end + + def test_textilize_should_not_sanitize_input_if_safe_option + assert_equal("<p>This is worded <strong>strongly</strong><script>code!</script></p>", textilize("This is worded <strong>strongly</strong><script>code!</script>", :safe)) + end + + def test_textilize_should_not_sanitize_safe_input + assert_equal("<p>This is worded <strong>strongly</strong><script>code!</script></p>", textilize("This is worded <strong>strongly</strong><script>code!</script>".html_safe)) + end + def test_textilize_with_hard_breaks assert_equal("<p>This is one scary world.<br />\n True.</p>", textilize("This is one scary world.\n True.")) end
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-q58j-fmvf-9rq6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2011-1497ghsaADVISORY
- github.com/rails/rails/blob/38df020c95beca7e12f0188cb7e18f3c37789e20/actionpack/CHANGELOGghsax_refsource_MISCWEB
- github.com/rails/rails/commit/61ee3449674c591747db95f9b3472c5c3bd9e84dghsaWEB
- github.com/rails/rails/commit/ab764ecbfea31a3b14323283287e2fc80955ace6ghsaWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/actionpack/CVE-2011-1497.ymlghsaWEB
- www.openwall.com/lists/oss-security/2011/04/06/13ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.