From dee8b33e29c5c084fc9d08d5edca1c6c9ef40139 Mon Sep 17 00:00:00 2001 From: Thiago Araujo Date: Wed, 28 Jan 2026 22:02:40 -0700 Subject: [PATCH 1/2] fix: add possessive quantifier to regex match for formatted strings (Fixes https://github.com/faker-ruby/faker/security/code-scanning/14) Adds a possessive quantifier (an extra `+`) to the regex to prevent backtracking. This is to prevent the issue 'Polynomial regular expression used on uncontrolled data code'. It seems a bit overkill, given how these formatted strings are used, but I don't think it hurts either. See reference: https://ruby-doc.org/3.4.1/Regexp.html#class-Regexp-label-Greedy-2C+Lazy-2C+or+Possessive+Matching --- lib/faker.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/faker.rb b/lib/faker.rb index ce19d536bd..499f490a99 100644 --- a/lib/faker.rb +++ b/lib/faker.rb @@ -133,7 +133,8 @@ def fetch_all(key) # formatted translation: e.g., "#{first_name} #{last_name}". def parse(key) fetched = fetch(key) - parts = fetched.scan(/(\(?)#\{([A-Za-z]+\.)?([^}]+)\}([^#]+)?/).map do |prefix, kls, meth, etc| + + parts = fetched.scan(/(\(?)#\{([A-Za-z]+\.)?([^}]+)\}([^#]++)?/).map do |prefix, kls, meth, etc| # If the token had a class Prefix (e.g., Name.first_name) # grab the constant, otherwise use self cls = kls ? Faker.const_get(kls.chop) : self From 066f63a71d2591410af83e2fa7c29edb29f7df71 Mon Sep 17 00:00:00 2001 From: Thiago Araujo Date: Wed, 28 Jan 2026 22:16:37 -0700 Subject: [PATCH 2/2] fix: add possessive quantifier to regex match for regexify (Fixes https://github.com/faker-ruby/faker/security/code-scanning/13) Similar to commit fa6d5df205517bc342cef1189b06b486c3a942c0. Adds a possessive quantifier (an extra `+`) to the regex to prevent backtracking. See reference: https://ruby-doc.org/3.4.1/Regexp.html#class-Regexp-label-Greedy-2C+Lazy-2C+or+Possessive+Matching --- lib/faker.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/faker.rb b/lib/faker.rb index 499f490a99..f44bc26e5b 100644 --- a/lib/faker.rb +++ b/lib/faker.rb @@ -95,12 +95,12 @@ def regexify(reg) reg .gsub(%r{^/?\^?}, '').gsub(%r{\$?/?$}, '') # Ditch the anchors .gsub(/\{(\d+)\}/, '{\1,\1}').gsub('?', '{0,1}') # All {2} become {2,2} and ? become {0,1} - .gsub(/(\[[^\]]+\])\{(\d+),(\d+)\}/) { |_match| Regexp.last_match(1) * sample(Array(Range.new(Regexp.last_match(2).to_i, Regexp.last_match(3).to_i))) } # [12]{1,2} becomes [12] or [12][12] - .gsub(/(\([^)]+\))\{(\d+),(\d+)\}/) { |_match| Regexp.last_match(1) * sample(Array(Range.new(Regexp.last_match(2).to_i, Regexp.last_match(3).to_i))) } # (12|34){1,2} becomes (12|34) or (12|34)(12|34) - .gsub(/(\\?.)\{(\d+),(\d+)\}/) { |_match| Regexp.last_match(1) * sample(Array(Range.new(Regexp.last_match(2).to_i, Regexp.last_match(3).to_i))) } # A{1,2} becomes A or AA or \d{3} becomes \d\d\d - .gsub(/\((.*?)\)/) { |match| sample(match.gsub(/[()]/, '').split('|')) } # (this|that) becomes 'this' or 'that' - .gsub(/\[([^\]]+)\]/) { |match| match.gsub(/(\w-\w)/) { |range| sample(Array(Range.new(*range.split('-')))) } } # All A-Z inside of [] become C (or X, or whatever) - .gsub(/\[([^\]]+)\]/) { |_match| sample(Regexp.last_match(1).chars) } # All [ABC] become B (or A or C) + .gsub(/(\[[^\]]++\])\{(\d+),(\d+)\}/) { |_match| Regexp.last_match(1) * sample(Array(Range.new(Regexp.last_match(2).to_i, Regexp.last_match(3).to_i))) } # [12]{1,2} becomes [12] or [12][12] + .gsub(/(\([^)]++\))\{(\d+),(\d+)\}/) { |_match| Regexp.last_match(1) * sample(Array(Range.new(Regexp.last_match(2).to_i, Regexp.last_match(3).to_i))) } # (12|34){1,2} becomes (12|34) or (12|34)(12|34) + .gsub(/(\\?.)\{(\d+),(\d+)\}/) { |_match| Regexp.last_match(1) * sample(Array(Range.new(Regexp.last_match(2).to_i, Regexp.last_match(3).to_i))) } # A{1,2} becomes A or AA or \d{3} becomes \d\d\d + .gsub(/\((.*?)\)/) { |match| sample(match.gsub(/[()]/, '').split('|')) } # (this|that) becomes 'this' or 'that' + .gsub(/\[([^\]]++)\]/) { |match| match.gsub(/(\w-\w)/) { |range| sample(Array(Range.new(*range.split('-')))) } } # All A-Z inside of [] become C (or X, or whatever) + .gsub(/\[([^\]]++)\]/) { |_match| sample(Regexp.last_match(1).chars) } # All [ABC] become B (or A or C) .gsub('\d') { |_match| sample(Numbers) } .gsub('\w') { |_match| sample(Letters) } end