Fundamentos de seguridad
Supabase "RLS disabled in public": lo que el aviso no ve
Supabase marca "RLS disabled in public" como error. No dice nada de la política de lectura que deja tu tabla igual de abierta a cualquiera.

En resumen
- "RLS disabled in public" significa una sola cosa: una tabla de tu esquema public tiene Row Level Security apagado, así que puede leerla cualquiera que tenga la dirección de tu proyecto y tu clave publicable.
- No dice nada de una tabla donde encendiste el interruptor y luego escribiste una política que deja leer a todo el mundo. Existe una regla para las políticas siempre verdaderas y esa regla se salta las de lectura a propósito.
- Arregla las tablas marcadas y después comprueba el resto desde fuera, porque el advisor lee tu configuración y nunca le pregunta a tu base de datos qué recibe de verdad un desconocido.
Abriste el Security Advisor en tu dashboard de Supabase, o alguien te pegó su salida delante, y ahí está en rojo: RLS disabled in public. Debajo, una línea por tabla, con las palabras de Supabase:
Table public.profiles is public, but RLS has not been enabled.
Aquí está la parte que guía tras guía cuenta mal: vaciar esa lista no significa que nadie pueda leer tus datos. El advisor tiene una regla aparte para una política que deja entrar a todo el mundo, y esa regla se salta justo la forma que escribe un constructor de IA cuando desatasca tu app. Así que la lista que acabas de arreglar y la lista de tablas que un desconocido puede leer son dos listas distintas, y una de ellas no aparece en ningún sitio de tu dashboard.
¿Qué significa "RLS disabled in public"?
Una tabla de tu esquema public tiene Row Level Security apagado, y la consecuencia es que cualquiera con la dirección de tu proyecto puede leer todas sus filas.
Las dos mitades de esa frase necesitan explicación. El esquema public es donde va una tabla cuando nadie dice otra cosa, y es la parte de tu base de datos que Supabase publica en la web: cada proyecto responde peticiones en una dirección propia, y la clave que hace falta para hablar con ella está en el código que tu web envía a cada visitante. Row Level Security es el interruptor que decide si tus reglas se consultan antes de entregar filas. Con él apagado no hay nada que consultar, así que la respuesta siempre es que sí.
Esa combinación es la razón de que esto se reporte como error y no como aviso. El advisor clasifica sus hallazgos, y este es el escalón más alto.
Piensa en el advisor como un inspector con un portapapeles. Lee tu documentación con cuidado y lo hace bien. Nunca prueba el picaporte. Nada de lo que hay en ese panel es el resultado de una petición que alguien haya hecho a tu base de datos.
¿Activar RLS va a romper mi app?
Sí, de inmediato, y eso es el interruptor funcionando.
El arreglo que te da Supabase es una línea, y la ejecutas en el SQL Editor:
alter table public.profiles enable row level security;
La propia documentación de Supabase es clara sobre lo que pasa después: los datos dejan de ser accesibles por la API con una clave publicable mientras no haya políticas definidas. Así que tus listas vuelven vacías, tus pantallas se quedan en blanco, y el error del advisor queda sustituido por una entrada más discreta que dice que la tabla tiene RLS activado pero ninguna política.
Esa es la puerta cerrada sin nadie todavía en la lista. Lo siguiente que hace casi todo el mundo es añadir una política que deja leer a cualquiera, porque eso es lo que hace que las pantallas vuelvan.
El fallo que el aviso no está buscando
Una tabla con Row Level Security encendido y una política de lectura cuya
condición es USING (true) entrega exactamente las mismas filas al mismo
desconocido. El advisor no la marca.
No es un descuido. Supabase sí tiene una regla para las políticas siempre verdaderas, y deja fuera las de lectura a propósito. Su propia descripción de la regla lo dice:
SELECT policies with `USING (true)` are intentionally excluded as this
pattern is often used deliberately for public read access.
En el caso general la regla tiene razón. Un catálogo de productos, una lista de artículos publicados, un mapa de locales: eso está pensado para que lo lea cualquiera, y marcarlo entrenaría a todos los desarrolladores de la plataforma para ignorar el panel. Lo que ningún linter puede saber es si la tabla que está mirando guarda locales o clientes.
Y una política que deja leer a todos es la forma más rápida de volver a poner en
pie una app rota, que es por lo que un constructor de IA tira de ella. Pídele a
Cursor o a Lovable que arregle las pantallas vacías y
FOR SELECT USING (true) es una respuesta habitual. Tu app carga, el error
desaparece, el panel se calla, y la tabla se lee igual que antes de empezar.
| Lo que el advisor puede ver | Cómo lo reporta | Lo que recibe un desconocido con tu clave publicable |
|---|---|---|
| RLS apagado | Error | Todas las filas |
| RLS encendido, sin ninguna política | Info | Nada |
RLS encendido, FOR SELECT USING (true) | Nada en absoluto | Todas las filas |
RLS encendido, FOR ALL USING (true) | Aviso | Todas las filas, y puede cambiarlas |
RLS encendido, USING (auth.uid() = user_id) | Nada en absoluto | Solo las suyas |
Las dos filas donde no se reporta nada en absoluto son el par que merece un momento. Una es una tabla que nadie fuera de tu app puede tocar. La otra es una tabla que puede leer cualquiera. Tu dashboard calla igual con las dos.
Cómo se escribió esa política, y por qué sustituirla tabla por tabla, es el tema de Row Level Security está activado y tu tabla sigue pública. Si esa misma política también tiene que dejar que tu app guarde datos, el error que te encuentras después es new row violates row-level security policy.
Por qué la tabla que hiciste con una migración nunca te avisó
Porque el Table Editor enciende Row Level Security por ti y SQL no.
Supabase documenta la diferencia sin rodeos: las tablas creadas con el Table
Editor del dashboard tienen RLS activado por defecto, y las creadas con SQL
directo hay que activarlas de forma explícita. Una tabla que creaste a base de
clics arranca protegida. Una tabla que llegó por un archivo de migración, un
supabase db push, un fragmento en el SQL Editor o una sentencia que tu
constructor de IA ejecutó por ti arranca abierta.
Esa segunda vía es como crea tablas un constructor de IA. Escribe el SQL y lo ejecuta por ti, así que nunca viste la casilla y nunca la viste sin marcar.
La costumbre que lo cierra es poner la línea en la migración, al lado de lo que protege:
create table public.profiles (
id uuid primary key references auth.users,
full_name text
);
alter table public.profiles enable row level security;
Cómo comprobar las tablas que el advisor dio por buenas
Hazle a tu base de datos la pregunta que hace un desconocido: manda una petición desde fuera, con la clave publicable que viaja dentro de tu app, y mira qué vuelve.
Esta es la diferencia sobre la que gira todo el artículo. El advisor lee tu configuración. Una petición lee tus filas. Son dos preguntas distintas, y una tabla puede aprobar la primera y suspender la segunda, que es exactamente lo que hace una política de lectura permisiva.
Hicimos esa petición a gran escala. Entre el 12 y el 14 de agosto de 2026 pasamos nueve comprobaciones externas a 30.998 apps en producción publicadas desde Lovable, Base44, Replit, v0 y Bolt. De las 3.680 apps con Supabase donde la comprobación pudo completarse, 2.096 tenían al menos una tabla que respondió con filas a una petición anónima. Eso es un 57 %, y es una proporción de las apps de las que pudimos obtener una respuesta clara, no de todo lo que escaneamos. Entre las apps hechas con Bolt fueron 27 de 35, una muestra lo bastante pequeña como para leerla como una dirección y no como una tasa. El conjunto de datos completo está publicado, y de qué es proporción ese 57 % repasa el recuento.
Puedes hacer tú mismo esa petición contra una tabla con un navegador y tu propia clave publicable. Si prefieres no ir tabla por tabla, nuestro escaneo gratuito le pregunta a tu app en producción desde fuera y te dice qué tablas respondieron. Tarda unos 20 segundos y no necesita cuenta: escanea tu app.
¿Necesito una copia de seguridad antes de cambiar políticas de RLS?
Para cualquier tabla en la que tu app escriba, sí. Para una tabla de solo lectura que solo estás cerrando, el riesgo es que tu app se quede en blanco, no que se pierdan datos.
Aquí conviene separar dos cosas distintas, porque solo una tiene que ver con el arreglo.
Lo que una política permisiva ya permitió. Si la regla de la tabla era
FOR ALL USING (true) en vez de FOR SELECT, entonces quien la encontrara
podía cambiar y borrar filas además de leerlas, y cerrarla hoy no hace nada
respecto a ayer. Esa versión suele aparecer como un mensaje de soporte sobre
datos que cambiaron solos, o como una tabla que de pronto está vacía.
El arreglo en sí. Reescribir políticas en una docena de tablas es un cambio sobre una base de datos en producción, escrito por las mismas herramientas que provocaron el problema. Una migración que borra una política y la vuelve a crear mal es un martes cualquiera, y el camino de vuelta es una copia de cómo estaba todo hace una hora.
En un plan de pago de Supabase tienes la copia de anoche en la consola. En el plan gratuito no hay nada a lo que volver, porque el plan gratuito no hace ninguna copia automática. Si ese es tu caso, haz una antes de tocar una política: cómo respaldar una base de datos de Supabase en el plan gratuito es la versión de diez minutos.
De qué guarda una copia Reeve Care
De tu base de datos de Supabase, copiada según un calendario, guardada fuera de tu cuenta de Supabase, cifrada y releída antes de que la fecha de tu panel se mueva. Los archivos que subieron tus usuarios viajan con ella en cuanto conectas una clave de Storage.
Dos límites, dichos por delante. Las copias son solo de Supabase: si tus datos viven en otro sitio lo decimos, en vez de venderte una suscripción que vigila una caja vacía. Y la clave de Storage se pide aparte, porque Supabase no emite una clave de solo lectura para archivos, así que la que copia tus subidas también puede escribir. La que copia tu base de datos no. Conectarla es opcional, y la base de datos se respalda igual.
La restauración es la parte que importa para este artículo. Poner una copia antigua encima de una base de datos en producción es el botón que más miedo da del producto, así que Care copia primero el estado actual y solo después reproduce el que elegiste. La restauración tiene su propio deshacer.
La otra mitad de Care es a la que este artículo lleva apuntando. Una política que se aflojó durante una migración no es algo que encuentres mirando, así que la misma comprobación externa se repite según un calendario y te avisa cuando cambia la respuesta. El uptime, un informe mensual y el escaneo van en la misma suscripción.
Lo que una copia no hace es escribir tus políticas, y ninguna copia convierte una tabla abierta en cerrada. Esas tablas siguen siendo cosa tuya. Lo que cambia la copia es lo que pasa cuando el arreglo sale torcido. Qué respalda Reeve en Supabase, cada cuánto y qué hace una restauración dibuja el ciclo entero, y los planes y sus precios están en la página de precios.
Qué hacer esta semana
Qué hacer
- Vacía primero las entradas "RLS disabled in public". Son las tablas donde no se consulta absolutamente nada, y el arreglo es una línea
alter tableen cada una. - Después abre Authentication → Policies y lee la condición de cada política que haya sobrevivido. Un
USING (true)en una política de lectura es invisible para el advisor y está de par en par para un desconocido. - Decide tabla por tabla si te sentirías cómodo publicando su contenido en una página. Esa es la pregunta que el linter no puede responder por ti, y con una política de lectura permisiva es la única que importa.
- Pon
alter table ... enable row level security;en cada migración que cree una tabla, y vuelve a ejecutar el advisor después de cada una. El valor por defecto del dashboard solo se aplica a las tablas que creas con clics. - Haz una copia de tu base de datos antes de reescribir políticas en una tabla en producción, y mira en qué plan de Supabase estás para saber si ya tienes una.
Empieza por la tabla que más vergüenza te daría como página pública. La lista de seguridad en 10 minutos cubre esto junto con el resto de lo que conviene comprobar en una app recién lanzada, y la guía de seguridad de Supabase repasa qué más suele quedarse abierto.
Preguntas frecuentes
¿"RLS disabled in public" es un error o un aviso?
Un error, y es el nivel más alto que usa el advisor. La entrada dice "Table public.<name> is public, but RLS has not been enabled." Las dos entradas relacionadas son más discretas: una tabla con el interruptor encendido y ninguna política se reporta como INFO, y una política cuya condición es siempre verdadera se reporta como WARN. Esos niveles describen la confianza del linter en lo que puede ver en tu configuración, no el tamaño de tu problema.
¿Activar Row Level Security va a romper mi app?
Al instante, sí, y eso es el interruptor haciendo su trabajo. Supabase documenta que los datos dejan de ser accesibles por la API con una clave publicable mientras no haya políticas definidas, así que en cuanto ejecutas la línea tus listas vuelven vacías y tus pantallas se quedan en blanco. La app vuelve cuando añades una política que dice quién puede ver qué filas. El error que hay que evitar es añadir una que permita a todo el mundo, porque esa versión también hace que la app funcione.
Activé RLS y ahora no carga nada. ¿Qué ha pasado?
No se ha roto nada. Con Row Level Security encendido y sin políticas escritas, Postgres (el motor de base de datos que hay debajo de Supabase) rechaza todas las peticiones, incluidas las de tu propia app, y el advisor cambia su error por una entrada INFO que dice que la tabla tiene RLS activado pero ninguna política. Escribe una política para las filas que tu app debe mostrar, empezando por la que compara al visitante identificado con la columna de propietario de la fila.
Mi tabla no está marcada y aun así cualquiera puede leerla. ¿Por qué?
Lo más probable es que Row Level Security esté encendido y haya una política de lectura cuya condición es USING (true). El advisor sí tiene una regla para las políticas siempre verdaderas, y se salta las de lectura a propósito, porque el acceso público de lectura es algo razonable para un catálogo de productos o una lista de artículos publicados. Nada en tu dashboard sabe si tu tabla guarda locales o clientes, así que esa comprobación es cosa tuya.
¿Necesito Row Level Security si mi app solo habla con mi propio servidor?
Si el navegador de verdad nunca habla con Supabase y no hay ninguna clave publicable en el código que tu web envía a los visitantes, entonces la Data API no es una vía de entrada y las políticas no son lo que protege esas tablas. Eso es raro en una app hecha con Lovable, Bolt o v0, porque esos constructores conectan el navegador directamente a Supabase por defecto. Abre tu propia web, mira si la URL del proyecto y la clave publicable están en la página, y deja que esa respuesta lo decida.