Regex Timeout Guard for Untrusted Input

Run operator-supplied regular expressions with the regex module timeout, an input length cap, and explicit failure handling.

PythonSecurity Boundaries

Code

snippet
import regex

USER_PATTERN = regex.compile(
    r"^(?P<user>[a-z0-9_]{3,32})@(?P<domain>[a-z0-9.-]{1,120})$"
)


def safe_fullmatch(
    pattern: regex.Pattern,
    text: str,
    *,
    timeout: float = 0.05,
    max_length: int = 4096,
) -> regex.Match | None:
    if len(text) > max_length:
        raise ValueError(f"input length {len(text)} exceeds {max_length}")
    try:
        return pattern.fullmatch(text, timeout=timeout)
    except TimeoutError as error:
        raise TimeoutError("pattern evaluation exceeded the time budget") from error


if __name__ == "__main__":
    match = safe_fullmatch(USER_PATTERN, "[email protected]")
    assert match is not None
    print(match.group("user"), match.group("domain"))

    try:
        safe_fullmatch(USER_PATTERN, "x" * 5000)
    except ValueError as error:
        print("rejected:", error)

    evil = regex.compile(r"^(a+)+$")
    try:
        safe_fullmatch(evil, "a" * 4000 + "b", timeout=0.001)
    except TimeoutError as error:
        print("timeout:", error)