Você escreve uma regex, ela passa nos testes unitários voando, e de repente uma entrada de usuário em produção crava o servidor em 100% de CPU — todas as requisições entram na fila e morrem. Reinicia o serviço, os logs não mostram erros. Na semana seguinte outra entrada dispara o mesmo travamento, e ops só pode culpar as “entradas esquisitas do usuário.”

Isso é backtracking catastrófico em regex. Não é bug no seu código — é a estrutura da sua regex que fica exponencialmente lenta com certas entradas. Você não reproduz em local porque os testes usam entradas “normais”; o que dispara são entradas “quase-mas-não-bem” no limite.

Por que regexes travam: backtracking exponencial

A maioria dos motores de regex (JavaScript, Python, Java, PHP, Ruby, Perl) usa NFA baseado em backtracking. Quando encontram (\d+)* — “quantificadores aninhados” — o motor não sabe quantas vezes casar, então tenta por força bruta. Casar (\d+)* com 30 caracteres como 123...0 exige 2³⁰ = 1 bilhão de particionamentos — cada um testado contra o resto da entrada, todos falhando antes do motor retornar false.

Ponto-chave: cada caractere adicional na entrada dobra o número de tentativas. Isso é complexidade exponencial. 30 caracteres = 1 bilhão de tentativas (~30 segundos numa CPU moderna). 60 caracteres = 10¹⁸ tentativas (~30 anos).

Boa notícia: existe um padrão para identificar e corrigir isso. O testador de regex do Piick mostra contagens em tempo real, então você consegue detectar “essa regex vai explodir em entradas longas” antes de chegar em produção.

3 casos reais de produção

Caso 1: regex de email derruba o login

Um site de e-commerce usa este validador de email:

^([a-zA-Z0-9_.-])+@([a-zA-Z0-9_.-])+.([a-zA-Z]{2,4})+$

Parece certo. O problema é o + no fim — “qualquer número de segmentos de TLD.” Quando um usuário coloca um email legal mas incomumente longo como john.doe@a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p, o motor começa a particionar o TLD exponencialmente e crava a CPU em segundos.

Correção: tire o + final e case com um único segmento de TLD.

Caso 2: regex de parse de logs mata o cluster ELK

Uma equipe usa esta regex para parsear logs do nginx:

^(\d+.\d+.\d+.\d+) - - [([^]]+)] ”([A-Z]+) (.+?) HTTP/[\d.]+” (\d+) (\d+)

Em logs padrão roda em milissegundos. Um dia ops importa logs históricos de 2008 (formato diferente, espaços extras), o padrão .+? dispara backtracking, cada linha leva 5 segundos, todo o pipeline ELK trava, Kibana estoura timeout em todos os dashboards.

Causa raiz: .+? é seguido pelo método HTTP, mas o método dentro da aspas não está ancorado — o motor não sabe “onde parar.”

Correção: troque .+? por [^\s]+ — URLs nunca têm espaços, então o range de match colapsa para um tamanho razoável.

Caso 3: regex de força de senha trava o cadastro

O formulário de cadastro usa:

^(?=.[a-z])(?=.[A-Z])(?=.\d)(?=.[@$!%*?&]).{8,}$

Quatro lookaheads positivos, cada um varrendo a entrada inteira. Uma senha de 1000 caracteres (teste de paste attack) dispara 4 × 1000 varreduras — lento mas não fatal.

A variante realmente mortal:

^(?=.[a-z])(?=.[A-Z])(?=.\d).[@$!%*?&].*$

Colapsa os quatro lookaheads em “qualquer coisa + caractere especial + qualquer coisa.” Um usuário digita aaaaaaaaaaaa! — muitos a e um !. O motor tenta cortar em cada posição de a (“corto aqui, depois caso !”), exponencialmente.

Correção: use classes de caracteres em vez de .*, ou não use regex para força de senha — use uma ferramenta.

Como detectar uma regex que vai explodir

Não dá pra ver a olho nu — ^(\w+\s?)*$ parece inocente, mas 30 caracteres travam o motor V8. Três métodos:

Método 1: teste entradas longas no testador de regex do Piick

O testador de regex do Piick mostra contagens. Se você ver 100.000+ matches, é quase certo que é backtracking catastrófico. Uma regex normal produz matches proporcionais ao tamanho da entrada.

Passos: cole sua regex, insira uma string longa que propositalmente não casa (como aaaa...!), observe:

  • Retorna em segundos → seguro
  • Trava 5+ segundos → alto risco
  • Congela a aba → feche imediatamente

Método 2: análise estática com ReScue

ReScue (Universidade de Nanjing, paper PLDI 2018) analisa estaticamente a complexidade de pior caso de uma regex. rescue analyze 'your-pattern-here' retorna SAFE / VULNERABLE / UNKNOWN. UNKNOWN não significa seguro — significa só que a ferramenta não conseguiu decidir; pode travar em tempo de execução.

Método 3: troque de motor e adicione timeouts

Independente de quão segura uma regex pareça, código de produção deveria usar motores de regex de tempo linear — RE2 do Google (require('re2') em Node.js, regexp padrão do Go, pyre2 em Python). RE2 tem complexidade estrutural O(entrada × tamanho_regex), então nunca explode. Adicione um timeout em worker thread como cinto e suspensórios: 100 ms e mata.

2 formas de corrigir

Método 1: reescreva a regex, reduza combinações

Ideia central: troque “limites difusos” por “limites explícitos.”

// perigosa ^(\w+\s?)$ // segura: espaços são obrigatórios ^(\w+\s)\w*$

A primeira permite “palavras quaisquer, cada uma com espaço opcional.” A segunda exige “[palavra + espaço] qualquer número de vezes”, tornando os espaços obrigatórios. Mesmo resultado casado, mas a primeira explora infinitas particionamentos e a segunda só uma.

Regra prática: quando ver (\w+)*, (.+)*, (.*)* — “quantificador dentro de grupo, com outro quantificador fora” — alarme. 99% são armadilhas de backtracking.

Método 2: grupos atômicos ou quantificadores possessivos

JavaScript, Java e Python não suportam nativamente grupos atômicos (?>...), mas dá pra simular com lookahead + backreference:

// simula grupo atômico: diz ao motor “não faça backtracking aqui” (?=(\w+))\1

O lookahead (?=(\w+)) vê se \w+ casa a partir daqui, mete o resultado num grupo de captura; depois \1 referencia e proíbe o motor de cortar dentro.

A solução de engenharia mais ampla: troque de motor (Método 3 acima). O trade-off: RE2 não suporta backreferences nem a maioria dos lookarounds. Validação de senha, parse de URL — totalmente compatíveis. Parse de logs que precisa de referências \1 — ainda tem que reescrever a regex.

Como o Piick te ajuda a evitar isso

O testador de regex do Piick é mais que um verificador de matches — a contagem de matches é seu sistema de alerta precoce: cole a regex, insira uma string longa que propositalmente não casa (como aaaa...z), observe a contagem e o tempo de resposta. Mesma magnitude do tamanho da entrada = seguro. Exponencial = alto risco. Faça isso em cada regex que escrever — cinco segundos agora economizam uma queda P0 depois.


Backtracking catastrófico é a bomba invisível do mundo dev — testes passam, produção morre, você não consegue reproduzir, e a culpa é da “entrada esquisita do usuário.” Hoje, identifique e corrija. Abra o testador de regex do Piick, passe suas 5 regexes mais longas pelo teste de alerta acima e pule a página de plantão do próximo trimestre.