Summary
Regex evaluation is intended to be isolated in a Web Worker with a 1-second timeout, but runRegexTestTask() falls back to synchronous main-thread execution whenever Worker support is unavailable or most worker failures occur.
That fallback defeats the exact safety boundary the worker is meant to provide: a catastrophic-backtracking pattern can block the UI indefinitely, and the timeout cannot interrupt synchronous JavaScript running on the main thread.
Current behavior
src/features/tools/regex-tester/regex-test-task.ts contains two synchronous fallback paths:
if (typeof Worker === "undefined") {
return testRegexPattern(...)
}
and, after worker creation/runtime/message/postMessage errors:
return testRegexPattern(...)
Only timeout and abort errors avoid the fallback.
This means patterns such as nested ambiguous quantifiers can run synchronously when:
Worker is unavailable;
- the worker constructor is blocked by CSP or browser policy;
- the worker chunk fails to load;
- the worker emits a runtime or message error;
- worker creation/postMessage fails for an environment-specific reason.
The same task is also used by the pipeline adapter, so the exposure is not limited to the standalone Regex Tester page.
Existing guard gap
tests/guards/regex-worker-guard.test.ts verifies that the page calls runRegexTestTask, that a worker constructor exists in the task, and that the file contains a timeout token. It does not detect synchronous fallback inside the task.
The guard therefore passes even though the main-thread ReDoS path remains reachable.
Why this matters
A timeout only protects work that can be terminated. Once an untrusted regular expression runs on the UI thread, neither AbortController nor a timer can regain control until the expression returns.
This is both a reliability issue and a client-side denial-of-service boundary for pasted or pipeline-supplied patterns.
Recommended implementation
Fail closed when worker isolation is unavailable:
- do not call
testRegexPattern() synchronously for user-controlled patterns;
- return a structured, localized
worker_unavailable / safe_evaluation_unavailable result;
- use
WorkerTaskError.code, not message text, to distinguish timeout, abort, construction, runtime, message, and postMessage failures;
- preserve the existing timeout/termination behavior for worker execution;
- consider an RE2-compatible bounded engine as a future explicit fallback, but do not use native synchronous
RegExp as the safety fallback.
Acceptance criteria
Relevant files
src/features/tools/regex-tester/regex-test-task.ts
src/features/tools/regex-tester/regex-test-worker.ts
src/features/tools/regex-tester/logic.ts
src/core/workers/run-worker-task.ts
src/features/pipeline/adapter-registry.ts
tests/guards/regex-worker-guard.test.ts
tests/guards/bf-heavy-worker-safeguards.test.ts
tests/unit/run-worker-task.test.ts
Summary
Regex evaluation is intended to be isolated in a Web Worker with a 1-second timeout, but
runRegexTestTask()falls back to synchronous main-thread execution whenever Worker support is unavailable or most worker failures occur.That fallback defeats the exact safety boundary the worker is meant to provide: a catastrophic-backtracking pattern can block the UI indefinitely, and the timeout cannot interrupt synchronous JavaScript running on the main thread.
Current behavior
src/features/tools/regex-tester/regex-test-task.tscontains two synchronous fallback paths:and, after worker creation/runtime/message/postMessage errors:
Only timeout and abort errors avoid the fallback.
This means patterns such as nested ambiguous quantifiers can run synchronously when:
Workeris unavailable;The same task is also used by the pipeline adapter, so the exposure is not limited to the standalone Regex Tester page.
Existing guard gap
tests/guards/regex-worker-guard.test.tsverifies that the page callsrunRegexTestTask, that a worker constructor exists in the task, and that the file contains a timeout token. It does not detect synchronous fallback inside the task.The guard therefore passes even though the main-thread ReDoS path remains reachable.
Why this matters
A timeout only protects work that can be terminated. Once an untrusted regular expression runs on the UI thread, neither
AbortControllernor a timer can regain control until the expression returns.This is both a reliability issue and a client-side denial-of-service boundary for pasted or pipeline-supplied patterns.
Recommended implementation
Fail closed when worker isolation is unavailable:
testRegexPattern()synchronously for user-controlled patterns;worker_unavailable/safe_evaluation_unavailableresult;WorkerTaskError.code, not message text, to distinguish timeout, abort, construction, runtime, message, and postMessage failures;RegExpas the safety fallback.Acceptance criteria
runRegexTestTask()never invokes native regex evaluation on the main thread whenWorkeris unavailable.Workerabsence and eachWorkerTaskErrorcategory and prove that the synchronous evaluator is not called.Relevant files
src/features/tools/regex-tester/regex-test-task.tssrc/features/tools/regex-tester/regex-test-worker.tssrc/features/tools/regex-tester/logic.tssrc/core/workers/run-worker-task.tssrc/features/pipeline/adapter-registry.tstests/guards/regex-worker-guard.test.tstests/guards/bf-heavy-worker-safeguards.test.tstests/unit/run-worker-task.test.ts