Vulnerabilidades DoS e Source Code em React Server Components

Descubra vulnerabilidades de DoS e exposição de código em React Server Components, incluindo configuração de bundlers, rate limiting e proteção de dados sensíveis.

Riscos de Segurança em React Server Components: Configuração e Recursos sob Ataque

React Server Components mudaram fundamentalmente como pensamos sobre renderização no servidor. Desde sua estabilização em março de 2023, times de desenvolvimento adotaram RSC massivamente através do Next.js App Router. Mas a mudança arquitetural que torna RSC poderoso também introduz vetores de ataque sutis que a maioria das aplicações em produção não está mitigando adequadamente.

A discussão não é sobre vulnerabilidades exóticas ou CVEs catalogados — até janeiro de 2025, não existem CVEs oficialmente registrados especificamente para React Server Components no banco de dados NVD. O problema é mais fundamental: RSC expõe uma superfície de ataque baseada em configuração incorreta de bundlers e gestão inadequada de recursos. Quando você transforma código server-side em algo que pode ser requisitado dinamicamente pelo cliente, as regras de segurança tradicionais precisam ser repensadas.

Vazamento de Código via Bundlers: A Ameaça Invisível

A arquitetura de RSC depende de uma separação estrita entre código que roda no servidor e código que vai para o cliente. O problema começa quando bundlers não entendem essa separação corretamente.

Webpack 5.88.0+ introduziu suporte nativo para RSC boundaries através da condição react-server em julho de 2023. Rspack 0.4.0+ implementou compatibilidade similar em outubro do mesmo ano. Suporte nativo não significa proteção automática contra configurações incorretas.

O risco real acontece em arquiteturas mais complexas. Webpack ModuleFederationPlugin, por exemplo, pode acidentalmente expor server components se as conditions não forem explicitamente configuradas. Você cria um micro-frontend, configura module federation para compartilhar componentes entre aplicações, e sem perceber, código que deveria ficar exclusivamente no servidor está sendo servido para o cliente.

A documentação do Webpack alerta sobre esse cenário: bundlers podem incluir server-only code em client bundles quando conditions não são corretamente configuradas. O alerta é genérico demais. Na prática, você precisa auditar cada entry point e cada shared module para garantir que a boundary seja respeitada.

O time do React criou o pacote server-only especificamente para combater isso. Quando você importa esse pacote em um módulo, o build falha em tempo de compilação se aquele código for referenciado por um client component. Next.js 13.4+ implementa automatic code splitting que previne vazamento quando usado com essa diretiva. A proteção só funciona se você lembrar de adicionar import 'server-only' em cada módulo sensível.

// database.ts - Server-only module
import 'server-only';
import { Pool } from 'pg';

export const db = new Pool({
  connectionString: process.env.DATABASE_URL
});

export async function getSecretData() {
  // Código que nunca deveria ir para o cliente
  return db.query('SELECT * FROM secrets');
}

A proteção é opt-in, não opt-out. Esqueça o import e o bundler pode silenciosamente incluir esse código no bundle do cliente, expondo strings de conexão, queries, lógica de negócio. Ferramentas de análise estática específicas para detectar essas configurações incorretas não estão disponíveis, então a verificação manual é sua única defesa.

Ataques de Negação de Serviço: Recursos sob Pressão

DoS em RSC não vem de exploits sofisticados. Vem de consumo descontrolado de recursos durante renderização. Server Components renderizam dinamicamente no servidor a cada request, e essa renderização pode ser arbitrariamente cara.

GitHub issue #48748 reporta potencial DoS via recursive server component rendering. O cenário é direto: um atacante envia requests que forçam renderização de component trees profundamente aninhados. React não implementa limites de profundidade por padrão. Você precisa implementar essa proteção na sua aplicação.

Next.js implementa algumas proteções básicas. Payloads de Server Actions são limitados a 1MB por padrão desde a versão 13.4, configurável via experimental.serverActions.bodySizeLimit. Vercel implementa timeouts de 10 segundos para planos Hobby, 60 segundos para Pro, 300 segundos para Enterprise. Essas proteções focam em operações individuais, não em consumo agregado de recursos.

O problema real aparece com streaming. Streaming SSR com RSC reduz TTFB em 30-40% comparado a renderização tradicional segundo benchmarks da Vercel, mas aumenta significativamente o memory footprint durante o processo. Streaming 10MB de payload RSC consome aproximadamente 150MB de RAM server-side versus 80MB para renderização estática segundo discussões no GitHub #48114.

GitHub issue #51242 documenta memory leaks em streaming RSC com grandes datasets. O vazamento acontece quando você tem múltiplas Suspense boundaries transmitindo dados simultaneamente sem backpressure adequado. Next.js 14.1+ ativou Node.js streams backpressure nativo por padrão para prevenir unbounded buffering, mas a proteção tem limites.

Vercel recomenda limitar Suspense boundaries a máximo 10-15 por página para evitar thread starvation. A recomendação faz sentido quando você entende o custo: RSC usa 60-70% menos memória que renderização síncrona em payloads maiores que 1MB, mas cada boundary adiciona overhead de gestão de estado.

A mitigação mais efetiva é rate limiting no nível de middleware. Vercel não inclui rate limiting por padrão — você precisa implementar via middleware customizado ou Vercel Firewall. Rate limiting tradicional por IP não funciona bem quando você tem infraestrutura legítima (CDNs, proxies) fazendo requests em nome de múltiplos usuários.

Serialização e Exposição Involuntária de Dados

A documentação oficial do React é explícita: qualquer dado passado de Server para Client Components é serializado e visível no payload de rede. Isso não é um bug, é design. A implicação raramente é clara até você inspecionar o network tab.

Quando um Server Component renderiza, o React serializa props, state e contexto que cruza a boundary server-client. Essa serialização acontece em JSON, e esse JSON é enviado para o cliente. Se você passar um objeto de usuário com campo passwordHash de um Server Component para um Client Component, aquele hash vai estar no payload, visível para qualquer um que abra DevTools.

// ServerComponent.tsx
import { getUser } from './database';

export default async function UserProfile({ userId }) {
  const user = await getUser(userId);
  
  // PROBLEMA: 'user' pode conter campos sensíveis
  // que serão serializados e enviados para o cliente
  return <ClientProfile user={user} />;
}

A proteção óbvia é sanitizar dados antes de passar para Client Components. “Óbvio” não significa “consistentemente implementado”. Em aplicações complexas com múltiplas camadas de componentes, é fácil perder o rastreamento de quais dados cruzam a boundary.

Server Actions adicionam outra camada de complexidade. Actions não incluem proteção CSRF por padrão segundo a documentação de segurança do Next.js. Você precisa implementar token validation manualmente. O padrão recomendado envolve gerar tokens no servidor, validá-los na action, mas essa validação é completamente bypass-able se você não implementar rate limiting adicional.

O risco não é apenas exposição de dados — é manipulação de operações server-side. Um atacante pode replay requests de Server Actions sem limitação se você não implementar proteções adequadas. A documentação oficial não especifica guidelines sobre rate limiting e throttling específicos para Server Actions.

Estratégias Práticas de Mitigação em Produção

Edge Runtime é recomendado para RSC quando possível. Edge reduz cold start DoS vectors e melhora latência, mas vem com limitações significativas de APIs Node.js disponíveis. Se sua aplicação depende de bibliotecas que usam módulos nativos do Node.js, Edge Runtime não é opção.

Para Node.js Runtime tradicional, a estratégia mais robusta combina múltiplas camadas:

Configuração de bundler explícita: Audite todas as module federation configs e shared scopes. Use server-only em todos os módulos que acessam recursos sensíveis (database, APIs externas, secrets). Configure Webpack conditions explicitamente para garantir que react-server seja respeitado.

Gestão de recursos agressiva: Implemente timeouts mais agressivos que os padrões do Vercel. 60 segundos é muito tempo para um atacante manter recursos do servidor ocupados. Para a maioria das operações, 5-10 segundos é suficiente. Configure experimental.serverActions.bodySizeLimit para valores conservadores baseados nos seus casos de uso reais.

Rate limiting em múltiplas camadas: Middleware do Next.js para rate limiting por session/user, não apenas IP. Vercel Firewall ou Cloudflare para proteção de infraestrutura. Throttling específico para Server Actions usando tokens com TTL curto.

Sanitização defensiva de dados: Crie wrappers para queries de database que automaticamente removem campos sensíveis antes de retornar objetos. TypeScript pode ajudar com tipos específicos para “server data” vs “client-safe data”, mas a runtime validation é essencial porque tipos não existem em runtime.

Next.js 14+ implementou backpressure mechanism para prevenir memory exhaustion durante streaming através do PR #50993. Esse mecanismo ajuda, mas não elimina o risco. Monitore memory usage em produção e implemente circuit breakers que degradam gracefully quando consumo ultrapassa thresholds.

A proteção mais efetiva é arquitetural. RSC permite que você busque dados no servidor e passe para o cliente, mas nem todo dado precisa ser buscado em Server Components. Para dados sensíveis que serão consumidos apenas no servidor, use Server Actions com retornos que nunca cruzam a boundary diretamente. Para dados que precisam ir para o cliente, busque em endpoints de API tradicionais com controles de acesso explícitos.

Benchmarks públicos detalhados sobre limites de profundidade de recursão antes de DoS não estão disponíveis. Load testing com component trees progressivamente mais profundos vai revelar onde sua aplicação quebra. Estudos de caso públicos de source code exposure real em produção também não existem, mas a ausência de casos públicos não significa que o risco seja teórico.

A segurança em RSC é uma responsabilidade compartilhada entre framework, bundler e implementação da aplicação. Next.js fornece ferramentas, mas as ferramentas precisam ser ativamente utilizadas. Audite suas configurações, teste seus limites de recursos, sanitize seus dados defensivamente. Trate a boundary server-client com o mesmo cuidado que você trataria uma API pública exposta, porque funcionalmente é exatamente isso.


← Voltar para home