What are RegexOption settings and common correctness/performance pitfalls (escaping, raw strings, catastrophic backtracking) when using Kotlin Regex on untrusted input?
answer
- Options: IGNORE_CASE, MULTILINE, DOT_MATCHES_ALL, LITERAL, COMMENTS
- Raw string """\d""" avoids double-escaping
- Regex.escape / escapeReplacement for literals
- (a+)+ -> catastrophic backtracking / ReDoS
- Compiled Regex is immutable + thread-safe
basics
~10 sRegexOption tunes matching (ignore case, multiline, dot-matches-newline). Watch escaping in literals, use raw strings, and avoid patterns that can backtrack catastrophically on hostile input.
solid answer
~40 sRegex accepts RegexOption flags: IGNORE_CASE, MULTILINE (^/$ match line boundaries), DOT_MATCHES_ALL (. matches newlines), COMMENTS, LITERAL, UNIX_LINES, CANON_EQ — pass a single option or a set. Correctness pitfalls: in a normal "..." literal you must double backslashes ("\\d"); raw strings """\d""" avoid that. For matching literal user text, use Regex.escape(text) or RegexOption.LITERAL rather than hand-escaping. Performance/security: nested quantifiers and overlapping alternations like (a+)+ cause catastrophic backtracking (ReDoS) — an adversary can hang a thread with a crafted input. Mitigate by avoiding ambiguous quantifiers, using possessive/atomic constructs or bounded repetition, validating input length, and treating regex over untrusted input as a potential DoS vector. Also reuse compiled Regex instances since they are immutable and thread-safe.
code
kotlin · 6 lines// safe literal matching of user text
fun containsLiteral(haystack: String, needle: String): Boolean =
Regex(Regex.escape(needle)).containsMatchIn(haystack)
// readable, raw-string pattern with options
val token = Regex("""^\w{3,32}$""", setOf(RegexOption.IGNORE_CASE))go deeper
Knows IGNORE_CASE exists and that backslashes need escaping in string literals.
Uses RegexOption sets and raw strings, and knows Regex.escape for literal matching.
Recognizes catastrophic backtracking patterns and applies mitigations (atomic groups, bounded repetition, input limits).
Treats regex over untrusted input as a DoS threat, sets org-wide guidance (escape helpers, length caps, parser-over-regex for structured formats) and reasons about engine internals.
## RegexOption Flags passed to the constructor/`toRegex` to change matching semantics: - **`IGNORE_CASE`** — case-insensitive matching. - **`MULTILINE`** — `^` and `$` match at line boundaries, not just string ends. - **`DOT_MATCHES_ALL`** — `.` also matches line terminators. - **`COMMENTS`** — whitespace and `#` comments in the pattern are ignored (readable patterns). - **`LITERAL`** — the pattern is treated as plain text, no metacharacters. - **`UNIX_LINES`**, **`CANON_EQ`** — line-terminator and canonical-equivalence handling. ```kotlin val ci = Regex("error", RegexOption.IGNORE_CASE) val multi = Regex("^x", setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE)) ``` ## Escaping & raw strings In a normal string literal, a backslash is itself escaped, so a digit class is `"\\d"`. Kotlin **raw strings** (`"""..."""`) take the text verbatim, so `"""\d{3}"""` is cleaner and less error-prone for complex patterns. To match a *literal* user-provided string, never hand-build the pattern — use `Regex.escape(input)` (escapes all metacharacters) or `RegexOption.LITERAL`. `Regex.escapeReplacement(text)` does the same for replacement strings so `$`/`\` aren't interpreted. ## Catastrophic backtracking (ReDoS) Patterns with nested or overlapping quantifiers — `(a+)+`, `(a|a)*`, `(.*)*` — can take exponential time on certain non-matching inputs because the engine tries enormous numbers of partitions. On **untrusted input** this is a denial-of-service vector. Mitigations: - Rewrite to unambiguous patterns; avoid nesting quantifiers. - Use **possessive quantifiers** (`a++`) or **atomic groups** (`(?>...)`), supported by the underlying `java.util.regex`, to prevent backtracking. - Bound repetition (`{0,100}`) and **cap input length** before matching. - Consider timeouts / running matching on input you control; prefer a real parser for structured formats. ## Thread-safety & reuse A compiled `Regex` is immutable and safe to share across threads; declare it once and reuse it to amortize compilation cost.
- How do you safely match an arbitrary user string literally?Wrap it with Regex.escape(text) or use RegexOption.LITERAL so metacharacters are treated as plain text.
- Name one way to defend against ReDoS in Kotlin/JVM regex.Use atomic groups (?>...) or possessive quantifiers (a++), bound repetition, cap input length, and avoid nested quantifiers.
Catastrophic backtracking is like trying every possible way to split a word into syllables before giving up — a few characters can explode into astronomically many attempts.
saying these in an interview costs you the question
- Building patterns from untrusted input without Regex.escape
- Unaware that (a+)+-style patterns can hang on hostile input
- Writing \d in a normal literal instead of \\d or a raw string
- Believing Regex is mutable/not thread-safe
- Using regex to parse deeply nested structured formats instead of a parser