RLS en Supabase: cómo configurarlo y probar si su app filtra datos
Qué es el RLS, en una frase
El RLS (Row Level Security, seguridad a nivel de fila) es la regla de la base de datos que decide qué filas puede leer, crear, modificar o borrar cada persona. Sin ella, quien puede acceder a la tabla accede a la tabla entera.
Por qué importa en Lovable, Bolt y Base44
Las apps hechas con estas herramientas suelen hablar con la base de datos directamente desde el navegador. Para eso, la clave publicable de Supabase viaja junto con el sitio. Está hecha para quedar expuesta, pero con una condición: Supabase pide activar el RLS en todas las tablas antes de publicar (Supabase, API keys).
La documentación de Supabase resume el riesgo: una tabla en un schema expuesto, sin RLS, puede ser leída y modificada por cualquier rol con permisos sobre ella (Supabase, Row Level Security). Y en proyectos antiguos, las tablas nuevas del schema public pueden nacer con todos los permisos otorgados a los roles del cliente.
Los ejemplos de abajo usan una tabla pedidos con una columna user_id, que guarda el dueño de cada fila. Reemplácelos por los nombres de su app.
Paso 1: encuentre las tablas expuestas
En el panel de Supabase, abra el Security Advisor. También se ejecuta desde la CLI con supabase db advisors (Supabase, Database Advisors). Las alertas que importan aquí:
| Alerta | Qué significa |
|---|---|
0013_rls_disabled_in_public |
Tabla pública con RLS desactivado. Quien tiene la URL del proyecto lee, edita y borra las filas |
0007_policy_exists_rls_disabled |
Hay política, pero el RLS está desactivado. La política no tiene efecto |
0008_rls_enabled_no_policy |
RLS activado y ninguna política. La API no devuelve nada |
0010_security_definer_view |
Vista que corre con los permisos de quien la creó y pasa por encima del RLS |
0002_auth_users_exposed |
Vista que expone la tabla de usuarios del login |
Empiece por las de nivel ERROR: 0013, 0010 y 0002.
En Lovable Cloud, la lista de políticas está en More → Database → Manage → Policies. Es solo de lectura: los cambios se le piden a Lovable en el chat (Lovable, Database access). Los pasos de abajo le sirven para saber qué pedir y qué revisar.
Paso 2: active el RLS
En el SQL Editor:
alter table public.pedidos enable row level security;
Atención: a partir de aquí, nada pasa por la clave publicable hasta que exista una política (Supabase, Row Level Security). Si la app ya está en línea, dejará de mostrar los pedidos. Haga los pasos 2 a 4 juntos, de preferencia en un proyecto de prueba.
Paso 3: revise los permisos de los roles
Supabase usa dos roles para quien llega desde el navegador:
anon: quien no inició sesión;authenticated: quien inició sesión.
Quite todo y devuelva solo lo necesario. Si solo los usuarios con sesión trabajan con pedidos:
revoke all on table public.pedidos from anon, authenticated;
grant select, insert, update, delete on table public.pedidos to authenticated;
Paso 4: escriba una política por operación
La documentación de Supabase recomienda una política para cada operación, siempre con to, para indicar a qué rol aplica. Para el caso “cada usuario solo trabaja con sus propios pedidos”:
create policy "leer los propios pedidos"
on public.pedidos for select
to authenticated
using ( (select auth.uid()) = user_id );
create policy "crear pedidos a nombre propio"
on public.pedidos for insert
to authenticated
with check ( (select auth.uid()) = user_id );
create policy "modificar los propios pedidos"
on public.pedidos for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
create policy "borrar los propios pedidos"
on public.pedidos for delete
to authenticated
using ( (select auth.uid()) = user_id );
using y with check no son lo mismo:
usingdecide qué filas que ya existen puede ver, modificar o borrar la persona.with checkdecide cómo puede quedar la fila después de creada o modificada.
En el update, escriba los dos, como hace la documentación de Supabase. Si falta el with check, PostgreSQL usa el using también para la fila nueva (PostgreSQL, CREATE POLICY). Funciona, pero oculta la intención: quien lea la política después no sabe si la omisión fue a propósito.
Un error común es crear solo la política de select y creer que la tabla está protegida. Cada operación necesita la suya.
Paso 5: corrija las vistas
Una vista creada en el schema public corre, por defecto, con los permisos de quien la creó e ignora el RLS de quien consulta. La corrección que indica Supabase es crear la vista con security_invoker=on (Supabase, Database Advisors):
create view public.resumo_pedidos
with (security_invoker = on) as
select user_id, count(*) as total
from public.pedidos
group by user_id;
Nunca exponga auth.users en una vista pública. Si necesita el nombre y la foto del usuario, cree una tabla de perfil con su propio RLS.
Paso 6: cree índices en las columnas de las políticas
Las políticas corren en cada consulta. Para no volver lenta la app, Supabase pide dos cosas (Supabase, Row Level Security):
- Un índice en cada columna usada en las políticas:
create index pedidos_user_id_idx on public.pedidos using btree (user_id);
- Escribir
(select auth.uid())en lugar deauth.uid(). Así Postgres calcula el valor una vez por consulta, y no una vez por fila.
Paso 7: pruebe si todavía filtra
Haga las pruebas en su propio proyecto.
Prueba 1: como un extraño. Use la clave publicable, la misma que está en el sitio, y llame a la API REST de Supabase (Supabase, REST API):
curl "https://SU-PROYECTO.supabase.co/rest/v1/pedidos?select=*" \
-H "apikey: SU_CLAVE_PUBLICABLE"
Lo esperado, con el revoke del paso 3, es un error de permisos o una lista vacía. Si aparecen pedidos, cualquier visitante del sitio puede obtener lo mismo.
Prueba 2: como otro cliente. Cree los usuarios A y B. Con A, cree un pedido. Entre con B e intente ver, editar y borrar el pedido de A desde la app, cambiando el ID en la dirección si la app usa IDs en la URL. Lovable pide esta misma prueba antes de publicar (Lovable, Security best practices).
Al final, vuelva a ejecutar el Security Advisor. Las alertas del paso 1 deben haber desaparecido.
El error “new row violates row-level security policy”
Este error aparece después de activar el RLS y es una buena señal: la base de datos rechazó una fila. Según la documentación de PostgreSQL, cuando la expresión del with check no es verdadera para la fila nueva, la base devuelve un error en lugar de grabar (PostgreSQL, CREATE POLICY).
Casi siempre la causa es una de estas:
- no hay política de
insertpara el rol que está grabando; - la app no envía el
user_id, o envía uno distinto al del usuario con sesión; - la persona no inició sesión, y la solicitud llega como
anon.
No lo resuelva con una política que acepta todo (with check (true)). Corrija la app para que envíe el user_id correcto, o complete la columna con el valor por defecto auth.uid().
El mismo error en storage significa que falta una política en el bucket de archivos. La lógica es la misma.
Cuidado con la clave secreta y las Edge Functions
La clave secreta (sb_secret_..., o la antigua service_role) pasa por encima de todas las políticas. Supabase indica que nunca se use en el navegador, en una app distribuida ni en el control de versiones (Supabase, API keys).
Su lugar es una función de servidor. Pero ahí el RLS ya no protege nada: la función debe verificar por su cuenta quién la llamó y qué puede hacer esa persona (Lovable, Security best practices). Revise toda Edge Function que use la clave secreta.
Si encuentra la clave secreta en el código del sitio, reemplácela: cree una nueva, sustitúyala en todos los lugares y solo después desactive la antigua.
Pida el diagnóstico de Heartscale
En el diagnóstico de Heartscale, nuestro equipo lee todas las tablas, políticas, vistas y funciones de su Supabase o Lovable Cloud. También prueba el acceso con la clave pública y revisa dónde se usa la clave secreta. Usted recibe la lista de lo que está abierto, por gravedad, y el precio de la corrección.
El diagnóstico cuesta US$ 79 y se descuenta si contrata la corrección. Solo trabajamos en apps de quien demuestra ser su dueño.