# Cloudflare 2019: O Regex que Derrubou uma Rede Global

Em 2 de julho de 2019, uma única regra de WAF contendo um regex com backtracking catastrófico saturou 100% da CPU em todos os servidores da Cloudflare globalmente, derrubando serviços por aproximadamente 27 minutos. O incidente expôs falhas críticas no processo de deploy de regras, na ausência de proteção contra complexidade algorítmica e na insuficiência do pipeline de testes para detectar padrões patológicos.

- URL: https://fernando.moretes.com/studies/cloudflare-regex-2019

- Markdown: https://fernando.moretes.com/studies/cloudflare-regex-2019/study.md?lang=pt

- Type: Post-mortem

- Company: Cloudflare

- Domain: Edge/WAF

- Date: 2019-07-02

- Tags: postmortem, cloudflare, waf, regex, catastrophic-backtracking, edge, cpu-saturation, incident

- Reading time: 9 min

---

## Fatos do Incidente

- **Empresa / Sistema:** Cloudflare — Rede de borda global (CDN, WAF, DDoS protection)
- **Data:** 2 de julho de 2019
- **Duração:** ~27 minutos de interrupção total; rollback completado às 14h52 UTC
- **Impacto:** Queda de ~80% do tráfego processado pela Cloudflare; 100% de utilização de CPU em todos os PoPs globais
- **Componente falho:** Regra WAF XSS (número 100137) com regex de backtracking catastrófico
- **Engine de WAF:** Lua WAF rodando sobre NGINX / OpenResty
- **Mecanismo de deploy:** Deploy global simultâneo sem rollout gradual (sem canary/staged rollout)
- **Escala da rede:** Presença em mais de 193 cidades / PoPs na época do incidente
- **Causa raiz:** Regex com backtracking catastrófico consumindo CPU ilimitada por requisição HTTP

Uma expressão regular mal construída — 18 tokens, um grupo de captura com quantificador aninhado — foi suficiente para paralisar a maior rede de borda do mundo por 27 minutos. Não foi um ataque. Foi um deploy de regra de WAF que passou por revisão de código, testes automatizados e staging, e ainda assim chegou à produção global com um padrão algoritmicamente explosivo. Este post-mortem examina o que aconteceu, por que os controles existentes falharam e o que a Cloudflare — e qualquer equipe que opera infraestrutura crítica — deveria ter feito de forma diferente.

## O que aconteceu

Em 2 de julho de 2019, às 13h42 UTC, a Cloudflare realizou um deploy de novas regras para seu WAF gerenciado. Entre as regras estava a 100137, destinada a detectar ataques XSS. O regex central da regra era:

```
(?:(?:"|'|\]|\[|\\|(?:nan|infinity|true|false|null|undefined|symbol|math)|\`|\-|\+)+[)]*;?((?:\s|-|~|!|{}|\|\||&&)*(?:[0-9]{0,4}d?(?:x|xx|xxx|xxxx)?)(?:;|\\|,|\+|\-|\*|\/|%|&|\||\^|<|>|=|!)))+
```

O problema está no grupo `((?:\s|-|~|!|{}|\|\||&&)*)` combinado com o quantificador externo `+`. Esse padrão cria o que a teoria da computação chama de **backtracking catastrófico**: para uma string que não casa com o padrão mas que é longa o suficiente, o motor de regex tenta exponencialmente mais combinações antes de desistir. O tempo de execução deixa de ser O(n) e passa a ser O(2ⁿ) em relação ao comprimento da entrada.

A Cloudflare utilizava a engine de regex do Lua (baseada em PCRE — Perl Compatible Regular Expressions), que não possui proteção nativa contra esse comportamento. Ao contrário de engines baseadas em autômatos finitos (como RE2 ou Rust's `regex` crate), o PCRE realiza backtracking sem limite de tempo por padrão.

Cada requisição HTTP processada pelo WAF passou a consumir 100% de um core de CPU pelo tempo que durasse a tentativa de matching. Com tráfego real chegando a centenas de milhares de requisições por segundo em cada PoP, todos os cores de todos os servidores em todos os data centers foram saturados quase instantaneamente. O sistema de proteção contra DDoS da própria Cloudflare — que depende dos mesmos processos NGINX — parou de funcionar. O tráfego legítimo foi descartado junto com o malicioso.

## Linha do Tempo

1. **13h42 UTC — Deploy iniciado** — Regra WAF 100137 é publicada globalmente via pipeline de deploy padrão. Nenhum staged rollout. A regra vai para todos os PoPs simultaneamente.

2. **13h42 UTC — CPU satura globalmente** — Imediatamente após o deploy, a CPU em todos os servidores da Cloudflare atinge 100%. O tráfego HTTP começa a ser descartado. Alertas disparam nos sistemas de monitoramento.

3. **13h47 UTC — Primeira resposta interna** — Engenheiros de plantão são acionados. A correlação inicial aponta para o deploy de WAF como causa provável, mas o diagnóstico preciso ainda não foi confirmado.

4. **~14h00 UTC — Diagnóstico confirmado** — A equipe identifica a regra 100137 como a fonte do problema. Análise do regex revela o padrão de backtracking catastrófico. Decisão de rollback é tomada.

5. **14h52 UTC — Rollback completo** — A regra problemática é revertida em todos os PoPs. A CPU volta ao normal. O tráfego é restaurado. Duração total da interrupção: aproximadamente 27 minutos.

6. **Horas seguintes — Análise forense** — A equipe realiza análise detalhada do regex, documenta o mecanismo de backtracking, e começa a desenhar as mudanças de processo e tecnologia necessárias para evitar recorrência.

## Fluxo de Falha: Deploy de Regra WAF → Saturação Global de CPU

O diagrama reconstrói o caminho percorrido pela regra defeituosa desde o pipeline de deploy até o impacto em cada PoP global, e como o backtracking catastrófico se propagou para saturar toda a capacidade de processamento.

### 🛠️ Pipeline de Deploy / Deploy Pipeline

- Engineer Rule Author (user)
- WAF Rules Repo Git + CI (ci)
- Test Suite (unit + staging) (ci)
- Global Deploy (simultaneous) (ci)

### 🌐 Edge PoP (replicado em 193+ cidades) / Edge PoP (replicated across 193+ citie

- NGINX / OpenResty HTTP Worker (edge)
- Lua WAF Engine (PCRE regex) (security)
- CPU Core (saturated 100%) (compute)
- DDoS Protection (NGINX-dependent) (security)

### 👤 Usuário Final / End User

- HTTP Request (legitimate traffic) (user)
- Customer Origin Server (external)

### Fluxos

- eng -> repo: commit regra 100137
- repo -> test: CI executa testes
- test -> deploy: testes passam ✓ (falso negativo)
- deploy -> nginx: deploy global simultâneo
- user -> nginx: requisição HTTP
- nginx -> lua_waf: inspeciona cada requisição
- lua_waf -> cpu: backtracking O(2ⁿ) por req
- cpu -> ddos: sem CPU disponível
- cpu -> nginx: workers bloqueados
- nginx -> origin: tráfego descartado / 502

> **Causa Raiz: Backtracking Catastrófico + Deploy Global Atômico:** A causa raiz técnica é o regex `((?:\s|-|~|!|{}|\|\||&&)*)` com quantificador externo `+`, criando ambiguidade de matching que força o motor PCRE a explorar exponencialmente mais caminhos à medida que a string de entrada cresce. Para uma string de 30 caracteres que não casa, o número de tentativas pode ultrapassar bilhões. A causa raiz sistêmica é a combinação de três ausências: (1) nenhuma análise estática de complexidade de regex no pipeline de CI; (2) nenhum timeout ou limite de CPU por operação de regex na engine Lua/PCRE; (3) nenhum mecanismo de staged rollout que teria limitado o blast radius ao primeiro PoP afetado.

## Por que os Controles Existentes Falharam

Este incidente é pedagogicamente valioso precisamente porque a Cloudflare **tinha** controles de qualidade. A regra passou por revisão de código humana. Passou por uma suíte de testes automatizados. Passou por um ambiente de staging. E ainda assim chegou à produção global com um padrão catastrófico. Por quê?

**Revisão de código humana** é eficaz para lógica de negócio, mas detectar backtracking catastrófico em regex requer conhecimento especializado em teoria de autômatos que não é universalmente distribuído entre engenheiros. O regex parecia razoável à inspeção visual — ele cobria os casos de XSS pretendidos nos testes de exemplo.

**Testes automatizados** validaram que a regra *funcionava* — que ela detectava os payloads XSS para os quais foi projetada. Mas os testes não incluíam casos de borda patológicos: strings longas que *quase* casam mas não casam, que são exatamente as entradas que maximizam o backtracking. Esse é um gap clássico: testar o caminho feliz não é suficiente para sistemas de segurança que processam input adversarial.

**Staging** replicava a configuração de produção, mas não replicava o volume de tráfego. Com poucas requisições por segundo, mesmo um regex patológico pode não ser observável — o impacto só se torna visível sob carga real. Isso é uma armadilha comum em testes de performance: o ambiente de staging raramente tem a pressão necessária para revelar problemas de complexidade algorítmica.

**Deploy global simultâneo** foi a decisão arquitetural que transformou um bug em uma catástrofe. Se a Cloudflare tivesse feito um staged rollout — começando com 1% dos PoPs, observando métricas de CPU por 5 minutos, e só então progredindo — o impacto teria sido contido. A ausência de staged rollout para mudanças de WAF é surpreendente dado o histórico da indústria com deploys graduais. A justificativa implícita provavelmente era velocidade de resposta a ameaças: quando você descobre um novo vetor de ataque, quer proteção global imediata. Mas essa velocidade tem um custo de risco que este incidente quantificou de forma dolorosa.

## Remediação e Mudanças Implementadas

A Cloudflare publicou um post-mortem detalhado e implementou um conjunto substancial de mudanças. Analiso cada uma com o olho de quem já operou sistemas de borda em escala financeira:

**1. Substituição do motor de regex (PCRE → RE2/Rust)**
A mudança mais importante. RE2 e a crate `regex` do Rust garantem complexidade O(n) para qualquer expressão regular, eliminando a possibilidade de backtracking catastrófico por construção. Isso não é uma mitigação — é uma eliminação da classe de falha. O trade-off é que RE2 não suporta alguns recursos do PCRE (lookahead negativo, backreferences), o que pode requerer reescrita de regras existentes. Para um WAF, esse trade-off é claramente favorável: previsibilidade de performance supera expressividade de regex.

**2. Análise estática de complexidade no CI**
Ferramentas como `rxxr2` ou análise de complexidade de regex baseada em teoria de autômatos foram adicionadas ao pipeline. Qualquer regex que possa exibir comportamento super-linear é rejeitado antes do merge. Isso é defense-in-depth correto: não confiar apenas na revisão humana para propriedades que podem ser verificadas automaticamente.

**3. Staged rollout obrigatório para mudanças de WAF**
Deploys de regras agora seguem um processo de rollout gradual: um subconjunto de PoPs primeiro, com observação automática de métricas de CPU e latência, antes de progressão global. Isso é o padrão correto para qualquer mudança que afete o caminho crítico de processamento de tráfego.

**4. Limite de CPU por operação de regex**
Mesmo com RE2, um timeout por operação de WAF foi adicionado como camada adicional de proteção. Se uma operação excede um threshold de tempo, ela é abortada e a requisição é passada (fail-open) ou bloqueada (fail-closed) conforme a política configurada. Isso é defense-in-depth: múltiplas camadas independentes.

**5. Melhoria dos casos de teste**
A suíte de testes foi expandida para incluir inputs patológicos: strings longas, strings que quase casam, strings com repetição de caracteres. Isso deveria ter existido desde o início para um sistema que processa input adversarial.

O conjunto de mudanças é exemplar. A Cloudflare não apenas corrigiu o sintoma (a regra específica) mas atacou a causa raiz em múltiplas camadas: a engine, o processo de deploy, o pipeline de CI e os testes. Isso é como um post-mortem bem executado deve resultar em ação.

## Lições Técnicas

- **Backtracking catastrófico é uma propriedade verificável estaticamente.** Ferramentas como `rxxr2`, `safe-regex` (Node.js) e análise de autômatos podem detectar padrões ReDoS antes do deploy. Não existe justificativa para não incluir isso em qualquer CI que processe regras de regex em produção.
- **PCRE em caminhos críticos de alta frequência é uma escolha de risco.** Engines baseadas em NFA com backtracking (PCRE, Python `re`, Java `java.util.regex`) não garantem complexidade linear. Para WAFs, parsers e qualquer sistema que processe input não confiável, prefira RE2, Rust `regex`, ou Hyperscan.
- **Deploy global atômico é um antipadrão para mudanças de configuração de segurança.** A velocidade de resposta a ameaças não justifica o risco de um bug de configuração derrubar 100% da capacidade global. Staged rollout com observação automática de métricas é o padrão correto.
- **Testes de segurança devem incluir inputs adversariais e patológicos.** Validar que uma regra detecta o que deve detectar não é suficiente. É necessário validar que a regra não explode sob inputs que *quase* casam, especialmente em sistemas que processam tráfego de internet não filtrado.
- **Defense-in-depth se aplica a operações de runtime, não apenas a perímetros de rede.** Um timeout por operação de regex é uma camada de proteção independente da engine escolhida. Sistemas críticos devem ter múltiplas camadas de contenção de blast radius.
- **O staging sem carga representativa tem valor limitado para problemas de performance.** Problemas de complexidade algorítmica só se manifestam sob volume. Load testing com tráfego sintético representativo deve ser parte do pipeline de validação de regras.

> **Minha Perspectiva: O que eu teria feito diferente:** Trabalhei com sistemas de borda e WAF em contextos de serviços financeiros, onde um minuto de indisponibilidade tem custo mensurável e regulatório. O que me chama atenção neste incidente não é o bug em si — regex com backtracking catastrófico é um problema bem documentado desde os anos 1990 — mas a ausência de controles que são, francamente, básicos para sistemas nessa escala.

O que eu teria exigido antes desse deploy chegar à produção:

**Primeiro**, análise estática de complexidade de regex no CI é não-negociável para qualquer sistema que processe input de internet. Ferramentas como `rxxr2` existiam antes deste incidente. O custo de integração é de horas, não semanas. A ausência disso em um WAF que protege uma fração significativa da internet é uma lacuna de processo difícil de justificar.

**Segundo**, o deploy global atômico me preocupa mais do que o regex em si. Um bug de configuração vai acontecer — a questão é se você tem mecanismos para conter o blast radius. Staged rollout com circuit breaker automático (se CPU > X% no PoP piloto, parar o rollout e alertar) é arquitetura padrão para sistemas críticos. A justificativa de velocidade de resposta a ameaças pode ser endereçada com um fast-path de rollout (5 minutos em 1% dos PoPs, não 30 minutos em 10%) sem abrir mão da segurança do processo.

**Terceiro**, e isso é menos óbvio: a escolha de PCRE para uma engine de WAF de alta frequência é uma decisão de risco que deveria ter sido revisitada independentemente deste incidente. RE2 e Hyperscan existem precisamente para esse caso de uso. A migração tem custo, mas o custo 

## Veredicto: Uma Falha Sistêmica Evitável

O incidente da Cloudflare de julho de 2019 é um caso de estudo sobre como múltiplas camadas de controle podem falhar simultaneamente quando cada uma delas foi projetada para verificar uma propriedade diferente da que causou o problema. A revisão de código verificou lógica. Os testes verificaram corretude funcional. O staging verificou compatibilidade de configuração. Nenhuma camada verificou complexidade algorítmica de pior caso sob input adversarial — que é exatamente a propriedade que importa para um WAF.

A lição central não é 'cuidado com regex'. É mais profunda: **propriedades de segurança e performance de sistemas críticos devem ser verificadas por mecanismos adequados para cada propriedade**, não assumidas como consequência de controles genéricos de qualidade. Backtracking catastrófico é verificável estaticamente. Deploy gradual é uma prática estabelecida. Timeouts de operação são primitivas básicas de engenharia de resiliência. A ausência de qualquer um desses controles em um sistema da escala e criticidade da Cloudflare é, em retrospecto, surpreendente.

O que salva a narrativa é a resposta: rollback em 27 minutos, post-mortem técnico detalhado publicado publicamente, e um

## Referências

- [Cloudflare — Details of the July 2 outage (official post-mortem)](https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/)

## Fontes do caso

- [Cloudflare — Details of the July 2 outage](https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/)
