#3119·joi

ReDoS via string.pattern() with catastrophic-backtracking regex blocks event loop

Author: zhangjiashuo-csCreated May 24, 2026Updated Jul 12, 2026

Description

Joi.string().pattern(re) uses RegExp.test() directly with no safety wrapper. When the schema developer passes (or accepts at runtime) a regex with nested quantifiers (e.g. ((a+)+)+$), validation of even short attacker-controlled strings blocks the event loop for seconds-to-minutes — classic Regular-Expression Denial-of-Service (ReDoS).

Reproduction (joi 17.13.3)

javascript
const Joi = require('joi');

const schema = Joi.string().pattern(/((a+)+)+$/);
const input = 'a'.repeat(22) + '!';   // 23 chars

const t0 = Date.now();
schema.validate(input);
console.log('elapsed', Date.now() - t0, 'ms');
// elapsed ≈ 5000ms (blocking the event loop)

Each additional a roughly doubles the time:

input length elapsed
18 + ! ~ 300 ms
20 + ! ~ 1.2 s
22 + ! ~ 5 s
24 + ! ~ 20 s
28 + ! ~ 5 min

Patterns like (a*)*$, (a|aa)*$, (a|a?)*$ exhibit the same behavior.

Property that fails

javascript
import fc from "fast-check";
import Joi from "joi";

// A schema developer composes a pattern from validated parts; whether the
// resulting regex is ReDoS-safe is not obvious at compile time.
fc.assert(fc.property(
  fc.integer({min: 1, max: 30}),
  (n) => {
    const schema = Joi.string().pattern(/((a+)+)+$/);
    const t0 = Date.now();
    schema.validate("a".repeat(n) + "!");
    return (Date.now() - t0) < 1000;   // < 1 s for any small input
  }
));
// Shrinks to n=22

Threat model

Two realistic attack scenarios:

  1. Public regex-config endpoint: An API lets users specify a "name pattern", "URL pattern", or similar custom validation regex (joi-based JSON Schema admin tools, low-code form builders). A malicious user supplies a catastrophic-backtracking pattern; subsequent requests that go through it hang the server.
  2. Static schema using user-supplied regex built from user input (e.g. Joi.string().pattern(new RegExp(req.body.pattern))): direct DoS on every request that flows through that handler.

Even for schemas that hard-code the regex, third-party schema libraries that wrap user input into joi patterns inherit the issue.

Suggested fix

Wrap the regex.test(value) call with one of:

  1. Synchronous timeout via vm.runInNewContext({timeout: N}) (rough, but blocks the event loop only for N ms).
  2. Re-implement matching with a Thompson-NFA backed engine for joi's patterns (e.g. re2 via re2/re2-wasm). Default to the safe engine; let users opt-in to native RegExp with a clear warning.
  3. At minimum: detect catastrophic-backtracking patterns at pattern(re) registration time using static analysis (e.g. safe-regex or recheck) and either reject or warn.

Yup, Zod, and ajv have all converged on documenting the ReDoS concern; many of them ship a format: re2 option. joi documents nothing here.

Environment

  • joi: 17.13.3
  • Node: 20+