Skip to content

[P1][Reliability/Security] Never evaluate untrusted regex on the main thread when worker isolation fails #330

Description

@baixiangcpp

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

  • runRegexTestTask() never invokes native regex evaluation on the main thread when Worker is unavailable.
  • Worker constructor, runtime, message, and postMessage failures return a safe actionable error instead of evaluating synchronously.
  • Timeout still terminates the worker and returns the existing timeout result.
  • Abort still terminates the worker and does not update stale UI state.
  • Pipeline execution receives the same fail-closed behavior.
  • Unit tests mock Worker absence and each WorkerTaskError category and prove that the synchronous evaluator is not called.
  • The regex worker guard explicitly rejects a main-thread fallback path.
  • A catastrophic-pattern regression test demonstrates that the UI/task promise remains bounded by worker termination.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions