Fundamentos de seguridad
¿Un endpoint de API abierto es un problema? Mira el JSON
Tu escaneo señaló un endpoint de API abierto. Que importe depende de lo que devolvió, y la mayoría de los que encontramos eran de la plataforma.

En resumen
- Un endpoint de API abierto significa que una ruta escrita en el código de tu aplicación respondió a una petición sin login, y lo que volvió fue JSON.
- De los que encontramos en 30.926 aplicaciones, siete de cada diez los puso la plataforma en la que se publicó la aplicación, y devuelven ajustes.
- La pregunta que lo decide es si algo en la respuesta describe a una persona. Una lista de precios es pública a propósito. Una lista de tus clientes nunca lo fue.
Tu escaneo volvió con una línea que no suena a veredicto: algunos endpoints de API responden sin login. Puede que alguien te lo haya dicho de forma más directa y te haya hablado de un endpoint de API abierto. Debajo hay una dirección, y hay bastantes posibilidades de que no la reconozcas.
Aquí está la parte que guía tras guía cuenta mal: el consejo habitual para este hallazgo es poner un login delante de tu endpoint, y en la mayoría de las aplicaciones que hemos escaneado no hay ningún endpoint tuyo delante del que ponerlo. Siete de cada diez de los que encontramos pertenecen a la plataforma en la que se publicó la aplicación. Así que leer este hallazgo empieza por averiguar de quién es la dirección, y la respuesta suele verse ya en la primera línea de lo que vuelve.
Qué significa un endpoint de API abierto en tu informe
Significa que una ruta escrita en el código de tu aplicación respondió a una petición que no llevaba login, y que lo que volvió fue JSON.
Tres pasos, y ninguno tiene truco. Cargamos tu aplicación en un navegador igual que lo hace un visitante, y leemos el código que ese navegador descargó. Dentro están las direcciones a las que tu aplicación llama cuando necesita datos. Entonces le pedimos datos a cada una, sin cuenta, sin cookie y sin clave. Si una dirección responde con JSON que no es un mensaje de error, va al informe.
Eso recoge lo que pasó. No es un juicio sobre los datos, porque nada que esté fuera de tu aplicación puede saber si una lista de cosas es una lista de cosas públicas. Por eso el hallazgo está redactado como está: estos endpoints devolvieron datos sin ninguna autenticación, puede que sea intencionado para datos públicos, comprueba que ninguno expone información privada.
Así que el informe te entrega una lista de direcciones para abrir. Todas y cada una hay que abrirlas en un navegador desconectado, y las primeras líneas de la respuesta son las que zanjan el asunto.
¿Todo endpoint de API abierto es un problema de seguridad?
No. Hay tres tipos, y solo el tercero es un problema.
Piensa en un bloque de pisos. Algunas cosas están pensadas para cualquiera que entre: el horario junto a la puerta, las salidas de emergencia, el aviso de que el martes revisan el ascensor. En algún sitio de ese mismo edificio hay un archivador con los datos de los vecinos. Desde el pasillo, un tablón de anuncios y un archivador sin llave son igual de abribles, y la única manera de distinguirlos es leer lo que pone el papel.
| Qué respondió | De quién es la dirección | Dónde estás |
|---|---|---|
| Ajustes de la plataforma: avisos de cookies, interruptores | De tu builder, en todas las aplicaciones que publica | Nada que hacer |
| Tus propios datos públicos: una lista de precios, un artículo | Tuya, y pensada para poder leerse | Nada que hacer |
| Filas sobre personas: nombres, correos, pedidos, mensajes | Tuya, y accesible para cualquiera que tenga la dirección | Ciérrala hoy |
La primera fila es un tablón que colgó otro. La segunda es un tablón que colgaste tú a propósito. La tercera es el archivador, y lleva abierto tanto tiempo como lleva existiendo la dirección.
Cómo comprobar qué devuelven tus endpoints
Abre cada uno desconectado y lee lo que llega. Leer lleva más tiempo del que sugiere el hallazgo, y es el único paso que responde a la pregunta.
Encuentra las direcciones. Abre tu aplicación en producción, pulsa F12 para abrir las herramientas de desarrollo del navegador, haz clic en Red y recarga la página. Repasa las peticiones que vuelven como JSON. Esas son las direcciones de las que tu aplicación saca datos, y la lista del informe es un subconjunto de ellas.
Pregunta a cada una como un desconocido. Copia una URL, abre una ventana de navegación privada para estar desconectado, y pégala. Lo que ves es exactamente lo que ve en esa dirección cualquiera en internet.
Lee las primeras líneas. Tres cosas pueden volver:
- Un error, un rechazo o
[]. La dirección quiere un login, o no hay nada ahí para alguien desconectado. Sigue. - Ajustes. Flags, interruptores, un color, una lista de funciones activadas, un número de versión. No se nombra a nadie y no se describe nada. Sigue.
- Filas. Un correo, el nombre de una persona, el importe de un pedido, el texto de un mensaje, un teléfono. Párate aquí, esta es la buena.
Si encuentras filas, prueba a cambiar un número de la URL antes de cerrar la
pestaña. Una dirección que entrega el registro de una persona con id=1 y el de
otra distinta con id=2 los está entregando todos, y eso conviene saberlo antes
de decidir cuán urgente es esto.
Nuestro escaneo gratuito hace los dos primeros pasos por ti: lee en el código de tu aplicación las direcciones a las que llama, pide cada una sin login y lista las que respondieron con datos. Tarda unos 20 segundos y no necesita cuenta: escanea tu aplicación. El tercer paso es tuyo, porque eres la única persona que sabe si un nombre de esa lista es un cliente real.
La mayoría de los que encontramos son de la plataforma
En nuestro barrido de agosto de 2026, 3.852 de las 30.926 aplicaciones en las que esta comprobación pudo obtener respuesta tenían al menos una dirección que responde sin login. De ellas, 2.705 eran aplicaciones de Base44, y en Base44 la dirección es casi siempre la misma.
Es /api/consent/config, los ajustes de cookies de la plataforma. En septiembre
volvimos a abrir una muestra de aplicaciones de Base44 señaladas y la única
dirección que respondió fue esa. Devuelve ajustes, es idéntica en todas las
aplicaciones, y ahí dentro no hay datos de nadie. El escáner la está informando
correctamente y el propietario no tiene nada que arreglar.
| Plataforma | Aplicaciones con el hallazgo | Aplicaciones en las que la comprobación respondió |
|---|---|---|
| Base44 | 2.705 | 5.419 |
| Dominios propios | 82 | 955 |
| Bolt | 4 | 1.120 |
| Lovable | 8 | 18.518 |
| v0 | 0 | 1.786 |
Falta una plataforma en esa tabla a propósito. A Replit le corresponde un bloque grande de los hallazgos restantes, y nadie ha vuelto a abrir una muestra de ellos como hicimos con los de Base44, así que todavía no sabemos de quién son esas direcciones. Publicar una cifra como problema del propietario antes de que alguien la haya mirado es justo el camino por el que un ajuste de plataforma de Base44 se convierte en una estadística sobre propietarios negligentes. Todas las cifras de arriba salen de nuestro escaneo de 30.998 aplicaciones vibe-coded en producción, que publica cada una junto a la base sobre la que se midió.
Lee juntos los dos extremos de esa tabla, porque la diferencia es la parte útil.
En Lovable son 8 aplicaciones de 18.518, y en v0 ninguna. Esas aplicaciones no
tienen un pequeño servidor propio: la página habla con Supabase directamente
desde el navegador, así que no hay ningún /api/… tuyo en ninguna parte que
esta comprobación pueda encontrar. Lo que protege los datos en una aplicación
así son las reglas de cada tabla, y ese es otro hallazgo con una
forma propia de salir mal.
Cuándo el JSON son ajustes y cuándo son personas
Una pregunta lo decide: ¿algo en la respuesta describe a una persona?
Los ajustes describen tu aplicación. Un flag que dice si el modo oscuro está activado, la lista de monedas que aceptas, la versión de algo, el texto de un banner de cookies. Publicar eso revela cómo está configurada tu aplicación, y tu aplicación funciona a la vista de todos de todas formas.
Las filas describen a tus usuarios. Un nombre, un correo, lo que alguien compró, lo que escribió, dónde vive. Eso no es una frase sobre configuración, es el contenido del archivador, y importa por lo corriente que resulta llevárselo. No hay allanamiento. La dirección es una línea de texto que funciona en cualquier navegador, así que acaba pegada en un chat de grupo, guardada en las notas de alguien y descargada a intervalos por un script que nadie vigila.
Cerrar uno, y por qué renombrar la ruta no lo cierra
Exige un login en la dirección, y responde con un 401 a quien llame sin tenerlo.
Ese es todo el arreglo, y va en la dirección y en ningún otro sitio. Tres cosas que parecen el arreglo y no lo son:
- Renombrar la ruta, o sacarla de tu código. Con esta hay que tener cuidado, porque el hallazgo desaparece. Quien ya tenía la URL la sigue teniendo, está en historiales de navegación y en registros de servidor, y el archivador sigue abierto, solo que sin la etiqueta delante.
- Estrechar tu cabecera CORS. Eso decide qué otras webs pueden leer una respuesta dentro del navegador de alguien, y nada más la consulta. Un script o un terminal lee la misma dirección de todos modos, y por eso el hallazgo del comodín es otra pregunta.
- Filtrar en la aplicación. Si la página esconde las filas que no debería mostrar, la dirección las sigue enviando. El filtrado tiene que ocurrir antes de que salga la respuesta.
Hay una cuarta opción, y en una página pública suele ser la mejor: dejar de tener el endpoint. Si los datos son solo para tu propia página, la página puede recibirlos mientras se construye, y la dirección sale de tu código con ellos.
Qué aspecto tiene el arreglo en código
Dos cosas, en el handler que responde a la dirección: averiguar quién pregunta, y parar si la respuesta es nadie.
Puede que nunca escribas esto tú, y no pasa nada. Léelo de todos modos, porque es con lo que compruebas que tu builder hizo de verdad lo que le pediste. Esta es la forma de antes, y es lo que encontró el escaneo:
app.get('/api/orders', async (req, res) => {
const orders = await db.orders.findMany()
res.json(orders)
})
Y después:
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)
})
Cambiaron dos cosas y las dos importan. El 401 es la puerta. El where es la
razón por la que la puerta merece la pena: sin él un visitante con sesión puede
leer los pedidos de todos los demás clientes, que es un público más pequeño para
los mismos datos y sigue siendo el equivocado.
En las Edge Functions de Supabase, lee la configuración antes de escribir
nada. Las Edge Functions comprueban el token de quien llama por su cuenta: la
referencia de configuración de Supabase pone verify_jwt en true por defecto,
así que una función que responde a desconocidos es una en la que alguien lo
desactivó, con una línea en supabase/config.toml o desplegando con
--no-verify-jwt:
[functions.orders]
verify_jwt = false
Si tu función debe ser privada, borra esas dos líneas y vuelve a desplegar. Déjalas solo donde el endpoint tenga que responder a cualquiera de verdad, que es el caso que la propia documentación de Supabase usa como ejemplo: un webhook de pagos. Dentro de la función, el SDK actual te da un cliente ya acotado a la persona que llamó:
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)
}),
}
ctx.supabase se ejecuta como quien llamó, así que tus reglas de seguridad a
nivel de fila deciden lo que vuelve, que es la misma protección de la que
depende el resto de tu aplicación y
lo que conviene comprobar aparte.
El prompt para pegar en tu builder
Si construyes en Lovable, Bolt, Cursor, Replit o v0, dale esto. Déjalo en inglés, en el idioma en que estés leyendo esto, porque ese es el idioma en el que razonan estas herramientas, y sustituye las dos partes entre corchetes por lo que diga tu propio informe:
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.
Esa última frase está ahí a propósito. Cuando se le pide a un modelo que haga desaparecer un hallazgo, a veces coge el camino más corto y mueve la dirección, que es el único resultado que parece un éxito al volver a escanear y no cambia nada.
Qué hacer ahora mismo
Qué hacer
- Abre cada dirección señalada en una ventana de navegación privada y lee lo que vuelve. Ese es el paso que el informe no puede dar por ti.
- Si la respuesta son ajustes y la dirección es una que tú nunca escribiste, es de tu builder y no hay nada que arreglar. Busca la ruta en la documentación de tu plataforma antes de gastar nada en ella.
- Si la respuesta contiene un nombre, un correo o un pedido, exige hoy mismo un login en esa dirección y devuelve un 401 a quien no lo tenga.
- No renombres la ruta ni borres la referencia a ella. El hallazgo desaparece y la dirección sigue respondiendo.
- Prueba a cambiar un id en la URL de tu propia aplicación. Una dirección que sirve un registro a un desconocido suele servirlos todos.
Las direcciones de esta lista las escribió alguien que sabía qué había detrás en aquel momento, y lo que cambia es lo que hay detrás. Reeve Care vuelve a pasar este escaneo por tu aplicación en producción de forma periódica y te envía un correo cuando un resultado empeora, que es justo el caso que el repaso desconectado de arriba no puede pillar: qué vigila y cuánto cuesta.
Si prefieres trabajarlo todo de una sentada, la lista de seguridad en 10 minutos cubre esto junto al resto de lo que una aplicación recién lanzada suele dejar abierto, y qué claves son seguras en tu frontend es el otro hallazgo con el que la gente suele llegar.
Preguntas frecuentes
¿Qué significa "endpoint de API abierto" en mi informe?
Significa que leímos el código que tu aplicación envía a un navegador, encontramos dentro una dirección a la que tu aplicación pide datos, y le pedimos datos a esa dirección sin cuenta y sin login. Respondió, y la respuesta era JSON. Ese es todo el hallazgo. Recoge lo que pasó, y no juzga si los datos eran privados, porque nada fuera de tu aplicación puede saberlo.
¿Todo endpoint de API abierto es un problema de seguridad?
No. Muchas direcciones están pensadas para responder a cualquiera: una lista de precios publicada, un artículo, una lista de qué funciones están activadas. El problema es la dirección que devuelve filas que pertenecen a personas, porque lo hace para todo el que tenga la URL. Lee lo que volvió y pregúntate si algo de eso describe a una persona.
Mi informe señala un endpoint que yo no escribí. ¿Qué es?
Normalmente es de tu builder. Las plataformas que le dan a tu aplicación un pequeño servidor propio también le meten unas cuantas direcciones suyas, y esas están en el código de todas las aplicaciones de la plataforma. El ejemplo más claro que hemos medido son los ajustes de cookies de Base44 en /api/consent/config, que suponen 2.705 de los 3.852 hallazgos de nuestro barrido de agosto de 2026. Devuelven ajustes, son idénticos en todas las aplicaciones de Base44, y nada de lo que hay ahí te pertenece.
¿Cómo compruebo qué devuelven los endpoints de mi aplicación?
Abre tu aplicación en producción, pulsa F12, haz clic en Red y recarga. Las peticiones que vuelven como JSON son las direcciones de las que tu aplicación saca datos. Copia cada URL, abre una ventana de navegación privada para estar desconectado, y pégalas de una en una. Lee lo que llega. Un error o una lista vacía está bien. Las filas con nombres, correos o importes de pedidos las puede leer cualquiera que tenga esa dirección.
¿Esconder la ruta lo arregla?
No. Renombrar la dirección, o quitar la referencia a ella de tu código, impide que un escáner la encuentre y la deja respondiendo igual que antes. Quien ya tenía la URL la sigue teniendo, y permanece en historiales de navegación, registros de servidor y todo lo que haya rastreado tu sitio. Se arregla en la dirección: exigir un login y responder a un desconocido con un 401.