Todas las guías
Severidad CríticaSeguridad de bases de datos

Row Level Security (RLS) en Supabase: protege tus tablas

Supabase expone tus tablas de Postgres directamente al navegador a través de su Data API, autenticada con la anon key (publishable) pública. Las políticas de Row Level Security (RLS, seguridad a nivel de fila) son las que deciden qué filas puede leer o modificar cada usuario. Una tabla del esquema public sin RLS puede leerla — y a menudo modificarla — cualquiera que abra tu sitio.

Qué es

Todo proyecto de Supabase incluye una anon key (o sb_publishable_) pensada para vivir en tu frontend. Identifica tu proyecto, no a tu usuario: cualquiera puede copiarla de tu bundle de JavaScript y enviar sus propias peticiones a https://<project>.supabase.co/rest/v1/, saltándose tu interfaz por completo.

Row Level Security es una funcionalidad de Postgres que asocia políticas a una tabla: condiciones SQL que se evalúan para cada fila en cada SELECT, INSERT, UPDATE y DELETE. Con RLS activado y sin políticas, no se puede acceder a nada a través de la API. Cada política que agregas abre exactamente una puerta, por ejemplo "los usuarios con sesión iniciada pueden leer las filas cuyo user_id coincide con su propio id".

Por qué importa

Una política ausente o demasiado amplia es una filtración de datos completa, no un bug menor: emails de usuarios, perfiles, pedidos, mensajes privados — lo que haya en la tabla — se pueden volcar con un solo comando curl. Las herramientas de programación con IA suelen crear tablas mediante migraciones SQL, donde RLS está desactivado por defecto, y generan políticas provisionales permisivas como using (true) solo para que la app funcione.

La service_role key (sb_secret_) se salta RLS por completo. Si termina en el código del cliente, todas las políticas que escribiste dan igual: quien la tenga obtiene acceso total de lectura y escritura a tu base de datos.

Cómo solucionarlo

Encuentra las tablas sin RLS

Ejecuta esto en el editor SQL de Supabase para listar todas las tablas del esquema public expuesto que todavía tienen RLS desactivado. El Security Advisor del dashboard reporta el mismo problema como "RLS disabled in public".

select tablename
from pg_tables
where schemaname = 'public'
  and rowsecurity = false;
Activa RLS y escribe políticas solo para el propietario

Activa RLS en cada tabla y luego agrega una política por operación. Limita las políticas al rol authenticated y compara la columna de propietario de la fila con auth.uid(). Los INSERT y UPDATE necesitan with check para que los usuarios no puedan escribir filas que pertenecen a otra persona.

alter table public.profiles enable row level security;

create policy "Users read own profile"
  on public.profiles for select
  to authenticated
  using ((select auth.uid()) = user_id);

create policy "Users update own profile"
  on public.profiles for update
  to authenticated
  using ((select auth.uid()) = user_id)
  with check ((select auth.uid()) = user_id);
Evita los errores de políticas más comunes

Sustituye las políticas provisionales como using (true) por condiciones reales. No bases la autorización en user_metadata — los usuarios pueden editarlo ellos mismos; usa app_metadata o una tabla de roles. Las vistas se saltan RLS salvo que se creen con security_invoker = true, y los buckets de storage necesitan sus propias políticas sobre storage.objects.

Prueba desde fuera con la anon key

Consulta tu API exactamente como lo haría un atacante: solo con la clave pública y sin sesión de usuario. Si esto devuelve filas que no deberían ser públicas, tus políticas son demasiado abiertas.

curl "https://<project>.supabase.co/rest/v1/profiles?select=*" \
  -H "apikey: <anon-key>" \
  -H "Authorization: Bearer <anon-key>"
Mantén la service_role key en el servidor

Usa la service_role key (sb_secret_) solo en código de servidor, como rutas de API o Edge Functions, nunca detrás de un prefijo NEXT_PUBLIC_ o VITE_. Si alguna vez llegó al navegador, rótala desde el dashboard.

Preguntas frecuentes

¿Es seguro que mi anon key de Supabase sea visible en el navegador?

Sí: la anon key (publishable) está diseñada para ser pública, pero solo si cada tabla expuesta tiene RLS activado con políticas correctas. La clave por sí misma no concede nada que tus políticas no permitan.

Activé RLS y ahora mi app devuelve resultados vacíos. ¿Está roto?

Es RLS funcionando como debe: sin políticas, se deniega cada fila. Agrega políticas para las operaciones que tu app necesita, limitadas a los usuarios correctos, en lugar de volver a desactivar RLS.

¿AppSafe prueba mis políticas de RLS?

AppSafe no inicia sesión en tu base de datos, así que no puede evaluar tus políticas: usa las comprobaciones con SQL y curl de arriba. Lo que sí hace es analizar tu JavaScript en busca de claves de Supabase. Si reporta una, decodifica la parte central del JWT y revisa el claim role: anon es lo esperado en el navegador; service_role es una filtración crítica.

¿Tu app está afectada?

AppSafe verifica este y docenas de problemas más en un solo escaneo gratis.

Escanear mi app gratis