Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

ReDoS no Node.js: Como Prevenir

Atualizado em: 30 de agosto de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Prevenir ReDoS no Node.js é essencial quando a aplicação usa expressões regulares em dados controlados pelo usuário. ReDoS significa Regular Expression Denial of Service e acontece quando uma regex exige tempo excessivo para decidir se uma entrada corresponde ao padrão.

O problema costuma aparecer em expressões com quantificadores aninhados, alternativas ambíguas e backtracking catastrófico. Como JavaScript executa a regex no mesmo processo, uma única entrada maliciosa pode bloquear o event loop e atrasar todas as requisições atendidas pela instância.

Neste guia, você aprenderá como identificar padrões perigosos, substituir regex complexas, limitar input, usar analisadores seguros, aplicar timeouts por isolamento, testar casos adversariais e monitorar o event loop.

O que é ReDoS?

ReDoS explora o custo computacional de uma expressão regular. A referência da OWASP sobre ReDoS explica o ataque. A documentação de expressões regulares da MDN apresenta a sintaxe usada no JavaScript.

Backtracking

Motores de regex podem tentar diferentes caminhos quando uma correspondência falha. Em padrões ambíguos, a quantidade de combinações cresce rapidamente.

Exemplo perigoso

const pattern = /^(a+)+$/;

pattern.test('aaaaaaaaaaaaaaaaaaaaaaaa!');

O padrão possui um quantificador dentro de outro. Quando o caractere final não corresponde, o motor pode testar muitas divisões possíveis da sequência de letras.

Outro padrão ambíguo

/^(a|aa)+$/

As alternativas se sobrepõem. A mesma sequência pode ser dividida de muitas maneiras.

Impacto no event loop

Uma regex síncrona bloqueia a thread JavaScript. Enquanto ela executa, timers, conexões e outras requisições aguardam.

Para entender esse comportamento, consulte Event Loop no Node.js.

Entrada controlada pelo usuário

Os principais riscos aparecem em:

  • validação de e-mail e URL;
  • filtros de busca;
  • parsers de logs;
  • rotas com padrões dinâmicos;
  • templates;
  • sanitização de HTML;
  • regras configuráveis por clientes.

Regex dinâmica

const regex = new RegExp(req.query.pattern);
const matched = regex.test(documentText);

Permitir que o usuário forneça o padrão completo é ainda mais perigoso. Além de ReDoS, o padrão pode consumir memória e produzir resultados inesperados.

Não aceite regex arbitrária

Prefira filtros estruturados, operadores predefinidos e busca textual do banco.

{
  "field": "name",
  "operator": "contains",
  "value": "ana"
}

A aplicação converte operadores permitidos em uma consulta segura.

Limite de tamanho

Mesmo uma regex razoável pode ficar cara em texto enorme. Limite o input antes da avaliação:

if (Buffer.byteLength(input, 'utf8') > 10_000) {
  throw new ValidationError('Entrada muito grande');
}

Limite do padrão

Se uma funcionalidade realmente aceita padrões, limite tamanho, recursos e quantidade de grupos. Ainda assim, uma análise completa de segurança é difícil.

Evite quantificadores aninhados

Padrões como:

(a+)+
(.*)*
(\w*)+

merecem revisão. Reescreva para uma única repetição quando possível.

Alternativas sobrepostas

Evite:

(a|aa)+
(\d|\d\d)*

Prefira uma forma não ambígua, como a+ ou um parser explícito.

Wildcards amplos

Padrões com .* entre delimitadores repetidos podem causar muito backtracking:

^.*BEGIN.*END.*$

Use limites mais específicos e análise em etapas.

Validação de e-mail

Não tente implementar toda a especificação de e-mail com uma regex gigantesca. Use uma validação simples de formato, limite de tamanho e, quando necessário, confirmação por e-mail.

Validação de URL

Use a classe URL, não uma regex complexa:

let parsed;
try {
  parsed = new URL(value);
} catch {
  throw new ValidationError('URL inválida');
}

Para URLs externas, combine com as defesas de SSRF no Node.js.

Validação de números

Converta e valide tipos:

const value = Number(input);

if (!Number.isFinite(value)) {
  throw new ValidationError('Número inválido');
}

Não use regex para tudo.

Parsers específicos

Datas, URLs, JSON e identificadores possuem parsers ou validadores dedicados. Eles normalmente são mais legíveis e previsíveis que padrões extensos.

Regex simples

Um padrão ancorado e limitado pode ser adequado:

/^[a-z0-9_-]{1,64}$/i

O quantificador possui limite e a classe não é ambígua.

Anchors

^ e $ reduzem buscas em posições diferentes quando a intenção é validar a string inteira. Eles não corrigem backtracking interno, mas evitam trabalho desnecessário.

Limites explícitos

Prefira:

/^[a-z]{1,100}$/

em vez de:

/^[a-z]+$/

quando existe um limite de negócio.

Flags

Entenda o impacto de g, m, s e u. Uma regex global mantém lastIndex, o que pode causar bugs quando a instância é reutilizada.

Unicode

Caracteres Unicode, combinações e pares substitutos podem aumentar complexidade. Normalize apenas quando o requisito é claro e limite o tamanho em bytes e caracteres.

Static analysis

Ferramentas como safe-regex e scanners SAST podem identificar padrões suspeitos. Elas geram falsos positivos e negativos, portanto precisam de revisão humana.

Dependências

Uma biblioteca pode usar regex vulnerável internamente. Mantenha lockfile e acompanhe advisories. O artigo sobre segurança de dependências será publicado nesta fila.

Testes adversariais

Para cada regex relevante, teste entradas que quase correspondem:

const input = 'a'.repeat(50_000) + '!';

O pior caso frequentemente termina com um caractere inválido.

Benchmark

const startedAt = performance.now();
const result = pattern.test(input);
const durationMs = performance.now() - startedAt;

assert.ok(durationMs < 50);

Use limites adequados ao ambiente de teste e execute com tamanhos crescentes.

Curva de crescimento

Meça 100, 1.000, 10.000 e 100.000 caracteres. Uma duração que cresce muito mais rápido que o tamanho indica risco.

Timeout não existe na regex nativa

JavaScript não fornece um timeout direto para RegExp.test(). Um setTimeout não interrompe a operação porque o event loop está bloqueado.

Worker Threads

Para padrões não confiáveis, execute em worker isolado e encerre após o prazo:

const worker = new Worker('./regex-worker.js', {
  workerData: { pattern, input }
});

const timer = setTimeout(() => {
  worker.terminate();
}, 100);

Isso limita o bloqueio ao worker, mas ainda exige controle de memória, quantidade de workers e input.

Consulte Worker Threads no Node.js.

Processo separado

Um processo filho oferece isolamento mais forte e pode receber limites de CPU e memória pelo sistema operacional.

Fila de trabalho

Não crie um worker por requisição sem limite. Use pool e rejeite excesso de carga.

RE2

Motores baseados em RE2 evitam backtracking catastrófico ao limitar recursos da linguagem. Eles não suportam todos os recursos de regex, como alguns backreferences e lookarounds.

Avalie bibliotecas mantidas e compatibilidade antes de substituir o motor.

Backreferences

Padrões com referências a grupos podem ser difíceis de analisar e não são suportados por motores lineares. Considere um parser.

Rate limiting

Limite endpoints que executam busca ou validação cara. Consulte Rate Limiting no Node.js.

Timeout da requisição

Timeout HTTP evita conexões infinitas, mas não interrompe a regex já executando. Ele deve ser combinado com eliminação do padrão ou isolamento.

Event loop delay

Monitore atraso do event loop com perf_hooks:

const histogram = monitorEventLoopDelay();
histogram.enable();

Veja Performance Hooks no Node.js.

CPU

ReDoS costuma causar CPU alta, latência crescente e poucas requisições concluídas. Configure alertas que correlacionem esses sinais.

Logs

Registre rota, ID da regra, tamanho do input e duração. Não registre o texto completo quando pode conter dados pessoais ou payload enorme.

Consulte Logs com Pino no Node.js.

Auditoria

Alterações em padrões configuráveis devem registrar autor, versão anterior, nova versão e resultado da validação.

Feature flags

Uma regex nova pode ser habilitada gradualmente e desativada rapidamente se métricas piorarem.

Code review

Procure por:

  • new RegExp com input externo;
  • quantificadores aninhados;
  • alternativas sobrepostas;
  • .* repetido;
  • regex extensa sem testes;
  • uso em texto sem limite;
  • padrões de dependências antigas.

Teste de rota

test('rejeita entrada acima do limite', async () => {
  const response = await request(app)
    .post('/validate')
    .send({ value: 'a'.repeat(100_000) });

  assert.equal(response.status, 400);
});

Teste de concorrência

Envie múltiplas entradas adversariais e confirme que o serviço mantém health checks e requisições normais.

Teste de worker

Use um padrão propositalmente caro, confirme término do worker e liberação dos recursos.

Teste de regressão

Quando uma regex vulnerável for corrigida, mantenha a entrada que causava lentidão como fixture permanente.

Erros comuns

  • Regex gigante para e-mail: complexidade desnecessária.
  • Input sem limite: custo cresce indefinidamente.
  • setTimeout ao redor da regex: não interrompe a execução.
  • Confiar só em scanner: padrões perigosos passam.
  • Regex dinâmica: usuário controla o algoritmo.
  • Isolar sem limitar workers: ataque vira exaustão de processos.
  • Ignorar dependências: vulnerabilidade continua transitiva.

Boas práticas

  • Prefira parsers e validação estruturada.
  • Limite tamanho de input.
  • Evite quantificadores aninhados.
  • Evite alternativas ambíguas.
  • Use limites explícitos.
  • Teste quase-correspondências.
  • Monitore event loop e CPU.
  • Isole padrões não confiáveis.
  • Atualize dependências.
  • Mantenha testes de regressão.

Conclusão

Prevenir ReDoS no Node.js exige avaliar o custo da regex, não apenas se ela retorna o resultado correto. Padrões ambíguos e entradas quase válidas podem bloquear o event loop por tempo suficiente para derrubar a instância.

Validação estruturada, limites, regex simples, testes adversariais e isolamento formam uma defesa efetiva. Quando o serviço monitora event loop e rejeita trabalho não confiável antes de executá-lo, expressões regulares deixam de ser uma porta silenciosa para negação de serviço.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita