From 95a45aa651c801fe1c0829d8e3a4eb19962ed1dd Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Fri, 20 Mar 2026 16:02:02 -0400 Subject: [PATCH 1/3] Don't memoize AutoLinkScrubber across format_html calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scrubber was memoized on the view helper in b0fa6525 so that @regexp_timeout_reported would stop scanning remaining text nodes after a timeout. But memoizing on the helper leaks that flag across all format_html calls for the entire request — if one comment triggers a timeout, every subsequent comment on the page loses auto-linking. Use a fresh scrubber per call. The @regexp_timeout_reported flag still works within a single document's scrub, which is the intended scope. Co-Authored-By: Claude Opus 4.6 (1M context) --- app/helpers/html_helper.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/helpers/html_helper.rb b/app/helpers/html_helper.rb index f399274f4..7b65d34cf 100644 --- a/app/helpers/html_helper.rb +++ b/app/helpers/html_helper.rb @@ -1,7 +1,6 @@ module HtmlHelper def format_html(html) - @auto_link_scrubber ||= AutoLinkScrubber.new - Loofah::HTML5::DocumentFragment.parse(html).scrub!(@auto_link_scrubber).to_html.html_safe + Loofah::HTML5::DocumentFragment.parse(html).scrub!(AutoLinkScrubber.new).to_html.html_safe end def card_html_title(card) From 87c659cd676df43d2bb09a8a5eac2f4a88d1d6a0 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Fri, 20 Mar 2026 15:52:39 -0400 Subject: [PATCH 2/3] Skip auto-linking in very large text nodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A large comment body was causing Regexp::TimeoutError in production. The URI regexp isn't catastrophically backtracking — it's linear — but with a long enough string it exceeds the 1s Regexp.timeout set by Rails. Skip scanning text nodes over 10KB, which is well beyond any reasonable content for auto-linking and still keeps us many orders of magnitued under the timeout on an unloaded machine. Fixes FIZZY-Q4 Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/auto_link_scrubber.rb | 4 ++++ test/helpers/html_helper_test.rb | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/auto_link_scrubber.rb b/lib/auto_link_scrubber.rb index 961c2c879..e36039be9 100644 --- a/lib/auto_link_scrubber.rb +++ b/lib/auto_link_scrubber.rb @@ -18,6 +18,8 @@ class AutoLinkScrubber < Loofah::Scrubber TRAILING_PUNCTUATION = %(.?,:!;"'<>) TRAILING_PUNCTUATION_REGEXP = /[#{Regexp.escape(TRAILING_PUNCTUATION)}]+\z/ + MAX_TEXT_NODE_LENGTH = 10_000 + def initialize @direction = :top_down @regexp_timeout_reported = false @@ -56,6 +58,8 @@ class AutoLinkScrubber < Loofah::Scrubber end def find_links(text) + return [] if text.length > MAX_TEXT_NODE_LENGTH + links = [] text.scan(AUTOLINK_REGEXP) do diff --git a/test/helpers/html_helper_test.rb b/test/helpers/html_helper_test.rb index f19d80003..b8a498774 100644 --- a/test/helpers/html_helper_test.rb +++ b/test/helpers/html_helper_test.rb @@ -125,6 +125,17 @@ class HtmlHelperTest < ActionView::TestCase end end + test "skip auto-linking in very large text nodes" do + url = "https://example.com" + large_text = "x" * 5_000 + " #{url} " + "y" * 5_000 + input = "

#{large_text}

" + + result = format_html(input) + + assert_no_match(/ Date: Fri, 20 Mar 2026 19:53:02 -0400 Subject: [PATCH 3/3] Remove @regexp_timeout_reported from AutoLinkScrubber No longer needed since the scrubber is not memoized across calls and the text node length guard prevents the performance issue. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/auto_link_scrubber.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/auto_link_scrubber.rb b/lib/auto_link_scrubber.rb index e36039be9..17191c2ea 100644 --- a/lib/auto_link_scrubber.rb +++ b/lib/auto_link_scrubber.rb @@ -22,13 +22,12 @@ class AutoLinkScrubber < Loofah::Scrubber def initialize @direction = :top_down - @regexp_timeout_reported = false end def scrub(node) return Loofah::Scrubber::STOP if EXCLUDED_ELEMENTS.include?(node.name) - if node.text? && !@regexp_timeout_reported + if node.text? replacement = autolink_text_node(node) node.replace(replacement) if replacement end @@ -78,7 +77,6 @@ class AutoLinkScrubber < Loofah::Scrubber links rescue Regexp::TimeoutError => error Sentry.capture_exception error if Fizzy.saas? - @regexp_timeout_reported = true [] end