Data center do Google e o vazamento de dados no React
Alguém em Lincoln, no Nebraska, passou uma tarja preta num documento sobre o data center do Google e achou que tinha resolvido o problema. Não resolveu. Os números de consumo de água e de energia elétrica que deveriam estar escondidos continuaram acessíveis, e a imprensa local foi atrás. Quem conta é a reportagem da 10/11 NOW, que terminou com mais perguntas do que respostas. Parece uma história de prefeitura, mas é exatamente o mesmo erro que eu vejo toda semana em código React. É o vazamento de dados no React mais comum que existe: esconder na tela algo que já foi entregue ao navegador.
O que aconteceu em Lincoln
O contexto é simples. Data centers consomem muita água para refrigeração e muita energia. Cidades que recebem esses projetos querem saber quanto. As empresas preferem não contar, porque consumo de recurso diz muito sobre capacidade e planos de expansão.
No meio disso, um documento público saiu com trechos marcados como sigilosos. Só que a redação foi feita do jeito errado: a informação continuava lá, coberta visualmente, mas recuperável. Resultado: os números que deveriam ficar entre a cidade e o Google viraram pauta de jornal.
Não vou repetir os valores aqui, e nem é isso que importa para quem programa. O que importa é o mecanismo da falha. Ela tem nome conhecido em segurança da informação: confundir ocultar com remover.
Se o dado chegou no cliente, ele é do cliente. A tarja é só decoração.
Por que isso é o mesmo bug do seu front-end
Pensa em como uma tarja preta mal feita funciona num PDF. Existe a camada de texto e existe um retângulo desenhado por cima. Quem olha a página vê preto. Quem seleciona, copia ou abre o arquivo num editor vê o texto inteiro.
Agora pensa num componente React assim:
function UserCard({ user }: { user: User }) {
return (
<div>
<h2>{user.name}</h2>
{user.role === 'admin' && <p>CPF: {user.cpf}</p>}
</div>
)
}
Para o usuário comum, o CPF não aparece. Missão cumprida? Não. Se o objeto user veio inteiro da API, o CPF está no JSON da resposta. Está na aba Network. Está no React DevTools, nas props do componente. O && é o retângulo preto desenhado por cima do texto.
A mesma coisa vale para:
display: noneouhiddenem algo que não deveria existir no HTML- filtrar uma lista no front que a API devolveu completa
- "esconder" um botão de admin em vez de bloquear a rota no servidor
- campo de preço de custo que vem no payload e só não é renderizado
Já peguei um painel de e-commerce em que a listagem de produtos devolvia margem de lucro e nome do fornecedor para qualquer visitante. A tela mostrava só nome e preço. O JSON mostrava o negócio inteiro. Ninguém tinha feito nada de malicioso, só um SELECT * e um res.json(produtos).
Server Components deixaram isso mais sutil
Com React Server Components e o App Router do Next.js, muita gente relaxou. "O componente roda no servidor, então está seguro." Em parte. O problema mora na fronteira entre servidor e cliente.
Quando um Server Component passa props para um Client Component, essas props são serializadas e enviadas no payload RSC. Tudo que você passa vai junto. Olha esse caso:
// page.tsx (Server Component)
export default async function Page() {
const user = await db.user.findUnique({ where: { id } })
return <ProfileForm user={user} />
}
// ProfileForm.tsx
'use client'
export function ProfileForm({ user }) {
return <input defaultValue={user.name} />
}
O formulário só usa name. Mas o objeto user inteiro, com passwordHash, stripeCustomerId e o que mais estiver na tabela, foi parar no HTML da página. Abra o código-fonte e procure por self.__next_f.push. Está lá, em texto puro.
Esse é o tipo de vazamento que nenhum teste visual pega. A tela está perfeita. O snapshot passa. O QA aprova. E o hash da senha está no HTML.
Como evitar vazamento de dados no React na prática
A regra é chata e funciona: o servidor decide o que sai, não o componente. Na prática, isso vira alguns hábitos.
1. Monte DTOs explícitos. Nunca passe o registro do banco direto para o cliente. Escolha os campos.
const user = await db.user.findUnique({
where: { id },
select: { id: true, name: true, avatarUrl: true },
})
Com Prisma, Drizzle ou SQL na mão, o princípio é o mesmo: select explícito. Se amanhã alguém adicionar uma coluna documento na tabela, ela não vaza sozinha.
2. Separe o código que só pode rodar no servidor. O pacote server-only faz o build quebrar se um módulo de acesso a dados for importado num Client Component.
// lib/data/users.ts
import 'server-only'
export async function getPublicProfile(id: string) {
// ...
}
É uma linha. Custa nada. E transforma um vazamento silencioso num erro de build.
3. Conheça as APIs de taint. O React tem experimental_taintObjectReference e experimental_taintUniqueValue, que marcam um objeto ou valor como proibido de atravessar para o cliente. Se alguém tentar passar como prop, dá erro. Ainda são experimentais, então eu trato como rede de segurança, não como estratégia principal. O DTO continua sendo a primeira linha.
4. Autorização no servidor, sempre. Esconder o botão de excluir é UX. Bloquear o DELETE na Server Action ou na rota é segurança. Você precisa dos dois, mas só um deles protege alguma coisa.
O teste de cinco minutos que eu faço em todo projeto
Não precisa de ferramenta cara. Abre a aplicação logado com o usuário de menor permissão e faz isto:
Ctrl+Una página e busca por palavras comopassword,hash,cpf,token,secret,cost.- Aba Network, filtra por Fetch/XHR, abre cada resposta e lê o JSON inteiro, não só o que aparece na tela.
- React DevTools, clica nos componentes principais e olha as props.
Em projeto legado, esse teste quase sempre acha alguma coisa. Na última vez que fiz num sistema que herdei, achei o e-mail de todos os outros usuários de uma organização vindo junto numa listagem de comentários. A tela mostrava só o primeiro nome. É a tarja preta de Lincoln, versão JavaScript.
Dá para automatizar uma parte disso num teste simples:
const html = await fetch('http://localhost:3000/perfil').then(r => r.text())
for (const proibido of ['passwordHash', 'stripeCustomerId']) {
if (html.includes(proibido)) throw new Error(`Vazou: ${proibido}`)
}
Não é bonito. Pega o óbvio. E o óbvio é o que mais vaza.
O que a história do Google ensina para quem escreve código
O caso de Lincoln tem um lado político, sobre transparência de big tech e uso de recursos públicos. Esse debate é legítimo e eu, sinceramente, acho que consumo de água de data center deveria ser público por padrão. Cidade que cede água e energia tem direito de saber quanto.
Mas o lado técnico é o que me interessa aqui, e ele é universal. Alguém tinha um dado sensível, queria protegê-lo, e aplicou a proteção na camada de apresentação. Funcionou visualmente. Falhou de verdade.
Em React, essa tentação é constante porque o modelo mental é visual. Você pensa em componentes, em telas, no que aparece. Condicional de renderização parece controle de acesso. Não é. Server Component parece cofre. É um cofre com uma janela chamada props.
Minha opinião: a maior parte dos vazamentos que eu vejo em apps React não vem de ataque sofisticado. Vem de SELECT * mais JSON.stringify. É preguiça na fronteira, não falta de criptografia. E a correção também não é sofisticada: escolher campos, marcar módulos como server-only e olhar o payload de vez em quando com o olhar de quem quer achar algo.
Se a prefeitura tivesse apagado o texto em vez de pintar por cima, não teria matéria. Se o seu select tivesse três campos em vez de vinte, também não. Se quiser ver como eu organizo essa fronteira entre servidor e cliente em projetos reais, dá uma olhada nos meus projetos.
Resumo para o LinkedIn
Alguém pintou uma tarja preta num documento do data center do Google e achou que tinha escondido os números. Não escondeu.
Em Lincoln, os dados de consumo de água e energia continuaram acessíveis por baixo da tarja, e a imprensa local foi atrás.
Eu vejo esse mesmo erro toda semana em React: `{isAdmin && user.cpf}` esconde o dado na tela, mas o JSON já entregou tudo pro navegador.
Com Server Components fica ainda mais sutil. Você passa o `user` inteiro como prop e o hash da senha vai parar no HTML da página.
Se o dado chegou no cliente, ele é do cliente. Ocultar não é remover.
Minha regra é: o servidor decide o que sai. `select` explícito, `server-only` e de vez em quando um Ctrl+U com olhar de quem quer achar problema.
Escrevi o passo a passo completo no blog, com o teste de cinco minutos que eu faço em todo projeto. Link nos comentários.
#React #NextJS #SegurançaDaInformação #DesenvolvimentoWeb #JavaScript