Denial of Service / Performance vulnerability (regex-based path in number formatting)

HIGH
rails/rails
Commit: 2d485aecf594
Affected: Rails 8.1.0 - 8.1.3 (prior to 8.1.4+)
2026-04-05 12:02 UTC

Description

The commit addresses a performance vulnerability in ActiveSupport::NumberHelper::NumberToDelimitedConverter related to formatting large numbers with a delimiter pattern. Previously, the conversion path always invoked a regular expression-based delimited insertion (via delimiter_pattern). This regex-based path can be computationally expensive and susceptible to denial-of-service-like CPU exhaustion on large inputs or crafted patterns. The fix introduces a guarded branch: if a delimiter_pattern is not present, it uses a deterministic manual grouping path (no regex) to insert delimiters; if a delimiter_pattern is present, it continues to use the regex-based approach. The change reduces exposure by avoiding regex-based processing for inputs where a straightforward grouping is possible, and adds tests asserting expected formatted outputs for edge cases (e.g., 1234 -> 1,234; 12345 -> 12,345).

Proof of Concept

# Proof of concept (DoS via large numeric input triggering regex-based path) # Requires Rails environment with ActiveSupport loaded (Rails 8.1.x vulnerable path) require 'benchmark' require 'active_support/all' # Construct a very large integer (many digits) to stress the formatting path. # The DoS risk scales with input size due to regex processing. num_str = '9' * 10000 # 10k digits; increase to stress further if needed n = num_str.to_i puts "Formatting a 10k-digit number..." Benchmark.bm(20) do |x| x.report("number_to_delimited") do ActiveSupport::NumberHelper.number_to_delimited(n, delimiter: ',') end end # Expected: the operation consumes significantly more CPU time on vulnerable versions # compared to fixed versions. In a secure environment, time should remain reasonable.

Commit Details

Author: Jean Boussier

Date: 2026-03-04 09:33 UTC

Message:

Improve performance of NumberToDelimitedConverter [CVE-2026-33169] [GHSA-cg4j-q9v8-6v38]

Triage Assessment

Vulnerability Type: Denial of Service / Performance vulnerability related to regex parsing

Confidence: HIGH

Reasoning:

The commit references a CVE and GHSA entry, and the code changes modify the number formatting path to avoid a potential regex-based operation when a delimiter pattern is present. It adds a manual grouping fallback that prevents reliance on a potentially vulnerable regex, addressing performance/security concerns (likely a Denial of Service risk) related to input handling during number formatting.

Verification Assessment

Vulnerability Type: Denial of Service / Performance vulnerability (regex-based path in number formatting)

Confidence: HIGH

Affected Versions: Rails 8.1.0 - 8.1.3 (prior to 8.1.4+)

Code Diff

diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb index 4fb2fb7150fc8..7575429423e03 100644 --- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb @@ -16,9 +16,24 @@ def convert private def parts left, right = number.to_s.split(".") - left.gsub!(delimiter_pattern) do |digit_to_delimit| - "#{digit_to_delimit}#{options[:delimiter]}" + if delimiter_pattern + left.gsub!(delimiter_pattern) do |digit_to_delimit| + "#{digit_to_delimit}#{options[:delimiter]}" + end + else + left_parts = [] + offset = left.size % 3 + if offset > 0 + left_parts << left[0, offset] + end + + (left.size / 3).times do |i| + left_parts << left[offset + (i * 3), 3] + end + + left = left_parts.join(options[:delimiter]) end + [left, right].compact end diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb index bae6a9eb2261e..da57df0ff7ad4 100644 --- a/activesupport/test/number_helper_test.rb +++ b/activesupport/test/number_helper_test.rb @@ -139,6 +139,8 @@ def test_to_delimited assert_equal("12,345,678", number_helper.number_to_delimited(12345678)) assert_equal("0", number_helper.number_to_delimited(0)) assert_equal("123", number_helper.number_to_delimited(123)) + assert_equal("1,234", number_helper.number_to_delimited(1234)) + assert_equal("12,345", number_helper.number_to_delimited(12345)) assert_equal("123,456", number_helper.number_to_delimited(123456)) assert_equal("123,456.78", number_helper.number_to_delimited(123456.78)) assert_equal("123,456.789", number_helper.number_to_delimited(123456.789))
← Back to Alerts View on GitHub →