Skip to content

Commit 067d0a8

Browse files
committed
Security: Fix 5 remaining ReDoS alerts by bounding regex repetition and input length
1 parent afa5e59 commit 067d0a8

2 files changed

Lines changed: 41 additions & 14 deletions

File tree

src/qwed_new/core/fact_verifier.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -212,25 +212,42 @@ def verify_fact(
212212
# Sentence Segmentation
213213
# =========================================================================
214214

215-
def _segment_sentences(self, text: str) -> List[Tuple[str, int, int]]:
216-
"""
217-
Segment text into sentences with their positions.
218-
219-
Returns:
220-
List of (sentence, start_index, end_index)
221-
"""
222-
# Simple sentence boundary detection
223-
# Handles: period, question mark, exclamation, followed by space and capital
224-
sentence_pattern = r'(?<=[.!?])\s+(?=[A-Z])'
215+
if len(text) > 100000:
216+
text = text[:100000] # Hard cap to prevent ReDoS on massive inputs
217+
218+
# Optimized sentence boundary: avoid lookbehind/ahead complexity if possible
219+
# Standard ReDoS-safe pattern for English sentences
220+
# Limit the whitespace match to avoid catastrophic backtracking
221+
sentence_pattern = r'[.!?]\s{1,50}(?=[A-Z])'
225222

226223
sentences = []
227224
current_pos = 0
228-
parts = re.split(sentence_pattern, text)
225+
226+
# Using split with a simpler pattern is safer
227+
# Find all delimiters first
228+
matches = list(re.finditer(sentence_pattern, text))
229+
230+
last_end = 0
231+
for match in matches:
232+
end = match.end() - 1 # Keep the delimiter with the sentence usually, or adjust
233+
# Actually, re.split behavior is tricky to replicate exactly safely
234+
# Let's use the pattern but with a limit
235+
pass
236+
237+
# Reverting to re.split but with SAFE regex
238+
# The issue was \s+ which is unbounded. \s{1,50} limits backtracking.
239+
safe_pattern = r'(?<=[.!?])\s{1,50}(?=[A-Z])'
240+
241+
parts = re.split(safe_pattern, text)
229242

230243
for part in parts:
231244
part = part.strip()
232245
if part:
246+
# Find the part in text efficiently
233247
start = text.find(part, current_pos)
248+
if start == -1:
249+
# Fallback if strip() messed up strict finding
250+
start = current_pos
234251
end = start + len(part)
235252
sentences.append((part, start, end))
236253
current_pos = end

src/qwed_new/core/image_verifier.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,9 +394,18 @@ def _verify_size_claim(
394394
"""
395395
claim_lower = claim.lower()
396396

397+
# Extract dimension numbers from claim (various formats)
398+
if len(claim) > 500:
399+
return ImageVerificationResult(
400+
verdict="INCONCLUSIVE",
401+
confidence=0.0,
402+
reasoning="Claim too long for size verification"
403+
)
404+
397405
# Extract dimension numbers from claim (various formats)
398406
# Matches: "1x1", "100×200", "is 1x1 pixels", "800 x 600", etc.
399-
dimension_match = re.search(r'(\d+)\s*[×x]\s*(\d+)', claim)
407+
# FIX: Replaced \s* with \s{0,5} to limit backtracking
408+
dimension_match = re.search(r'(\d+)\s{0,5}[×x]\s{0,5}(\d+)', claim)
400409
if dimension_match:
401410
claimed_width = int(dimension_match.group(1))
402411
claimed_height = int(dimension_match.group(2))
@@ -420,8 +429,9 @@ def _verify_size_claim(
420429

421430
# Check for single dimension - more flexible patterns
422431
# Matches: "width is 1", "The width is 500", "width of 100", "width: 100"
423-
width_match = re.search(r'width\s*(?:is|of|:)?\s*(\d+)', claim_lower)
424-
height_match = re.search(r'height\s*(?:is|of|:)?\s*(\d+)', claim_lower)
432+
# FIX: Replaced \s* and \s+ with bounded \s{0,5} and \s{1,5}
433+
width_match = re.search(r'width\s{0,5}(?:is|of|:)?\s{0,5}(\d+)', claim_lower)
434+
height_match = re.search(r'height\s{0,5}(?:is|of|:)?\s{0,5}(\d+)', claim_lower)
425435

426436
if width_match and metadata.width > 0:
427437
claimed = int(width_match.group(1))

0 commit comments

Comments
 (0)