Trinco
Relatório de segurança: DemoAgenda
Versão 1.0 · Análise de 3 a 4 de março · Confidencial
Documento de exemplo. DemoAgenda, seus dados e todos os achados são fictícios. Nenhum sistema real foi testado para criar este relatório.
1. Resumo para decisão
Não recomendamos abrir o app para clientes antes de corrigir os itens crítico e altos. Hoje, uma pessoa sem conta consegue ler dados de clientes, e a chave administrativa do banco está exposta. Os dois problemas juntos permitem copiar ou alterar a base inteira.
A boa notícia: os itens mais graves têm correção objetiva e devem caber em cerca de um dia de trabalho, se a estrutura real for parecida com a observada.
| ID | Gravidade | Problema | Corrigir |
| RX-01 | Crítico | Tabela de agendamentos pública | Agora |
| RX-02 | Alto | Chave administrativa no navegador | Agora |
| RX-03 | Alto | Fatura de outro cliente pode ser aberta | Antes de lançar |
| RX-04 | Alto | Webhook aceita pagamento falso | Antes de cobrar |
| RX-05 | Médio | Login sem limite de tentativas | Até 7 dias |
| RX-06 | Médio | Assistente de IA sem limite de uso | Até 7 dias |
| RX-07 | Baixo | Cabeçalhos de segurança ausentes | Até 30 dias |
2. O que foi analisado
| Aplicativo | DemoAgenda, agenda online para pequenos consultórios |
| Ambiente | homolog.demoagenda.example, com dados fictícios |
| Tecnologias | Next.js, Supabase e Stripe, gerados com auxílio de IA |
| Acessos | Duas contas de usuário comum, uma de administrador e leitura do repositório |
| Autorização | Termo de escopo assinado pelo responsável legal |
| Fora do escopo | Sobrecarga, engenharia social, produção e sistemas de terceiros |
3. Achados
Tabela de agendamentos pode ser lida sem login
O que isso significa: qualquer visitante pode baixar nomes, telefones e horários dos clientes.
Como reproduzir
curl "https://PROJETO.supabase.co/rest/v1/agendamentos?select=*&limit=1" \
-H "apikey: CHAVE_PUBLICA_ANON"
Resultado: a API devolveu um registro fictício sem sessão. O teste parou no primeiro registro, como definido no escopo.
Causa
A regra de segurança por linha (RLS) está desligada na tabela. A chave pública é visível no navegador por natureza, então não pode ser a única proteção.
Correção
alter table public.agendamentos enable row level security;
create policy "usuario le os proprios agendamentos"
on public.agendamentos for select
to authenticated
using ((select auth.uid()) = usuario_id);
Repita a revisão nas operações de inclusão, alteração e exclusão, e nas demais tabelas.
Prompt para sua ferramenta de IA
Ative RLS em todas as tabelas públicas do Supabase. Crie políticas para que cada
usuário acesse apenas registros em que usuario_id seja igual a auth.uid(). Liste
cada política e não use "using (true)".
Chave administrativa enviada ao navegador
O que isso significa: quem encontrar a chave pode ignorar as regras do banco, ler, alterar ou apagar dados.
Evidência
O arquivo JavaScript público contém o valor de NEXT_PUBLIC_SUPABASE_SERVICE_ROLE. A chave foi identificada, mas não utilizada.
Correção
- Gerar uma nova chave no Supabase e revogar a atual.
- Renomear a variável, retirando o prefixo
NEXT_PUBLIC_.
- Usar a chave somente no servidor e publicar novamente.
- Revisar registros de acesso em busca de uso desconhecido.
Usuário abre fatura de outro cliente ao trocar o ID
O que isso significa: um cliente logado pode ver dados e valores de outro.
Como reproduzir
- Entrar com a conta de teste A.
- Abrir
/api/faturas/1048, que pertence à conta de teste B.
- Observar que a API responde 200 com os dados da fatura.
Correção
const fatura = await buscarFatura(id);
if (!fatura || fatura.usuarioId !== sessao.usuarioId) {
return Response.json({ erro: "Não encontrada" }, { status: 404 });
}
Webhook de pagamento aceita evento sem assinatura
O que isso significa: uma pessoa pode tentar ativar um plano pago enviando um aviso falso de pagamento.
Evidência
No ambiente de teste, um evento fictício sem o cabeçalho Stripe-Signature foi aceito e alterou uma conta de teste.
Correção
const corpo = await request.text();
const assinatura = request.headers.get("stripe-signature");
const evento = stripe.webhooks.constructEvent(
corpo,
assinatura,
process.env.STRIPE_WEBHOOK_SECRET
);
Trate falha de validação como erro 400 e não altere a conta.
Login sem limite de tentativas
Foram feitas 30 tentativas controladas contra uma conta de teste, sem bloqueio ou atraso.
Correção: aplicar limite por conta e por IP, atraso progressivo e alerta para volume anormal.
Assistente de IA sem limite de uso
Uma conta comum pode fazer pedidos sem cota, o que pode gerar custo inesperado.
Correção: definir limite por usuário, limite de tamanho, teto mensal no provedor e alerta de gasto. Não permitir que texto do usuário amplie as permissões do assistente.
Cabeçalhos de segurança ausentes
Não foram encontrados Content-Security-Policy e X-Content-Type-Options.
Correção: configurar os cabeçalhos e testar para não bloquear scripts legítimos.
4. Pontos sem problema encontrado
- HTTPS ativo no ambiente analisado.
- Senhas não são armazenadas diretamente pelo app, pois o login usa o Supabase Auth.
- Nenhuma chave foi encontrada no histórico de commits analisado.
Isso não prova ausência de falhas. Registra apenas o resultado dos testes feitos.
5. Plano de correção
- Hoje: revogar a chave, publicar sem ela, ativar RLS e revisar registros de acesso.
- Antes de lançar: corrigir o acesso às faturas.
- Antes de cobrar: validar a assinatura do webhook.
- Até 7 dias: limitar login e uso do assistente de IA.
- Até 30 dias: configurar cabeçalhos e revisar após grandes mudanças.
Possível incidente: como a chave estava pública, avalie se houve acesso indevido. Havendo indício real, procure orientação jurídica sobre as obrigações da LGPD.
6. Limites da análise
Este relatório retrata o ambiente e o período indicados. Ele não garante ausência de vulnerabilidades, não é certificação e não substitui assessoria jurídica. Ataques de sobrecarga, engenharia social, produção e sistemas de terceiros não foram testados.
Gravidade considera o impacto no negócio e a facilidade de exploração neste contexto. Evidências são apagadas 30 dias depois da entrega, salvo acordo escrito diferente.