# Unsafe Dynamic Regex
# Detects re.X() calls where the pattern argument is a variable (not a string literal).
# Static regexes (r"...", "...") are safe.
id: python-unsafe-regex
name: Unsafe Dynamic Regex
severity: warning
category: security
defect_class: injection
inline_tier: blocking
language: python

message: "re.{{FUNC}}() with dynamic pattern — ReDoS risk if pattern is user-controlled"

description: |
  Building regex patterns from variables can cause ReDoS (Regular Expression
  Denial of Service) if the pattern contains user input. A crafted input can
  cause catastrophic backtracking.

  ✅ FIX:
    # If pattern must be dynamic, validate/escape it first
    import re
    safe_pattern = re.escape(user_input)
    re.search(safe_pattern, text)

    # Or use string methods instead
    text.find(user_input)

query: |
  (call
    function: (attribute
      object: (identifier) @MOD
      attribute: (identifier) @FUNC)
    arguments: (argument_list) @ARGS)
  (#eq? @MOD "re")
  (#match? @FUNC "^(compile|match|search|fullmatch|findall|finditer|sub|subn|split)$")

metavars:
  - MOD
  - FUNC
  - ARGS

# Post-filter: fire ONLY when the first argument is an identifier (dynamic pattern),
# not a string literal (static pattern, which is safe).
post_filter: regex_first_arg_identifier

has_fix: false

tags:
  - security
  - regex
  - ReDoS
  - python-specific

examples:
  bad: |
    pattern = req.GET["q"]   # attacker-controlled
    re.search(pattern, text)  # BAD - dynamic pattern
  
  good: |
    re.search(r"static_pattern", text)        # OK - literal pattern
    re.search(re.escape(user_input), text)    # OK - escaped
