Saltar al contenido

¿Es segura tu app de Supabase?

¿Puede Supabase guardar datos reales de usuarios? Sí, y está hecho para consultarse desde el navegador. Por eso mismo dos ajustes lo deciden todo.

Vlad Tkachenko5 min de lectura
La tarjeta de revisión de seguridad de Reeve para apps de Supabase, con el logotipo de Supabase sobre un azulejo blanco.

Los logotipos son propiedad de sus respectivos dueños y se muestran solo para indicar compatibilidad.

En resumen

  • Supabase puede guardar datos reales de usuarios, y está hecho para consultarse directamente desde un navegador. Justo por eso las reglas de tus tablas hacen todo el trabajo.
  • La clave publicable pertenece a tu app. sb_secret_… y service_role se saltan todas las reglas que hayas escrito.
  • Las tablas hechas en el Table Editor reciben Row Level Security automáticamente. Las creadas ejecutando SQL no.

Supabase te da una base de datos Postgres de verdad con una API ya puesta delante, y por eso una app puede estar hablando con datos reales veinte minutos después de empezar. Lo que sorprende a la gente más tarde es que esa misma API la puede alcanzar cualquiera, desde donde sea, con credenciales impresas dentro de tu app.

Aquí está lo que guía tras guía cuenta mal: eso no es un defecto, y esconderlo no es el arreglo. Supabase está diseñado para consultarse directamente desde un navegador. La protección siempre estuvo pensada para vivir en otro sitio, y saber dónde es casi todo lo que hay que saber para llevar una app de Supabase con tranquilidad.

Por qué tu base de datos está en internet a propósito

Porque tu app le habla desde el navegador de tu visitante, no desde un servidor que lleves tú.

Piénsalo como una tienda. Tu app es el escaparate, entregado entero a cada visitante. Tu base de datos es el almacén, y con Supabase el almacén tiene adrede su propia puerta a la calle, para que tu escaparate pueda servirse sin un servicio de reparto en medio. Esa puerta es lo que te ahorra escribir un backend.

Lo cual significa que la puerta es pública, y su dirección está en tu app porque tu app tiene que llamar a ella. No existe ninguna disposición en la que esa dirección sea secreta. Así que la pregunta nunca fue «¿pueden los desconocidos llegar a mi base de datos?»: pueden, por diseño. La pregunta es qué les entrega cuando preguntan.

¿La clave de Supabase que hay en mi app tiene que estar ahí?

Una de ellas sí. La otra nunca, y se parecen casi por completo.

Las dos se llaman claves de API. Solo una estuvo pensada para poder leerse.

La clave publicable (sb_publishable_… en los proyectos nuevos, anon en los antiguos) dice a qué proyecto pertenece una petición. No lleva privilegios propios, por lo que la propia documentación de Supabase la describe como segura para empaquetar en páginas web y apps móviles. Encontrarla en tu app no es un hallazgo.

La clave secreta, sb_secret_… o la antigua service_role, es lo contrario. Se salta tus reglas por completo y lee y escribe cada fila de cada tabla, desde donde sea. Su sitio es un servidor, una edge function, un worker: ningún lugar al que llegue un navegador.

Están una junto a otra en la misma página del panel y tienen la misma forma. Si alguna vez se ha pegado una en el código del frontend, renuévala en el panel antes de editar nada, porque borrar la línea no cierra la puerta.

Esa página es Settings y luego API Keys, y contiene cuatro valores en vez de dos.

El ajuste que decide qué entrega la puerta

Row Level Security: un interruptor por tabla que decide, fila a fila, quién puede leer qué. Es todo el motivo por el que una clave publicable es segura.

Tu app y un desconocido llaman a la misma puerta. El ajuste decide quién entra, no las pantallas de tu app.

Apagado, esa clave devuelve la tabla entera a quien pregunte. Encendido y con una política escrita, la propia base de datos filtra cada petición, incluidas las que nunca pasaron por tu app.

Dos detalles deciden si de verdad lo tienes, y ninguno se ve desde tu app:

Cómo se creó la tabla. Supabase activa Row Level Security automáticamente en las tablas hechas en el Table Editor del panel. Las tablas creadas ejecutando SQL no lo reciben y hay que activarlas a propósito. Los archivos de migración, los scripts y los esquemas generados por IA crean tablas ejecutando SQL, así que un proyecto construido así puede tener una fila de tablas desprotegidas que se ven exactamente igual que las protegidas.

Qué dice la política. Activado significa cerrado hasta que una política lo abra. Una política que permite a todo el mundo lo abre del todo mientras el panel sigue informando de que la tabla tiene Row Level Security activado: el estado que parece protegido y no lo está.

El almacenamiento va aparte otra vez. Un bucket público lo puede listar y descargar cualquiera con la dirección de tu proyecto, digan lo que digan las reglas de tus tablas.

Cómo revisar tu propio proyecto de Supabase en unos diez minutos

Authentication → Policies. Lee la lista entera. Cualquier tabla que muestre Row Level Security como desactivado la puede leer cualquiera. Para las que lo muestran activado, abre la política y lee qué permite en realidad.

Settings → API. Confirma que la clave que envía tu app es la publicable. Si alguna vez hubo una clave secreta en el código del frontend, renuévala aquí primero. Si tu panel muestra claves que empiezan por sb_publishable_ y sb_secret_ en vez de anon y service_role, ese es el formato más nuevo, y la misma regla decide cuál de las dos va en tu app.

Storage → Buckets. Comprueba cuáles son públicos y si eso era lo que querías para los archivos que hay dentro.

Y luego mira desde fuera. Todo lo anterior es lo que tu panel dice que está configurado. Qué recibe de vuelta una petición anónima es otra pregunta, y es la que responde nuestro escaneo gratuito: consulta como lo haría un desconocido, sin leer ninguna de tus filas, y te da una nota en unos 20 segundos: comprueba qué expone tu app.

Qué hacer

  • Tu API de Supabase es pública a propósito. La protección son las reglas de tus tablas, nunca el secreto de la dirección.
  • La clave publicable pertenece a tu app. sb_secret_… y service_role nunca, y una filtrada ignora todas las reglas que hayas escrito.
  • Las tablas hechas en el Table Editor reciben Row Level Security automáticamente. Las creadas ejecutando SQL no.
  • Activado no es restringido. Lee la política, no solo el interruptor.
  • Los buckets de almacenamiento tienen sus propios ajustes, así que unas tablas cerradas no dicen nada de tus archivos.

Abre Authentication → Policies y recorre la lista una vez. Las tablas marcadas como desactivado son por donde empezar, y si prefieres recorrer toda la superficie como una lista, la lista de seguridad en 10 minutos es la versión corta. Encuentres lo que encuentres, saber que puedes devolver los datos a su sitio importa tanto como cerrarlos, y eso depende por completo de qué estás respaldando.

Si prefieres que no dependa de que te acuerdes, así funcionan las copias de seguridad automáticas de Supabase.

Qué puede salir mal de verdad con Supabase

Nada de esto significa que hayas hecho algo mal: son las brechas habituales cuando vas rápido. Esto es lo que vale la pena comprobar:

  • Row Level Security (RLS) desactivada

    El RLS es la regla de Supabase sobre quién puede leer o cambiar cada fila de una tabla. Con la clave pública «anon» (que se supone que debe estar en tu app), cualquiera puede consultar tu base de datos directamente. El RLS es lo que le impide ver filas que no son suyas. Si está desactivado, una tabla puede ser totalmente legible, o incluso editable, por cualquiera. Es el ajuste más importante de Supabase, y Reeve lo comprueba contando filas, nunca leyéndolas.

  • Una clave service_role filtrada

    Supabase te da dos claves. La clave «anon» es pública por diseño y segura en el navegador. La clave «service_role» salta todas tus reglas de seguridad y debe vivir solo en un servidor. Si alguna vez acaba en el código del front-end de tu app, alguien puede hacer cualquier cosa con tus datos. Reeve decodifica las claves que encuentra y te dice exactamente cuál está expuesta: la «anon» segura recibe una marca verde, la «service_role» es una alerta roja.

  • Buckets de almacenamiento públicos

    Los archivos que la gente sube viven en «buckets» de Supabase Storage. Un bucket configurado como público significa que cualquiera puede listar y descargar lo que hay dentro, así que las subidas privadas pueden hacerse visibles para todos. Reeve comprueba si tus buckets son listables; nunca descarga los archivos de nadie.

  • Tu API abierta a cualquier sitio (CORS)

    Supabase le da a tu base de datos una dirección web (vía PostgREST). Combinada con un ajuste de compartición demasiado permisivo, otro sitio web podría llamar a tus datos desde el navegador de un visitante. Reeve comprueba si tus endpoints responden a extraños y a otros sitios, sin usarlos nunca para cambiar nada.

  • Tablas y endpoints expuestos sin reglas

    Cada tabla que creas es accesible a través de la API de Supabase; eso es por diseño, y el RLS debe protegerla. Pero una tabla nueva añadida con prisas, antes de fijar sus reglas, puede quedar brevemente (o de forma duradera) abierta. Reeve comprueba qué es realmente accesible desde fuera.

  • Claves o configuración dejadas en la app desplegada

    Más allá de las propias claves de Supabase, las apps a menudo llevan otros ajustes y secretos en su código front-end o en un .env expuesto. Reeve lee el código cargado de tu app y comprueba si hay archivos de configuración accesibles, luego te dice qué valores son seguros de hacer públicos y cuáles no.

Qué es Reeve, y qué no

Reeve es una comprobación gratuita, de solo lectura y desde fuera, como un inspector que prueba las puertas sin entrar. Es rápida y detecta los errores comunes de alto impacto. No es una auditoría de seguridad completa, y una nota limpia no es garantía. Significa que las puertas obvias están cerradas. Todo lo que hace Reeve es pasivo: cuenta filas en vez de leerlas, y nunca descarga tus archivos.

Supabase te da herramientas de seguridad reales (un Security Advisor y un linter de base de datos que señalan problemas de RLS y de exposición justo en el panel), y vale de verdad la pena usarlos. Lo que Reeve añade: la mayoría de quienes construyen sobre Supabase viven en su creador de apps, no en el editor SQL, y el asesor habla en lenguaje de desarrollador. Reeve comprueba toda tu app desde fuera, como un atacante llegaría a tus datos, y explica lo que encuentra en palabras sencillas. Y si prefieres no vigilarla tú mismo, podemos hacerlo nosotros.

¿Quieres que se gestione, no solo que se compruebe?

Reeve Care sigue vigilando tu app, respalda tus datos y te ayuda a arreglar cosas cuando se rompen, para que puedas seguir creando en vez de preocuparte.

Conoce Reeve Care

Preguntas frecuentes

¿Qué es el RLS de Supabase y realmente lo necesito?

El RLS (Row Level Security) decide quién puede ver o cambiar cada fila de tus tablas. Como tu app envía una clave pública «anon» que puede consultar la base de datos directamente, el RLS es lo que impide que los datos de un usuario sean visibles para todos. Sí: para cualquier tabla con datos reales, lo necesitas activado. Reeve comprueba si lo está, sin leer tus datos.

¿Es segura de exponer la clave anon de Supabase?

Sí. La clave «anon» está diseñada para vivir en tu front-end, y por sí sola solo hace lo que tus reglas RLS permiten. La clave que nunca debe exponerse es la «service_role», que ignora todas las reglas. Reeve decodifica las claves de tu app y te dice cuál es cuál.

¿Qué pasa si se filtra mi clave service_role?

La clave service_role salta todas las reglas de seguridad, así que cualquiera que la tenga puede leer, cambiar o borrar todos tus datos. Si está en tu código front-end, trátala como comprometida: rótala en el panel de Supabase y muévela a un servidor. Reeve marca una clave service_role expuesta como un problema crítico.

¿Puede Reeve comprobar mi Supabase sin la contraseña de mi base de datos?

Sí. Reeve solo usa lo que tu app ya expone públicamente: la misma clave anon y los mismos endpoints que usa el navegador de cualquier visitante. Nunca necesita la contraseña de tu base de datos, nunca inicia sesión como administrador y nunca lee ni descarga tus filas o archivos.

¿Por qué Supabase deja que un navegador hable con mi base de datos?

Porque eso es el producto. Supabase pone una API delante de Postgres para que tu app pueda consultar datos sin que tú escribas y alojes un backend, y eso es lo que hace tan rápido construir con él. La seguridad no viene de esconder esa API, que no se puede esconder. Viene de que Postgres se niega a devolver filas a las que el visitante no tiene derecho.

¿Activar Row Level Security en una tabla la hace privada?

La deja cerrada hasta que escribas una política, que es el punto de partida seguro. Pero una política que permite a todo el mundo la vuelve a abrir mientras el panel sigue mostrando la tabla con Row Level Security activado. Activado y restringido son dos estados distintos, y solo uno se ve de un vistazo.

¿Qué tablas tienen más papeletas de estar desprotegidas?

Las creadas ejecutando SQL en vez de haciendo clic en el Table Editor: archivos de migración, scripts y esquemas escritos por un builder de IA. El Table Editor activa Row Level Security automáticamente. El SQL no, y después nada señala la diferencia.

¿Mi almacenamiento de Supabase está cubierto por Row Level Security?

El almacenamiento tiene sus propios ajustes aparte, así que una base de datos bien cerrada no te dice nada de tus archivos. Un bucket marcado como público lo puede listar y descargar cualquiera que conozca la dirección de tu proyecto, enlace o no tu app a lo que hay dentro.

Escrito por

Vlad Tkachenko

Fundador de Reeve

Me paso el día mirando apps creadas con Lovable, Bolt, v0, Cursor y Replit, y la corta lista de errores que aparecen en ellas una y otra vez.

Más sobre el autor

¿Creaste tu app en una herramienta concreta? Aquí tienes el mismo repaso honesto para:

← Ver todas las guías de seguridad de creadores

Comprobación externa automatizada, no una auditoría completa. La ausencia de hallazgos no es garantía de seguridad.