Isto não é alarme, é inventário. Escrito em 02/09/2026 para que a decisão que falta seja tomada com o número na mão, e não no susto.
A frase curta
equipe.beneverso.com.br não tem senha, e o banco por trás dele também não.
Qualquer pessoa que descubra o endereço lê tudo — e, pior, quem souber ler o
JavaScript da página pública consegue ler o banco inteiro sem nem abrir a
área da equipe.
O que exatamente está aberto
1. A área da equipe, sem senha
O Basic Auth do Traefik saiu em 13/08/2026, a pedido do Felipe, porque
travava o uso no celular. As duas linhas que o ligavam de volta continuam
escritas no docker-compose.yml, comentadas.
Alcance: quem tiver o link. Não está indexado no Google (o robots.txt e a
vedação do proxy cuidam do host público), mas link se compartilha.
2. O banco, direto — sem passar pelo site
Esta é a parte que quase ninguém espera, e é a mais séria.
15 das 16 tabelas têm leitura liberada para o papel public (remedido em 02/09/2026 direto em pg_policies; só membros_equipe está fechada). A chave anon
do Supabase, que é necessária para o site funcionar, vai embutida no
JavaScript entregue ao navegador — inclusive em beneverso.com.br, que é o
site público. Com essa chave e o endereço do projeto, dá para consultar as
tabelas por REST, de qualquer lugar, sem abrir nossa área.
O efeito prático: as vedações que o site faz por campo protegem só o HTML.
Por exemplo, a /pautas apaga como_aborda e limites antes de mandar a
página ao público — e os dois campos continuam legíveis por consulta direta.
O que fica ao alcance de quem fizer isso:
| Tabela | O que tem dentro |
|---|---|
adversarios | o dossiê inteiro: polemicas, como_mencionar, atencao |
paginas_colab | com quem falamos, o que cada página aceita, quando pedimos |
agenda_posts | o que vai ao ar, quando, e as observações de aprovação |
pautas | como_aborda e limites — a nota interna de cada bandeira |
timeline_events | sugestao_editorial e status |
citacoes | uso, verificar, link_confirmacao |
sugestoes, metricas_*, aprovacoes | o resto do trabalho interno |
Só membros_equipe está fechada de verdade.
3. Os MP4 da série, em bucket público
Os cortes da série do Senado estão no Supabase Storage num bucket público, desde 28/08 — inclusive as sete peças anônimas. A URL é adivinhável a partir do nome do episódio.
⚠️ E a /serie promete o contrário, por escrito: "não reproduz vídeo, não
hospeda nada e não aponta para lugar público". A promessa está errada e a
/videos toca esses arquivos por essa URL.
O que JÁ foi fechado (02/09/2026)
A escrita pública em sugestoes. A tabela aceitava INSERT e UPDATE do
papel public: qualquer pessoa na internet podia criar e alterar sugestões sem
nunca ter visitado a nossa área. As duas políticas foram derrubadas.
Não quebrou nada, e o motivo está medido: todas as escritas do site passam por
server action com a chave service_role, que ignora RLS. A leitura continua
aberta, senão a /videos para de funcionar.
O que NÃO dá para consertar sem uma decisão
Fechar a leitura das 15 tabelas derruba o site inteiro — se nada mais mudar. Não há login: o site
lê tudo com a chave anon, e é ela que as políticas liberam. Trocar
using (true) por using (is_membro()) — que é a função certa, e ela já existe
no banco, sem nenhuma política a usando — apagaria todas as telas.
Ou seja: é decisão do Felipe, não conserto de migração. As opções são três:
| Caminho | Custo | O que resolve |
|---|---|---|
Voltar o Basic Auth — as duas linhas já estão no docker-compose.yml | minutos | fecha a área da equipe. NÃO fecha o banco: a chave anon continua no bundle público |
Login de verdade (Supabase Auth + is_membro() nas políticas) | horas, e mexe em tudo | fecha os dois. É a solução completa |
| Aceitar e conviver | zero | assumir que o material interno é semi-público, e não escrever nada que não possa vazar |
⚠️ Se algum dia o login for ligado, há uma armadilha esperando: 9 tabelas dão
ALL (inclusive DELETE) para authenticated com using (true). Hoje isso é
inofensivo porque não existe um único usuário. No dia em que existir, qualquer
conta autenticada apaga a linha do tempo inteira. Prender essas 9 a is_membro()
antes de ligar login não é opcional.
O quarto caminho — descoberto em 02/09/2026
A tabela acima dá três opções e a completa custa "horas, e mexe em tudo". Há uma quarta, bem mais barata, e ela existe por um motivo que ninguém tinha medido: nenhuma tela deste site lê o banco pelo navegador.
Medição: os sete componentes "use client" que importam de lib/supabase.ts
— adversários, calendário, citações, pautas, produção, projetos e linha do
tempo — não chamam supabase. uma única vez. Todos recebem os dados já
prontos, por props, do componente de servidor. Os dois casos que a busca aponta
são comentários citando o nome do arquivo.
Se toda leitura é no servidor, o navegador não precisa de chave nenhuma. Daí:
- Trocar a chave que
lib/supabase.tsusa: deNEXT_PUBLIC_SUPABASE_ANON_KEYpara uma variável sem o prefixoNEXT_PUBLIC_(que é o que faz o Next embutir o valor no pacote do navegador). - Rotacionar a chave anon no painel do Supabase. Sem isto, nada muda de fato: a chave antiga já está publicada e continua valendo para sempre.
- Fechar as políticas de
SELECTparapublic.
O site continua funcionando porque nenhuma leitura passa pelo navegador. Não
precisa de login, não precisa de is_membro(), não mexe em tela nenhuma.
🚨 A armadilha deste caminho — leia antes de tentar
Três componentes de cliente importam CONSTANTES de lib/supabase.ts, e não
só tipos:
| Arquivo | O que importa |
|---|---|
app/calendario/calendario-client.tsx | MESES |
app/pautas/pautas-client.tsx | PRIORIDADE |
app/timeline/timeline-client.tsx | CATEGORIAS |
Importar valor arrasta o módulo inteiro para o pacote do navegador — e junto
vai a chave que estiver dentro dele. É exatamente assim que a chave anon foi
parar lá. Os outros quatro usam import type, que o compilador apaga e não
arrasta nada.
Se alguém trocar a chave por uma de servidor sem resolver estes três antes, a chave de SERVIÇO vai para o navegador — e essa ignora RLS e escreve. Seria trocar um problema de leitura por um de escrita total, que é ordens de grandeza pior.
O conserto dos três é passar a constante como prop do componente de servidor, como já é feito com os dados. Depois disso, confira no pacote antes de publicar:
grep -r "SUPABASE" .next/static/chunks/ | head
Tem que sair vazio.
A régua enquanto a decisão não vem
Foi ela que guiou as correções de 02/09, e vale para quem escrever qualquer coisa nova:
- Preço e valor não entram. A tabela da LUZ saiu por isso: a Julia declarou o PDF não público, e a nossa área não tem senha.
- Nada sobre terceiro que não está no grupo. Especialmente posição política. Saiu a linha sobre a simpatia do Marcus di Flora.
- Nada que avalie pessoa. Cansaço, humor, capacidade. Fato operacional ("desliga às 21h30") entra; diagnóstico não.
- Antes de escrever, o teste: isto pode ser lido por alguém de fora amanhã? Se a resposta for não, não escreva — conversa.