RLS desactivado en Supabase: qué revisar antes de activarlo

Vibe Coding Rescue · 2026-07-22 · Datos y seguridad
Última revisión 2026-07-23Incluye SupabaseFuentes oficiales 6

Una advertencia roja de Security Advisor hace que Activar RLS parezca el paso obvio. En una aplicación en producción, ese clic puede bloquear lecturas y escrituras simultáneamente. Aplazarlo mantiene la exposición. Haz un inventario del propósito de la tabla, los privilegios actuales y las políticas existentes, y luego aplica los cambios necesarios como una versión probada.

Revisa cuatro cosas antes de tocar el interruptor

  1. ¿La tabla está expuesta a través de la API de datos?
  2. ¿Qué privilegios GRANT tienen los roles anon y authenticated?
  3. ¿Una política existente abre el acceso de forma amplia mediante una regla como using (true)?
  4. ¿El navegador está usando una clave secreta o una clave de rol de servicio en lugar de una clave publicable?

RLS disabled in public significa que la seguridad por filas está desactivada para una tabla en el esquema public. Un GRANT controla qué operaciones puede intentar un rol sobre la tabla. RLS reduce esas operaciones a las filas permitidas. Ninguna de las capas reemplaza a la otra.

RLS no oculta columnas. Si la misma fila contiene direcciones de correo, notas internas u otros campos que no deben llegar al navegador, no concedas SELECT sin restricciones sobre toda la tabla. Usa permisos de columna, una vista security_invoker de PostgreSQL 15 o superior que exponga solo las columnas necesarias, o una API separada del servidor.

No infieras los valores predeterminados de Supabase en 2026 solo basándote en la fecha de creación del proyecto. Los nuevos valores predeterminados comenzaron a implementarse en proyectos nuevos el 30 de mayo y están programados para llegar a proyectos existentes el 30 de octubre. El cambio afecta los permisos predeterminados GRANT en tablas creadas en el futuro; no revoca automáticamente los permisos en tablas existentes. Revisa directamente la configuración del proyecto y los permisos actuales.

1. Inspecciona los permisos y cada política existente

Abre Database → Security Advisor, identifica la tabla afectada y decide si debe ser legible antes del inicio de sesión, si solo los usuarios autenticados deben poder escribir en ella o si debe quedar reservada al servidor. En el Editor SQL, lista las políticas actuales:


select policyname, permissive, roles, cmd, qual, with_check
from pg_policies
where schemaname = 'public' and tablename = 'profiles';

Agregar una nueva política no hace que el acceso sea más estricto automáticamente. Varias políticas permisivas, el tipo predeterminado, se combinan con OR. Una antigua política using (true) o to public puede abrir el acceso más allá de una nueva política por usuario. Elimina o limita las políticas innecesarias antes de agregar otra. Si ya existe una política con el nombre previsto, inspecciona su definición; usa ALTER POLICY o una migración revisada DROP POLICY en lugar de reejecutar CREATE POLICY ciegamente.

2. Prepara RLS, permisos y políticas juntos

Este ejemplo asume que profiles.user_id almacena el ID del usuario autenticado. Permite que un usuario autenticado seleccione, inserte, actualice y elimine sus propias filas. Adapta la tabla, columnas y reglas al producto, y ejecútalo primero en staging.


begin;

alter table public.profiles enable row level security;

grant select, insert, update, delete
on table public.profiles to authenticated;

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

create policy "Users can insert own rows"
on public.profiles for insert
to authenticated
with check ( (select auth.uid()) = user_id );

create policy "Users can update own rows"
on public.profiles for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );

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

commit;

Para una actualización, using decide si la fila existente puede ser modificada y with check valida la fila después del cambio. PostgreSQL reutiliza la condición using cuando with check se omite, pero escribir ambas hace que la regla prevista sea más fácil de revisar. UPDATE también necesita una política SELECT que permita ver la fila.

Para una tabla reservada al servidor, no inventes una política del cliente. Revoca en su lugar los privilegios de los roles de la API de datos, por ejemplo con revoke all on table public.profiles from anon, authenticated;. Para una tabla que la aplicación necesita, otorga solo las operaciones requeridas explícitamente. Si una operación INSERT usa una secuencia directamente, otorga el permiso mínimo de secuencia por separado. Para datos disponibles antes del inicio de sesión, crea una política dedicada anon y revisa las filas públicas y columnas públicas de forma independiente.

Las tablas creadas en el Editor de Tablas tienen RLS habilitado por defecto. Las tablas creadas mediante SQL puro o Editor SQL requieren que lo habilites tú. Si la interfaz se vuelve vacía después de activar RLS, es probable que las filas no hayan sido eliminadas; las solicitudes suelen ser rechazadas porque no existe una política ni un privilegio GRANT aplicable.

3. Migra todos los consumidores antes de desactivar la clave heredada

Los navegadores deben usar una clave publicable (sb_publishable_...). La clave anon de un proyecto existente también está destinada al uso público, pero migrar al nuevo sistema de claves es preferible. Una clave secreta (sb_secret_...) y la clave heredada service_role omiten RLS y deben permanecer fuera de los clientes.

Crear una nueva clave no revoca la antigua. Haz la migración en este orden:

  1. Reemplaza anon con una clave publicable en web, móvil, escritorio y clientes ya desplegados.
  2. Reemplaza service_role con una clave secreta en servidores, Funciones de Borde, workers, CI/CD, tareas programadas, integraciones externas, Webhooks de Base de Datos, y pg_net.
  3. Verifica si alguna integración debe enviar la nueva clave secreta en la cabecera apikey en lugar de Authorization: Bearer.
  4. Confirma que ninguna solicitud aún use las credenciales antiguas, y luego desactiva las claves heredadas anon y service_role bajo Settings → API Keys.

Si un valor sb_secret_... se filtró, crea una clave de reemplazo, actualiza el servidor y revoca la clave antigua. Trata una clave heredada service_role expuesta de la misma manera: no corrijas una sola ruta del código y des por terminado el trabajo. Completa la migración y desactiva la credencial heredada. Las antiguas versiones de la aplicación, los trabajos programados y los webhooks pueden fallar durante la transición, así que mantén un inventario de consumidores.

4. Prueba con los roles reales y dos usuarios

En el Editor SQL, select auth.uid(); normalmente devuelve null porque no hay JWT de usuario. SET LOCAL solo dura para la transacción actual, así que colócalo entre BEGIN y ROLLBACK. En el código siguiente, el texto entre corchetes significa el UUID del usuario que quieres probar:


begin;
set local role authenticated;
set local request.jwt.claim.sub = '<UUID of the user under test>';
select * from public.profiles;
rollback;

Ese SQL es una verificación rápida de política, no la prueba final. En la aplicación, ejecuta select, insert, update y delete sin iniciar sesión, como usuario A, y como usuario B. Confirma que se deniegue el acceso al usuario A incluso cuando envíe directamente el ID del usuario B, y que las columnas restringidas nunca aparezcan en la respuesta.

Coordina las políticas, los permisos GRANT requeridos y el despliegue de la aplicación como un cambio breve en producción. Si descubres una política ampliamente abierta, no la dejes abierta por conveniencia. Trátala como una prioridad, valida la corrección en staging y prepara SQL de rollback antes de cerrarla.