Rate limit não é sobre bloquear abuso. É sobre decidir quem sofre quando o Redis cai
Antes disso, qualquer endpoint de autenticação, envio de e-mail ou rota de API estava sem nenhuma proteção.
Não era descuido. Era prioridade.
Mas prioridade tem prazo de validade.
O problema real
Sem controle, três frentes ficavam expostas ao mesmo tempo:
- Credential stuffing e brute force contra login
- Abuso de envio de e-mail transacional
- Consumo descontrolado de API por cliente único
Já tínhamos aprendido, com a conta de 8 mil reais na Azure, que ausência de controle não é neutra. Ela vira custo. Ou vira incidente de segurança.
Rate limit deixou de ser item de backlog.
A escolha da biblioteca
Entre @upstash/ratelimit e rate-limiter-flexible, escolhemos o segundo.
Motivo:
@upstash/ratelimitexige cliente HTTP próprio da Upstash e não suporta Redis em modo Sentinel (issue aberta desde 2024)rate-limiter-flexiblese integra direto ao Redis que já mantínhamos em produção- Suporta Sentinel, bloqueio progressivo e um limiter de emergência em memória
Não escolhemos a lib mais popular. Escolhemos a que não nos obrigava a mudar infraestrutura para resolver um problema de segurança.
Três superfícies, três políticas
Rate limit não é uma regra única aplicada em todo lugar. Definimos três:
- Auth: 10 pontos a cada 15 minutos, com bloqueio progressivo de 30 minutos
- E-mail: 5 envios por usuário a cada hora
- API geral: 100 requisições por minuto
E três chaves diferentes:
- Auth por IP, porque ainda não existe sessão nesse ponto
- E-mail por destinatário
- API por
user.idquando autenticado, por IP quando anônimo
Tratar as três superfícies com a mesma régua teria sido mais simples de implementar e mais fácil de errar.
Bloqueio progressivo: uma defesa deliberadamente hostil
No auth, esgotar o limite não devolve o usuário para a fila. Bloqueia por mais 30 minutos.
É uma escolha agressiva contra brute force.
E tem um custo real: o usuário legítimo que erra a senha 10 vezes espera meia hora ou aciona o suporte.
Aceitamos esse trade-off conscientemente. Prevenção de credential stuffing pesou mais do que fricção ocasional de um usuário real.
O que fazer quando o Redis cai
Essa foi a decisão mais importante do trabalho todo, e a menos óbvia.
Rate limit depende de um store central. Se o Redis cai, o que acontece com a proteção?
Não tratamos as três superfícies da mesma forma:
- Auth ganhou um insurance limiter em memória. Se o Redis falha, cada instância continua aplicando limite localmente. É o caminho mais sensível — não podia ficar aberto.
- E-mail e API fazem fail-open. Se o Redis falha, a requisição passa. Um alerta é registrado no Axiom, e a janela de exposição tende a ser curta.
Rate limit sem plano para "o store caiu" não é rate limit. É uma sensação de segurança que desaparece exatamente no momento em que mais precisa funcionar.
Rate limit de e-mail centralizado, não espalhado
Em vez de aplicar o limite em cada rota que dispara e-mail, centralizamos num único helper de envio.
Toda chamada de e-mail — autenticação, notificações de comportamento, relatórios — passa pelo mesmo ponto. Um lugar só decide se o envio é permitido.
Isso significa que qualquer novo caller futuro herda a proteção automaticamente. Ninguém precisa lembrar de aplicar rate limit de novo.
O que ficou fora de escopo
Não implementamos:
- Proteção contra DDoS — isso é responsabilidade de outra camada (proxy/rede), não da aplicação
- Rate limit em WebSocket — não existe esse tipo de endpoint hoje
- Dashboard dedicado de violações — os logs no Axiom já respondem "quem, quando, quantas vezes"; um dashboard só se justifica se a frequência de consulta crescer
Resolver tudo de uma vez teria atrasado o que realmente importava.
O aprendizado
Rate limit parece, à primeira vista, uma feature de segurança que se implementa e esquece.
Não é.
É uma decisão sobre quem sofre e quando: o usuário legítimo que erra a senha, o time que recebe o alerta às 3 da manhã, ou o atacante que esperava uma porta aberta.
Implementar a biblioteca é a parte fácil.
Decidir o comportamento no pior cenário — Redis fora do ar — é a parte que realmente importa.