Regex segura em produção: como evitar lentidão, travamentos e falsos positivos
Publicado em 2026-08-29T20:13:00Z · atualizado em 2026-08-29T19:13:02+00:00
Expressões regulares resolvem em uma linha problemas que exigiriam dezenas de condições: localizar códigos, validar um formato, separar campos ou substituir padrões. O risco aparece quando a linha curta esconde uma busca cara. Uma regex apa
Regex segura em produção: como evitar lentidão, travamentos e falsos positivos
Expressões regulares resolvem em uma linha problemas que exigiriam dezenas de condições: localizar códigos, validar um formato, separar campos ou substituir padrões. O risco aparece quando a linha curta esconde uma busca cara. Uma regex aparentemente inocente pode ficar cada vez mais lenta conforme a entrada cresce, bloquear a interface do navegador ou consumir o processo responsável por atender outras requisições.
Este guia mostra como escrever uma regex segura, previsível e verificável em JavaScript. Você vai aprender a distinguir validação de extração, reconhecer padrões com backtracking perigoso, construir casos de teste, medir tempo e definir limites. Os exemplos podem ser reproduzidos no console do navegador ou no Node.js e conferidos no testador de Regex do IATechNerds. Para preparar entradas, use também o limpador de texto e o contador de palavras e caracteres.
Legenda: quando várias partes da expressão podem consumir os mesmos caracteres, o mecanismo pode testar muitas divisões equivalentes.
O primeiro passo é decidir se você realmente precisa de regex
Regex é adequada quando o problema pode ser descrito por padrões de texto. Ela não é automaticamente a melhor escolha para formatos que já possuem um analisador confiável. JSON deve ser lido com JSON.parse; URLs, com a classe URL; HTML, com DOMParser; datas de negócio, com regras explícitas; CSV, com um parser que entenda aspas e quebras de linha.
Veja um caso clássico:
const url = new URL("https://exemplo.test:8443/pasta?q=abc");
console.log(url.hostname); // exemplo.test
console.log(url.port); // 8443
console.log(url.searchParams.get("q")); // abc
Uma regex para “validar qualquer URL” costuma crescer até reproduzir de forma incompleta um padrão que o navegador já conhece. O mesmo vale para tentar capturar HTML com uma única expressão. Use regex para tarefas delimitadas: localizar todos os IDs no formato PED-12345, confirmar que um campo simples segue uma convenção interna ou normalizar espaços.
Antes de escrever o padrão, formule três listas: entradas que devem passar, entradas que devem falhar e entradas extremas. Sem essas listas, a regex será julgada apenas pelos exemplos felizes que inspiraram sua criação.
Validação, busca e extração pedem desenhos diferentes
Uma validação precisa responder se a entrada inteira obedece ao formato. Por isso, geralmente utiliza âncoras de início e fim:
const codigoPedido = /^PED-[0-9]{5}$/;
codigoPedido.test("PED-12345"); // true
codigoPedido.test("xPED-12345"); // false
codigoPedido.test("PED-12345 antigo"); // false
Uma busca procura o padrão dentro de um texto maior e não deve usar as mesmas âncoras:
const localizarPedidos = /PED-[0-9]{5}/g;
const texto = "Separar PED-12345 e revisar PED-90001.";
console.log([...texto.matchAll(localizarPedidos)].map(m => m[0]));
Já uma extração precisa guardar partes relevantes:
const linha = /^(?<prefixo>[A-Z]{3})-(?<numero>[0-9]{5})$/;
const resultado = linha.exec("PED-12345");
console.log(resultado.groups.prefixo); // PED
console.log(resultado.groups.numero); // 12345
Misturar os três objetivos produz expressões difíceis de manter. Se o objetivo é validar, não adicione .* antes e depois “para garantir”. Se precisa extrair, dê nomes aos grupos. Se quer localizar várias ocorrências, entenda o efeito da flag g sobre lastIndex.
Entenda o que é backtracking e quando ele explode
Muitos mecanismos de regex tentam um caminho e, quando ele falha adiante, voltam para testar outra divisão dos caracteres. Esse retorno é o backtracking. Ele é normal e frequentemente rápido. O problema surge quando o padrão oferece muitas maneiras equivalentes de consumir a mesma sequência.
Examine este exemplo didático:
const perigosa = /^(a+)+$/;
Para uma sequência composta apenas por a, a expressão encontra sucesso. Mas uma entrada com muitos a seguida de ! força o mecanismo a descobrir, no fim, que não existe combinação válida. O grupo externo e o quantificador interno podem redistribuir os mesmos caracteres de muitas formas.
Não execute testes extremos desse tipo em uma aplicação real ou com tamanho ilimitado. Para estudar, use entradas pequenas, aumente gradualmente e interrompa ao perceber crescimento acentuado. O objetivo é reconhecer a estrutura, não travar o ambiente.
Os sinais mais importantes são:
- quantificador aplicado a um grupo que já contém quantificador, como
(a+)+; - alternativas sobrepostas, como
(a|aa)+; - curingas amplos em sequência, como
.*.*; - trechos opcionais repetidos, como
(a?)+; - tentativa de validar estruturas recursivas ou linguagens complexas com uma única regex;
- entrada controlada pelo usuário sem limite de tamanho.
O nome comum para o impacto explorável é ReDoS, negação de serviço por expressão regular. Mesmo sem ataque, dados inesperados podem acionar o pior caso e produzir o mesmo travamento.
Reescreva para reduzir ambiguidades
A primeira estratégia é remover níveis de repetição desnecessários. Se você quer um ou mais a, use:
const simples = /^a+$/;
Se uma alternativa compartilha prefixo, extraia o prefixo comum:
// Mais ambígua
const antes = /^(casa|casaco)$/;
// Intenção mais clara
const depois = /^casa(?:|co)$/;
Nem toda fatoração produz uma diferença relevante de desempenho, mas ela ajuda a tornar os caminhos distinguíveis. Prefira classes específicas a curingas:
// Amplo demais: pode atravessar campos inesperados
const amplo = /^nome:.*;email:.*$/;
// Delimitado: cada valor para no ponto e vírgula
const delimitado = /^nome:[^;]*;email:[^;]*$/;
Defina limites superiores quando o domínio possui limites reais:
const usuario = /^[a-z0-9._-]{3,40}$/i;
Esse padrão comunica uma regra de produto e impede que uma entrada enorme seja tratada como um nome de usuário. Limite não é remendo: é parte do contrato. Se um campo pode ter no máximo 40 caracteres na aplicação, a validação e a interface devem concordar com isso.
Alguns mecanismos oferecem grupos atômicos ou quantificadores possessivos, que impedem certos retornos. JavaScript não disponibiliza todas as construções presentes em outras linguagens. Portanto, não copie uma correção de Java, PCRE ou .NET presumindo que funcionará no navegador. Confirme a sintaxe e a compatibilidade do mecanismo utilizado.
Trate Unicode como requisito, não como detalhe
Em JavaScript, strings usam UTF-16. Sem modo Unicode, determinados símbolos podem ser interpretados como duas unidades. Além disso, \w é essencialmente voltado ao conjunto ASCII e não representa todas as letras usadas em nomes reais.
Para reconhecer letras de diferentes alfabetos, use propriedades Unicode quando o suporte do projeto permitir:
const nomeHumano = /^[\p{L}\p{M} .'-]{1,80}$/u;
console.log(nomeHumano.test("João da Silva"));
console.log(nomeHumano.test("Łukasz Nowak"));
\p{L} representa caracteres com propriedade de letra; \p{M} inclui marcas combinantes. Isso não transforma a expressão em uma regra universal para nomes. O ponto é evitar uma validação que rejeita silenciosamente pessoas porque o autor testou apenas A-Z.
As flags u e v ativam modos conscientes de Unicode, mas não são iguais e não podem ser combinadas. O modo v amplia operações em classes de caracteres e precisa ser conferido na matriz de navegadores do projeto. Se o sistema suporta ambientes antigos, teste antes de adotar.
Normalização também importa. O mesmo caractere visual pode ser representado como um ponto de código ou como letra mais marca combinante. Quando o domínio permitir, normalize antes de comparar:
const normalizado = entrada.normalize("NFC");
Não normalize identificadores opacos, assinaturas ou hashes. Nesses casos, qualquer alteração de bytes pode mudar o valor.
Escape entradas dinâmicas antes de montar uma expressão
Quando um termo digitado pelo usuário entra no construtor RegExp, seus caracteres especiais ganham significado. Pesquisar literalmente por a+b com new RegExp("a+b") procura um a seguido de um ou mais b, não o texto com sinal de mais.
Use uma função de escape literal:
function escaparRegex(texto) {
return texto.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
const termo = "a+b";
const busca = new RegExp(escaparRegex(termo), "giu");
console.log("Itens: a+b e ab".match(busca)); // ["a+b"]
Esse escape resolve a interpretação de metacaracteres, mas não elimina todos os riscos. A entrada e o texto pesquisado continuam precisando de limites. Uma busca global em um documento gigantesco pode ser cara mesmo com padrão literal. Defina tamanho máximo, paginação ou processamento em partes.
Evite permitir que usuários comuns forneçam a própria expressão diretamente em funcionalidades públicas. Se isso for requisito de uma ferramenta técnica, isole a execução, limite entrada e tempo e explique que padrões ruins podem ser interrompidos.
Crie um conjunto de testes com positivos, negativos e adversariais
Uma regex não está pronta porque passou em três exemplos. Transforme o contrato em uma tabela:
const regra = /^PED-[0-9]{5}$/;
const casos = [
["PED-12345", true, "formato válido"],
["ped-12345", false, "minúsculas não permitidas"],
["PED-1234", false, "quatro dígitos"],
["PED-123456", false, "seis dígitos"],
[" PED-12345", false, "espaço inicial"],
["PED-12345", false, "dígitos de largura cheia"],
["PED-12345\n", false, "quebra final"]
];
for (const [entrada, esperado, motivo] of casos) {
const obtido = regra.test(entrada);
console.assert(obtido === esperado, { entrada, esperado, obtido, motivo });
}
Inclua string vazia, tamanho máximo, um caractere acima do máximo, quebras de linha, acentos, emojis, espaços invisíveis e uma entrada longa. Para extração global, confira quantidade, conteúdo e índices. Para substituição, verifique também o resultado quando não há correspondência.
Regex com g ou y pode manter estado em lastIndex. Reutilizar o mesmo objeto em chamadas sucessivas de test() gera resultados que parecem alternar:
const estado = /abc/g;
console.log(estado.test("abc")); // true
console.log(estado.test("abc")); // false: lastIndex mudou
Se sua função só precisa de um booleano independente, remova g, crie um novo objeto por validação ou redefina lastIndex conscientemente.
Meça desempenho sem transformar benchmark em superstição
Use performance.now() para comparar ordens de grandeza em entradas controladas:
function medir(regex, entrada, repeticoes = 1000) {
const inicio = performance.now();
for (let i = 0; i < repeticoes; i++) {
regex.lastIndex = 0;
regex.test(entrada);
}
return performance.now() - inicio;
}
const regra = /^PED-[0-9]{5}$/;
console.log(medir(regra, "PED-12345"));
console.log(medir(regra, "PED-1234X"));
Rode entradas válidas e inválidas, porque o pior caso muitas vezes acontece na falha tardia. Aumente o tamanho em etapas e observe a curva. Se dobrar a entrada multiplica o tempo de maneira extrema, investigue ambiguidade. Não use um único número como garantia universal: navegador, versão do motor, máquina e aquecimento do compilador influenciam medições pequenas.
Em uma interface, orçamento de tempo importa mais do que recorde de benchmark. Uma validação executada a cada tecla deve ser muito barata. Trabalhos maiores podem ocorrer após pausa de digitação, em Web Worker ou no servidor, sempre com limites. No backend, monitore tempo por rota e tamanho de entrada para encontrar regressões reais.
Legenda: segurança vem do processo completo; não existe uma flag que conserte qualquer padrão.
Coloque limites fora da regex
Mesmo uma expressão bem desenhada não deveria receber entrada ilimitada. Valide tamanho antes:
function validarCodigo(valor) {
if (typeof valor !== "string") return false;
if (valor.length > 32) return false;
return /^PED-[0-9]{5}$/.test(valor);
}
No servidor, limite também o tamanho total do corpo e a quantidade de campos. No navegador, não execute regex pesada no thread principal sobre arquivos enormes. Se o usuário precisa analisar um arquivo, processe em blocos e ofereça cancelamento.
Timeout em JavaScript exige arquitetura. Uma chamada síncrona a regex.test() não pode ser interrompida pelo mesmo thread enquanto está travada. Para padrões fornecidos pelo usuário ou análises potencialmente pesadas, execute em um Worker que possa ser terminado pelo controlador depois do prazo. Isso é mais robusto do que iniciar um setTimeout ao lado da regex: o temporizador também fica bloqueado enquanto a execução síncrona não devolve o controle.
Registre métricas sem registrar o texto sensível. Tamanho da entrada, identificador da regra, tempo, sucesso ou falha e versão da expressão costumam bastar. Não grave conteúdo pessoal apenas para medir regex.
Revise falsos positivos com o mesmo cuidado que travamentos
Desempenho não é a única dimensão de segurança. Uma regex permissiva pode aceitar dados incorretos e empurrar o problema para etapas posteriores. Uma expressão restritiva demais pode bloquear nomes, endereços ou códigos legítimos.
Evite usar regex como única validação de regras semânticas. Um padrão pode confirmar que a data tem formato AAAA-MM-DD, mas não que 2026-02-31 exista. Pode confirmar a forma de um e-mail interno, mas não que a caixa exista. Pode verificar dígitos em um documento, mas não autorização, titularidade ou situação cadastral.
Separe camadas:
- limite de tipo e tamanho;
- validação sintática simples;
- parsing para estrutura adequada;
- regras semânticas;
- autorização e regras de negócio.
Quando a regex serve apenas para orientar a interface, a mensagem deve explicar a regra: “use cinco dígitos após PED-” é melhor que “valor inválido”. Mensagens precisas diminuem tentativas aleatórias e melhoram os próprios dados que chegam ao sistema.
Checklist de aprovação antes de colocar a regex em produção
Revise estes pontos:
- O problema realmente exige regex ou existe parser nativo?
- A expressão valida a entrada inteira quando deveria?
- Há quantificadores aninhados ou alternativas sobrepostas?
- Curingas podem ser substituídos por delimitadores específicos?
- Tipo e tamanho são limitados antes da execução?
- Entradas dinâmicas são escapadas?
- Unicode e normalização foram considerados?
- Existem casos válidos, inválidos, limites e adversariais?
- O pior caso inválido foi medido com tamanho crescente?
- Flags globais ou sticky estão causando estado inesperado?
- A execução pesada está fora do thread principal?
- Métricas permitem detectar aumento de tempo sem guardar dados sensíveis?
- A mensagem ao usuário explica como corrigir?
Uma regex segura costuma ser menos impressionante visualmente. Ela faz uma tarefa estreita, usa limites explícitos e vem cercada de testes. Esse é um bom sinal. O objetivo não é escrever a expressão mais curta, mas tornar seu custo e seu comportamento previsíveis.
Documentação primária e referências técnicas
- ECMAScript Language Specification — RegExp Objects
- MDN — Regular expressions em JavaScript
- MDN — RegExp.prototype.test e o estado de lastIndex
- MDN — modo Unicode em RegExp
- OWASP — Regular expression Denial of Service
Nota sobre a produção deste conteúdo
Rascunho inédito produzido com apoio de IA generativa após comparação com as pautas existentes do IATechNerds. A explicação usa o comportamento documentado pelo padrão ECMAScript e pela MDN, com uma referência complementar da OWASP para o risco de ReDoS. Os exemplos foram escritos para fins educativos e precisam de revisão técnica humana em diferentes navegadores antes da publicação.
Camada extra: como tomar uma decisão melhor neste cenário
Em uso real, Regex segura em produção: como evitar lentidão, travamentos e falsos positivos costuma envolver mais de uma camada. O ganho de qualidade aparece quando cada camada tem uma pergunta de verificação antes de qualquer mudança definitiva.
Expressões regulares resolvem em uma linha problemas que exigiriam dezenas de condições: localizar códigos, validar um formato, separar campos ou substituir padrões. O risco aparece quando a linha curta esconde uma busca cara. Uma regex apa A ideia central pode ser testada com quatro perguntas: o que foi observado, qual hipótese explica o sintoma, qual mudança é reversível e qual evidência prova que funcionou. Esse encadeamento evita que uma coincidência seja confundida com causa e torna o procedimento repetível por outra pessoa.
Separe exposição, probabilidade e impacto
Em segurança e privacidade, um alerta isolado não mede o risco inteiro. Para Regex, JavaScript, segurança, performance, registre primeiro o que está exposto: conta, arquivo, dispositivo, sessão, metadado ou autorização. Depois avalie como o abuso poderia acontecer e o que seria perdido se acontecesse. Essa separação ajuda a priorizar uma credencial comprometida acima de um aviso meramente visual e, ao mesmo tempo, evita transformar qualquer comportamento incomum em prova de ataque.
Prefira respostas reversíveis no primeiro minuto: interromper uma transação, revogar uma sessão, remover uma permissão, confirmar a identidade por outro canal ou trabalhar em uma cópia. Só depois faça mudanças permanentes. O objetivo não é reagir com medo, mas reduzir a janela de exposição enquanto você coleta evidências.
Mini matriz de decisão
| Sinal | O que verificar | Ação inicial |
|---|---|---|
| Pedido inesperado ou comportamento fora do padrão | Identidade, domínio, origem e contexto | Não confirmar pela mesma mensagem |
| Permissão ou acesso excessivo | Necessidade real e escopo | Reduzir ao mínimo necessário |
| Dado sensível envolvido | Onde será processado e armazenado | Preferir fluxo local ou serviço confiável |
O teste que evita falsa sensação de segurança
Depois da correção, repita o cenário original de forma controlada. Verifique se a conta continua funcional, se a permissão removida não reapareceu, se o arquivo não contém a informação que deveria ter sido removida e se o canal alternativo de confirmação realmente funciona. Segurança sem teste de recuperação é apenas configuração; a validação transforma a configuração em controle.
Checklist de fechamento
- Registre o estado inicial antes de alterar qualquer coisa.
- Faça uma mudança por rodada e anote o efeito.
- Teste um caso normal, um caso-limite e o cenário que originalmente falhou.
- Confirme que a solução continua válida depois de reiniciar, reabrir ou repetir o fluxo.
- Guarde uma forma de voltar ao estado anterior quando a alteração for destrutiva.
Esse método acrescenta profundidade sem transformar o artigo em teoria abstrata: o leitor entende por que cada passo existe e como reconhecer quando a situação exige uma decisão diferente.
