Module: Alap::ValidateRegex
- Defined in:
- lib/alap/validate_regex.rb
Overview
Lightweight ReDoS guard for server-side regex parameters.
Rejects patterns with nested quantifiers that cause catastrophic backtracking: (a+)+, (a*)*b, (w+w+)+, etc.
Constant Summary collapse
- QUANTIFIER_AFTER =
/\A(?:[?*+]|\{\d+(?:,\d*)?\})/- QUANTIFIER_IN_BODY =
/[?*+]|\{\d+(?:,\d*)?\}/
Class Method Summary collapse
-
.call(pattern) ⇒ Object
Returns { “safe” => true } or { “safe” => false, “reason” => “…” }.
Class Method Details
.call(pattern) ⇒ Object
Returns { “safe” => true } or { “safe” => false, “reason” => “…” }.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/alap/validate_regex.rb', line 17 def self.call(pattern) begin Regexp.new(pattern) rescue RegexpError return { "safe" => false, "reason" => "Invalid regex syntax" } end group_starts = [] i = 0 while i < pattern.length ch = pattern[i] # Skip escaped characters if ch == "\\" i += 2 next end # Skip character classes [...] if ch == "[" i += 1 i += 1 if i < pattern.length && pattern[i] == "^" i += 1 if i < pattern.length && pattern[i] == "]" while i < pattern.length && pattern[i] != "]" i += 1 if pattern[i] == "\\" i += 1 end i += 1 next end if ch == "(" group_starts.push(i) i += 1 next end if ch == ")" unless group_starts.empty? start = group_starts.pop after_group = pattern[(i + 1)..] if after_group && QUANTIFIER_AFTER.match?(after_group) body = pattern[(start + 1)...i] stripped = strip_escapes_and_classes(body) if QUANTIFIER_IN_BODY.match?(stripped) return { "safe" => false, "reason" => "Nested quantifier detected — potential ReDoS" } end end end i += 1 next end i += 1 end { "safe" => true } end |