| name | regex-escape-redos-guard |
| description | Prevent ReDoS (Regex Denial of Service) by escaping user-supplied regex special characters. escape-string-regexp patterns, catastrophic backtracking detection, safe dynamic regex construction, and timeout guards. Sources: sindresorhus/escape-string-regexp. |
/regex-escape-redos-guard
When to Use
- Building regex from user input or agent-supplied strings
- Search/replace where the pattern comes from config/user data
- Escaping file names, URLs, or shell strings before regex use
- Detecting and blocking catastrophically backtracking patterns
Do NOT use for
- Sanitizing HTML/code content (use [[dompurify-xss-prevention]])
- Validating regex syntax (use a dedicated regex linter)
Basic escaping
import escapeStringRegexp from 'escape-string-regexp'
function safeSearch(haystack: string, needle: string, flags = 'gi'): RegExpMatchArray | null {
const escaped = escapeStringRegexp(needle)
return haystack.match(new RegExp(escaped, flags))
}
const re = new RegExp(escapeStringRegexp(userInput))
DIY escaping (no dependency)
function escapeRegex(str: string): string {
return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d')
}
ReDoS patterns to reject (denylist)
const REDOS_PATTERNS = [
/\(.*\+\).*\+/,
/\(.*\*\).*\*/,
/\(.*\|\).*\|/,
/\.\*.*\.\*/,
]
function isReDoSSafe(pattern: string): boolean {
return !REDOS_PATTERNS.some(r => r.test(pattern))
}
function safeRegex(pattern: string, flags = ''): RegExp {
if (!isReDoSSafe(pattern)) {
throw new Error(`[regex] potentially catastrophic pattern: ${pattern}`)
}
return new RegExp(pattern, flags)
}
Timeout guard (Node.js worker thread)
import { Worker, isMainThread, workerData, parentPort } from 'worker_threads'
function regexWithTimeout(
input: string,
pattern: string,
timeoutMs = 100
): Promise<string[] | null> {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, {
workerData: { input, pattern }
})
const timer = setTimeout(() => {
worker.terminate()
reject(new Error('[regex] timeout — possible ReDoS'))
}, timeoutMs)
worker.on('message', (result) => { clearTimeout(timer); resolve(result) })
worker.on('error', (err) => { clearTimeout(timer); reject(err) })
})
}
if (!isMainThread) {
const { input, pattern } = workerData
const match = input.match(new RegExp(pattern, 'g'))
parentPort!.(match)
}
Anti-Fake-Pass Checklist
❌ new RegExp(userInput) without escaping → ReDoS or injection of any pattern
❌ denylist pattern approach alone (imperfect) → pair with escaping or timeout
❌ Timeout guard kills worker but request hangs → must resolve/reject in timeout handler
❌ Flag 'g' + lastIndex not reset → regex object stateful, skips matches on second call
❌ Backslash in input not escaped → \\w in user input becomes \w (wildcard) in regex