ReDoS, ou Regular Expression Denial of Service, acontece quando uma expressão regular possui caminhos de backtracking excessivos e recebe uma entrada criada para consumir CPU. Como o JavaScript executa regex na thread principal, uma única validação pode bloquear o event loop e atrasar todas as requisições do processo Node.js.
Exemplo perigoso
const pattern = /^(a+)+$/;
pattern.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaa!');Grupos repetidos e sobrepostos podem causar crescimento exponencial do trabalho para uma entrada que quase corresponde.
Por que bloqueia o Node.js
O motor de regex executa de forma síncrona. Enquanto ele tenta combinações, timers, respostas HTTP e callbacks não avançam. CPU sobe e event loop delay aumenta.
Padrões de risco
- quantificadores aninhados, como
(a+)+; - alternativas sobrepostas, como
(a|aa)+; - repetição ampla antes de um sufixo;
- grupos opcionais repetidos;
- backtracking em strings sem limite;
- regex construída pelo usuário.
Limite a entrada
if (typeof value !== 'string' || value.length > 256) {
throw new ValidationError('Valor inválido');
}
Limites reduzem impacto, mas não corrigem uma expressão exponencial. Use as duas medidas.
Prefira validação simples
Para campos estruturados, combine verificações:
function isSafeUsername(value) {
if (typeof value !== 'string') return false;
if (value.length < 3 || value.length > 30) return false;
return /^[a-zA-Z0-9_]+$/.test(value);
}Uma regex ancorada e com classe simples é mais previsível.
Não tente implementar toda a especificação de email com uma expressão gigante. Use validação pragmática, limite de tamanho e confirmação por envio de mensagem.
URLs
Use o parser nativo:
function parseHttpsUrl(value) {
const url = new URL(value);
if (url.protocol !== 'https:') throw new ValidationError();
return url;
}Regex não substitui parsing e validação de segurança contra SSRF.
Escape de entrada
Quando o usuário fornece um termo literal para busca por regex, escape metacaracteres:
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const regex = new RegExp(escapeRegExp(term), 'i');Ainda limite tamanho e quantidade de buscas.
Não aceite regex arbitrária
Uma API que recebe padrões do usuário permite ReDoS direto. Para filtros, ofereça operadores predefinidos, busca literal, prefixo ou linguagem com limites.
Static analysis
Ferramentas como safe-regex podem sinalizar padrões suspeitos, mas possuem falsos positivos e negativos. Use como camada no CI, não como prova definitiva.
Testes adversariais
const inputs = [
'a'.repeat(100) + '!',
'a'.repeat(1000) + '!',
' '.repeat(10000),
];
for (const input of inputs) {
const start = performance.now();
pattern.test(input);
const duration = performance.now() - start;
expect(duration).toBeLessThan(20);
}Benchmarks em CI compartilhado podem oscilar; use limites amplos ou runner dedicado para regressões importantes.
Timeout não interrompe regex
Um setTimeout não consegue interromper código síncrono que bloqueia o event loop. O callback do timeout só executa depois que a regex termina.
Worker Threads
Quando uma regex complexa é inevitável, execute em worker isolado e termine após prazo:
const worker = new Worker(new URL('./regex-worker.js', import.meta.url), {
workerData: { pattern, input },
});
const timer = setTimeout(() => worker.terminate(), 100);
Não envie padrões arbitrários sem limites. Criar worker por request também custa recursos; use pool controlado.
RE2
Motores baseados em RE2 evitam backtracking exponencial, mas não suportam todos os recursos de regex JavaScript, como algumas referências e lookarounds. Avalie compatibilidade e dependência nativa.
Regex em dependências
Vulnerabilidades podem existir em routers, validadores, parsers, markdown e bibliotecas de sanitização. Mantenha dependências atualizadas e revise advisories.
JSON Schema pattern
O keyword pattern pode executar regex sobre entrada externa. Use expressões simples, maxLength e testes de pior caso.
Rotas Express
Parâmetros e rotas podem usar padrões. Não crie uma rota com regex ambígua para caminhos de tamanho ilimitado. Configure limite de URL no proxy.
Logs e busca
Sistemas que permitem filtros regex em logs precisam de sandbox, timeout real no motor ou linguagem limitada. Não execute padrão enviado pelo painel diretamente no processo da API.
Rate limiting
Rate limiting reduz volume, mas uma única entrada pode bloquear o processo. Corrija a expressão e limite tamanho.
Event Loop Utilization
Monitore ELU e event loop delay. Um pico de CPU com poucas requisições pode indicar regex, parsing, compressão ou outro trabalho síncrono.
Profiling
Use --prof, flamegraphs ou Clinic.js. A pilha pode mostrar funções de RegExp ou validação próximas do gargalo.
Resposta e proteção
Rejeite entrada grande antes da regex. Configure limites de body, URL, headers e campos. Não devolva a expressão interna em erros.
Fuzzing
Gere strings longas, quase correspondentes e repetitivas. Fuzzing é especialmente útil para parsers e regras de validação adicionadas por usuários internos.
Revisão de código
Para cada regex nova, pergunte:
- qual é o tamanho máximo da entrada?
- há quantificadores aninhados?
- alternativas se sobrepõem?
- um parser seria melhor?
- o padrão vem do usuário?
- há teste de pior caso?
Erros comuns
- regex complexa para email;
- sem limite de entrada;
- aceitar padrão do usuário;
- achar que setTimeout interrompe;
- confiar somente em scanner;
- ignorar patterns de schemas;
- usar rate limit como correção;
- não medir event loop;
- regex em dependência desatualizada.
Fluxo recomendado
- limite tamanho;
- simplifique o padrão;
- prefira parsers;
- escape busca literal;
- proíba regex arbitrária;
- analise no CI;
- teste entradas adversariais;
- monitore event loop;
- isole casos complexos;
- atualize dependências.
Combine a proteção com JSON Schema, Event Loop Utilization, CPU Profiling, Worker Threads e Rate Limiting.
Consulte a referência da OWASP sobre ReDoS e a documentação de expressões regulares da MDN.



