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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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