Noções de segurança
Um endpoint de API aberto é um problema? Leia o JSON
Seu scan apontou um endpoint de API aberto. Se isso importa depende do que voltou, e a maioria dos que encontramos era da própria plataforma.

Em resumo
- Um endpoint de API aberto significa que um caminho escrito no código do seu aplicativo respondeu a uma requisição sem login, e o que voltou foi JSON.
- Dos que encontramos em 30.926 aplicativos, sete em cada dez foram colocados lá pela plataforma em que o aplicativo foi publicado, e devolvem configurações.
- A pergunta que decide é se algo na resposta descreve uma pessoa. Uma tabela de preços é pública de propósito. Uma lista dos seus clientes nunca foi.
Seu scan voltou com uma linha que não soa como um veredito: alguns endpoints de API respondem sem login. Talvez alguém tenha falado de forma mais direta e dito que você tem um endpoint de API aberto. Embaixo há um endereço, e há uma boa chance de você não reconhecê-lo.
Aqui está a parte que guia após guia conta errado: o conselho de sempre para esse achado é colocar um login na frente do seu endpoint, e na maioria dos aplicativos que escaneamos não existe nenhum endpoint seu na frente do qual colocar um. Sete em cada dez dos que encontramos pertencem à plataforma em que o aplicativo foi publicado. Ler esse achado começa, então, por descobrir de quem é o endereço, e a resposta costuma aparecer logo na primeira linha do que volta.
O que um endpoint de API aberto significa no seu relatório
Significa que um caminho escrito no código do seu aplicativo respondeu a uma requisição que não levava login, e que o que voltou foi JSON.
Três passos, e nenhum deles é esperto. Carregamos seu aplicativo num navegador do jeito que um visitante faz, e lemos o código que esse navegador baixou. Ali dentro estão os endereços que o seu aplicativo chama quando precisa de dados. Depois pedimos dados a cada um, sem conta, sem cookie e sem chave. Se um endereço responde com JSON que não é uma mensagem de erro, ele vai para o relatório.
Isso registra o que aconteceu. Não é um julgamento sobre os dados, porque nada que esteja fora do seu aplicativo consegue dizer se uma lista de coisas é uma lista de coisas públicas. Por isso o achado é redigido assim: estes endpoints devolveram dados sem nenhuma autenticação, isso pode ser intencional para dados públicos, verifique se nenhum deles expõe informações privadas.
Então o relatório entrega a você uma lista de endereços para abrir. Cada um deles precisa ser aberto num navegador desconectado, e são as primeiras linhas da resposta que resolvem a questão.
Todo endpoint de API aberto é um problema de segurança?
Não. Existem três tipos, e só o terceiro é um problema.
Pense num prédio de apartamentos. Algumas coisas ali são para qualquer um que entre: o horário na porta, as saídas de emergência, o aviso de que o elevador passa por manutenção na terça. Em algum outro canto do mesmo prédio existe um arquivo com os dados dos moradores. Do corredor, um quadro de avisos e um arquivo destrancado são dois móveis que dá para abrir, e o único jeito de distinguir os dois é ler o que está no papel.
| O que respondeu | De quem é o endereço | Onde você está |
|---|---|---|
| Configurações da plataforma: avisos de cookie, interruptores | Do seu builder, em todo aplicativo que ela publica | Nada a fazer |
| Seus próprios dados públicos: uma tabela de preços, um artigo | Seu, e feito para ser lido | Nada a fazer |
| Linhas sobre pessoas: nomes, e-mails, pedidos, mensagens | Seu, e alcançável por qualquer um que tenha o endereço | Feche hoje |
A primeira linha é um quadro de avisos que outra pessoa parafusou na parede. A segunda é um quadro que você pendurou de propósito. A terceira é o arquivo, e ele está aberto desde que o endereço existe.
Como verificar o que os seus endpoints devolvem
Abra cada um desconectado e leia o que chega. A leitura demora mais do que o achado sugere, e é o único passo que responde à pergunta.
Ache os endereços. Abra seu aplicativo em produção, aperte F12 para abrir as ferramentas de desenvolvedor do navegador, clique em Rede e recarregue a página. Passe pelas requisições que voltam como JSON. Esses são os endereços de onde o seu aplicativo busca dados, e a lista do relatório é um subconjunto deles.
Consulte cada um como um estranho. Copie uma URL, abra uma janela anônima para estar desconectado, e cole. O que você vê é exatamente o que qualquer pessoa na internet vê naquele endereço.
Leia as primeiras linhas. Três coisas podem voltar:
- Um erro, uma recusa, ou
[]. O endereço quer um login, ou não há nada ali para quem está desconectado. Siga em frente. - Configurações. Flags, interruptores, uma cor, a lista de recursos ligados, um número de versão. Ninguém é nomeado e nada é descrito. Siga em frente.
- Linhas. Um e-mail, o nome de uma pessoa, o valor de um pedido, o texto de uma mensagem, um telefone. Pare aqui, é essa.
Se encontrar linhas, tente mudar um número na URL antes de fechar a aba. Um
endereço que entrega o registro de uma pessoa em id=1 e o de outra em id=2
está entregando todos, e vale saber disso antes de decidir o quanto isso é
urgente.
Nosso scan gratuito faz os dois primeiros passos por você: ele lê no código do seu aplicativo os endereços que ele chama, consulta cada um sem login, e lista os que responderam com dados. Leva uns 20 segundos e não precisa de conta: escaneie seu aplicativo. O terceiro passo é seu, porque você é a única pessoa que sabe se um nome nessa lista é um cliente de verdade.
A maioria dos que encontramos é da plataforma
Na nossa varredura de agosto de 2026, 3.852 dos 30.926 aplicativos em que essa verificação conseguiu uma resposta tinham pelo menos um endereço respondendo sem login. Desses, 2.705 eram aplicativos Base44, e na Base44 o endereço é quase sempre o mesmo.
É o /api/consent/config, as configurações de cookies da plataforma. Em
setembro reabrimos uma amostra de aplicativos Base44 apontados e o único
endereço que respondeu foi esse. Ele devolve configurações, é idêntico de um
aplicativo para outro, e não tem dado de ninguém ali. O scanner está relatando
isso corretamente e o dono não tem nada para corrigir.
| Plataforma | Aplicativos com o achado | Aplicativos em que a verificação respondeu |
|---|---|---|
| Base44 | 2.705 | 5.419 |
| Domínios próprios | 82 | 955 |
| Bolt | 4 | 1.120 |
| Lovable | 8 | 18.518 |
| v0 | 0 | 1.786 |
Falta uma plataforma nessa tabela, de propósito. À Replit cabe um bloco grande dos achados restantes, e ninguém reabriu uma amostra deles como fizemos com os da Base44, então ainda não sabemos de quem são aqueles endereços. Publicar um número como problema do dono antes que alguém tenha olhado é exatamente o caminho pelo qual uma configuração de plataforma da Base44 vira uma estatística sobre donos negligentes. Todo número acima vem do nosso scan de 30.998 aplicativos vibe-coded no ar, que publica cada um junto com a base sobre a qual foi medido.
Leia as duas pontas dessa tabela juntas, porque a diferença é a parte útil. Na
Lovable são 8 aplicativos em 18.518, e na v0 nenhum. Esses aplicativos não têm
um pequeno servidor próprio: a página fala com o Supabase direto do navegador,
então não existe em lugar nenhum um /api/… seu para essa verificação
encontrar. O que protege os dados num aplicativo assim são as regras em cada
tabela, e esse é outro achado com um
jeito próprio de dar errado.
Quando o JSON são configurações e quando são pessoas
Uma pergunta decide: alguma coisa na resposta descreve uma pessoa?
Configurações descrevem o seu aplicativo. Um flag dizendo se o modo escuro está ligado, a lista de moedas que você aceita, a versão de alguma coisa, o texto de um banner de cookies. Publicar isso revela como o seu aplicativo está configurado, e o seu aplicativo roda à vista de todo mundo de qualquer forma.
Linhas descrevem seus usuários. Um nome, um e-mail, o que alguém comprou, o que escreveu, onde mora. Isso não é uma frase sobre configuração, é o conteúdo do arquivo, e importa pelo quanto é banal levá-lo embora. Não há arrombamento. O endereço é uma linha de texto que funciona em qualquer navegador, então ele é colado num grupo de mensagens, salvo nas anotações de alguém, e baixado de tempos em tempos por um script que ninguém está olhando.
Fechar um, e por que renomear o caminho não fecha
Exija um login no endereço, e responda 401 a quem chamar sem ter um.
A correção é essa, e ela fica no endereço e em nenhum outro lugar. Três coisas que parecem a correção e não são:
- Renomear o caminho, ou tirá-lo do seu código. Dessa é preciso desconfiar, porque o achado some. Quem já tinha a URL continua tendo, ela está em históricos de navegação e em logs de servidor, e o arquivo continua aberto, só que sem a etiqueta na frente.
- Estreitar o seu cabeçalho CORS. Isso decide quais outros sites podem ler uma resposta dentro do navegador de alguém, e nada mais consulta esse cabeçalho. Um script ou um terminal lê o mesmo endereço de qualquer jeito, e é por isso que o achado do curinga é outra pergunta.
- Filtrar no aplicativo. Se a página esconde as linhas que não deveria mostrar, o endereço continua mandando. O filtro tem que acontecer antes de a resposta sair.
Existe uma quarta opção, e numa página pública ela costuma ser a melhor: deixar de ter o endpoint. Se os dados são só para a sua própria página, a página pode recebê-los enquanto está sendo construída, e o endereço sai do seu código junto com eles.
Como a correção fica no código
Duas coisas, no handler que responde ao endereço: descobrir quem está pedindo, e parar se a resposta for ninguém.
Talvez você nunca digite isso, e tudo bem. Leia assim mesmo, porque é com isso que você confere se o seu builder fez de fato o que você pediu. Esta é a forma de antes, que é o que o scan encontrou:
app.get('/api/orders', async (req, res) => {
const orders = await db.orders.findMany()
res.json(orders)
})
E depois:
app.get('/api/orders', async (req, res) => {
const user = await getUserFromRequest(req)
if (!user) {
return res.status(401).json({ error: 'Not signed in' })
}
const orders = await db.orders.findMany({ where: { userId: user.id } })
res.json(orders)
})
Duas coisas mudaram e as duas importam. O 401 é a porta. O where é a razão
de a porta valer a pena: sem ele um visitante logado consegue ler os pedidos de
todos os outros clientes, o que é um público menor para os mesmos dados e
continua sendo o errado.
Em Edge Functions do Supabase, leia a configuração antes de escrever qualquer
coisa. Edge Functions conferem o token de quem chama por conta própria: a
referência de configuração do Supabase deixa verify_jwt em true por padrão,
então uma função que responde a desconhecidos é uma em que alguém desligou isso,
seja com uma linha em supabase/config.toml, seja publicando com
--no-verify-jwt:
[functions.orders]
verify_jwt = false
Se a sua função deve ser privada, apague essas duas linhas e publique de novo. Deixe-as só onde o endpoint realmente precisa responder a qualquer um, que é o caso que a própria documentação do Supabase usa como exemplo: um webhook de pagamento. Dentro da função, o SDK atual entrega a você um cliente já limitado à pessoa que chamou:
import { withSupabase } from 'npm:@supabase/server'
export default {
fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
const { data } = await ctx.supabase.from('orders').select()
return Response.json(data)
}),
}
O ctx.supabase roda como quem chamou, então são as suas regras de segurança em
nível de linha que decidem o que volta, que é a mesma proteção da qual o resto
do seu aplicativo depende e
o que vale a pena conferir à parte.
O prompt para colar no seu builder
Se você constrói no Lovable, Bolt, Cursor, Replit ou v0, entregue isto a ele. Deixe em inglês, seja qual for o idioma em que você está lendo, porque é nesse idioma que essas ferramentas raciocinam, e troque as duas partes entre colchetes pelo que o seu relatório diz:
A security scan of [my-app.com] found these API endpoints answer
without any authentication: [/api/orders, /api/users].
Please look at each one and decide whether it is meant to be public.
For the ones that are not, require a valid session or API token and
return 401 otherwise. Make sure each one returns only the rows that
belong to the person asking, not the whole table filtered in the UI.
For any that must stay open, make sure they return only non-sensitive
data and add rate limiting so they cannot be abused.
Tell me what you changed for each endpoint, and do not rename or
remove the paths.
Aquela última frase está ali de propósito. Quando se pede a um modelo que faça um achado desaparecer, ele às vezes pega o caminho mais curto e move o endereço, que é o único resultado que parece sucesso num novo scan e não muda nada.
O que fazer agora
O que fazer
- Abra cada endereço apontado numa janela anônima e leia o que volta. Esse é o passo que o relatório não faz por você.
- Se a resposta forem configurações e o endereço for um que você nunca escreveu, ele é do seu builder e não há nada a corrigir. Procure o caminho na documentação da sua plataforma antes de gastar qualquer coisa com isso.
- Se a resposta tiver um nome, um e-mail ou um pedido, exija hoje um login naquele endereço e devolva 401 a quem não tiver.
- Não renomeie o caminho nem apague a referência a ele. O achado some e o endereço continua respondendo.
- Tente mudar um id na URL no seu próprio aplicativo. Um endereço que serve um registro a um estranho costuma servir todos.
Os endereços dessa lista foram todos escritos por alguém que sabia o que havia atrás deles na época, e o que muda é justamente o que há atrás. O Reeve Care roda esse scan de novo no seu aplicativo em produção com uma periodicidade e manda um e-mail quando um resultado piora, que é exatamente o caso que a passagem desconectada acima não consegue pegar: o que ele vigia e quanto custa.
Se preferir resolver tudo de uma vez, a checklist de segurança em 10 minutos cobre isso ao lado do resto do que um aplicativo recém-lançado costuma deixar aberto, e quais chaves são seguras no seu frontend é o outro achado com o qual as pessoas costumam chegar.
Perguntas frequentes
O que significa "endpoint de API aberto" no meu relatório?
Significa que lemos o código que o seu aplicativo envia para um navegador, achamos ali dentro um endereço de onde o seu aplicativo busca dados, e pedimos dados a esse endereço sem conta e sem login. Ele respondeu, e a resposta era JSON. O achado é isso. Ele registra o que aconteceu, e não julga se os dados eram privados, porque nada fora do seu aplicativo tem como saber.
Todo endpoint de API aberto é um problema de segurança?
Não. Muitos endereços existem para responder a qualquer um: uma tabela de preços publicada, um artigo, a lista de quais recursos estão ligados. O problema é o endereço que devolve linhas que pertencem a pessoas, porque ele faz isso para todo mundo que tiver a URL. Leia o que voltou e pergunte se alguma coisa ali descreve uma pessoa.
Meu relatório aponta um endpoint que eu não escrevi. O que é isso?
Em geral é do seu builder. Plataformas que dão ao seu aplicativo um pequeno servidor próprio também colocam nele alguns endereços delas, e esses estão no código de todo aplicativo da plataforma. O exemplo mais claro que medimos são as configurações de cookies da Base44 em /api/consent/config, que respondem por 2.705 dos 3.852 achados da nossa varredura de agosto de 2026. Elas devolvem configurações, são idênticas em todo aplicativo Base44, e nada ali pertence a você.
Como eu verifico o que os endpoints do meu aplicativo devolvem?
Abra seu aplicativo em produção, aperte F12, clique em Rede e recarregue. As requisições que voltam como JSON são os endereços de onde o seu aplicativo busca dados. Copie cada URL, abra uma janela anônima para estar desconectado, e cole uma de cada vez. Leia o que chega. Um erro ou uma lista vazia está tudo bem. Linhas com nomes, e-mails ou valores de pedidos podem ser lidas por qualquer pessoa que tenha aquele endereço.
Esconder o caminho resolve?
Não. Renomear o endereço, ou tirar do seu código a referência a ele, impede que um scanner o encontre e o deixa respondendo exatamente como antes. Quem já tinha a URL continua tendo, e ela fica em históricos de navegação, logs de servidor e em tudo que rastreou o seu site. A correção é no endereço: exigir um login e responder 401 a um desconhecido.