← Volver al blog
SeguridadEquipo de Scanaris · 7 min de lectura · 03 de octubre de 2026

Seguridad en Supabase: lo que tienes que revisar antes de lanzar

Supabase te da una base de datos y una API en minutos, y ese es justo el peligro: la API queda expuesta desde el primer momento. Si no configuras la seguridad, cualquiera con tu clave pública puede leer y escribir tus tablas. Esta guía repasa, en el orden en que conviene hacerlo, todo lo que hay que revisar antes de abrir tu app al mundo: RLS y políticas con SQL de ejemplo, qué clave va dónde, Storage, vistas, la configuración de Auth y cómo comprobar al final que de verdad está cerrado.

Cómo expone Supabase tus datos (y por qué eso es bueno)

Delante de tu Postgres hay una capa (PostgREST) que convierte cada tabla del esquema public en un endpoint HTTP. Tu app en el navegador habla con él directamente usando una clave pública. Eso ahorra escribir un backend, pero significa que no hay ningún servidor tuyo en medio que compruebe permisos: las políticas de Row Level Security son tu backend. Si están bien, el diseño es sólido; si faltan, la tabla entera está a una URL de distancia.

1. RLS activado en TODAS las tablas

Row Level Security es la barrera principal de Supabase. Sin ella, la clave pública puede consultar tablas enteras desde el navegador. El fallo típico del vibe coder es crear una tabla nueva rápido —o dejar que la IA la cree— y olvidar activar RLS. Fue exactamente la causa de la vulnerabilidad de Lovable (CVE-2025-48757).

Revisa tabla por tabla en el Table Editor: las que aparecen con "RLS disabled" están abiertas al público. Para no depender del ojo, esta consulta en el SQL Editor lista las que faltan:

select c.relname
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'r' and not c.relrowsecurity;

Y para cada una:

alter table public.nombre_de_la_tabla enable row level security;

No basta con activarlo: una tabla con RLS activo pero sin políticas lo deniega todo (tu app dejará de funcionar hasta que las escribas), y una con una política demasiado permisiva no protege nada.

2. Políticas que reflejan de verdad quién puede qué

Una política using (true) para todos deja la tabla abierta aunque RLS esté activo; la IA las sugiere a menudo "para que funcione". Escribe políticas concretas y separa las reglas por operación, porque a veces quieres permitir leer pero no borrar.

Patrón A: cada usuario sus filas

create policy "notes: leer propias" on public.notes
  for select to authenticated
  using ((select auth.uid()) = user_id);

create policy "notes: crear propias" on public.notes
  for insert to authenticated
  with check ((select auth.uid()) = user_id);

create policy "notes: editar propias" on public.notes
  for update to authenticated
  using ((select auth.uid()) = user_id)
  with check ((select auth.uid()) = user_id);

El with check es lo que impide que alguien inserte o edite una fila a nombre de otro usuario. Envolver auth.uid() en (select …) hace que Postgres lo evalúe una vez por consulta y no una vez por fila; en tablas grandes la diferencia es enorme. Pon un índice en user_id.

Patrón B: pertenencia a un equipo u organización

create policy "projects: miembros del equipo" on public.projects
  for select to authenticated
  using (
    exists (
      select 1 from public.team_members m
      where m.team_id = projects.team_id
        and m.user_id = (select auth.uid())
    )
  );

La tabla team_members también necesita sus políticas, y conviene que solo un administrador pueda insertar en ella.

Patrón C: contenido público de lectura

create policy "posts: lectura pública" on public.posts
  for select to anon, authenticated
  using (published = true);

Es legítimo. Lo que no debe existir es una política de escritura abierta ni columnas con datos personales en una tabla de lectura pública.

3. anon, service_role y las claves nuevas

La clave anon es pública y va en el frontend: está pensada para que RLS la controle. La clave service_role se salta RLS por completo y tiene acceso total. Nunca, jamás, debe aparecer en el navegador ni en una variable NEXT_PUBLIC_, VITE_ o NUXT_PUBLIC_: ese prefijo significa "esto se envía al cliente".

Supabase ha introducido además un par de claves nuevas con nombres que lo dejan claro: sb_publishable_… (para el navegador, sujeta a RLS) y sb_secret_… (solo servidor, salta RLS, revocable por separado). Si empiezas un proyecto hoy, úsalas; si tienes uno antiguo, migra cuando puedas. Usa la secreta solo en el servidor, en Edge Functions o en backends de confianza, y si sospechas que se ha filtrado, rótala de inmediato desde el panel.

4. Storage: los buckets también tienen reglas

El almacenamiento de archivos usa sus propias políticas, sobre la tabla storage.objects. Un bucket público sirve cualquier archivo por URL a quien la tenga, para siempre. Decide qué buckets son públicos de forma consciente (avatares, imágenes de producto) y protege el resto. Un ejemplo para que cada usuario solo lea y suba dentro de su propia carpeta:

create policy "documentos: propios" on storage.objects
  for all to authenticated
  using (
    bucket_id = 'documents'
    and (storage.foldername(name))[1] = (select auth.uid())::text
  )
  with check (
    bucket_id = 'documents'
    and (storage.foldername(name))[1] = (select auth.uid())::text
  );

Sube documentos privados a buckets privados y sírvelos con URLs firmadas de corta duración, no a un bucket público con nombres difíciles de adivinar.

5. Vistas, funciones y esquemas expuestos

Tres sitios por donde se escapan datos aunque las tablas estén bien:

  • Vistas. En Postgres una vista se ejecuta con los permisos de quien la creó, así que una vista sobre una tabla protegida puede devolver filas que las políticas prohibirían. Créalas con with (security_invoker = true) (Postgres 15 o superior) o revisa cada una.
  • Funciones RPC. Una función security definer corre con los permisos de su dueño. Úsala solo cuando lo necesites de verdad, valida los parámetros dentro y limita quién puede ejecutarla con revoke execute … from anon.
  • Esquemas expuestos. En Settings → API puedes elegir qué esquemas publica la API. Lo que no tenga que salir por HTTP, que no esté en la lista.

6. Configuración de Auth que conviene revisar

  • Confirmación de email activada, para que nadie registre cuentas con direcciones ajenas.
  • Límites de intentos de login y de envío de emails: vienen activados; comprueba que nadie los subió "para probar".
  • URLs de redirección permitidas solo hacia tus dominios; una lista abierta permite usar tu login para redirigir a sitios de phishing.
  • Proveedores OAuth: solo los que uses, con el consent screen a nombre de tu marca.
  • Caducidad de los enlaces mágicos y OTP corta; el valor por defecto es razonable, no lo alargues.

7. Secretos y claves fuera del frontend

Además de la clave secreta de Supabase, revisa que no haya claves de terceros (Stripe, correo, IA) incrustadas en el JavaScript del cliente. La IA a veces las coloca ahí para que algo funcione rápido, y quedan visibles para siempre en el bundle. Las llamadas a servicios de pago van desde una Edge Function o desde tu servidor, nunca desde el navegador. Mira los secretos que tu app filtra en el navegador.

8. Entornos: desarrollo no es producción

Usa un proyecto de Supabase distinto para desarrollo, con datos inventados. Un proyecto de pruebas con una copia de los datos reales y las políticas a medio hacer está igual de expuesto que producción, y nadie lo vigila.

Cómo comprobar que está cerrado (la prueba final)

  • Prueba de incógnito. Con la app abierta, copia desde F12 → Network una llamada a /rest/v1/ y ábrela sin sesión. Tiene que devolver una lista vacía o un error.
  • Prueba de "pedir de más". Con sesión, quita el filtro de la URL (?user_id=eq.…). Solo deben volver tus filas.
  • Prueba de dos cuentas. Crea dos usuarios y confirma que ninguno ve, edita ni borra lo del otro, incluidos los archivos de Storage.
  • Security Advisor. En el panel, la sección Advisors lista tablas sin RLS, políticas permisivas y funciones expuestas. Déjala en verde.

Scanaris revisa tu Supabase por ti

Scanaris incluye una auditoría opcional de Supabase que, con una connection string de solo lectura, comprueba tabla por tabla si RLS está activado y si hay políticas, y en el escaneo normal detecta claves expuestas en el frontend, CORS permisivo y endpoints que sirven datos sin sesión. Cada hallazgo trae un prompt listo para pegar en tu IA. Pásalo antes de lanzar y sabrás si dejaste alguna puerta abierta; actívalo en monitorización y te avisará si la IA la vuelve a abrir en una tabla nueva.

Preguntas frecuentes

¿Puedo saltarme RLS poniendo un backend propio delante?

Puedes, usando solo la clave secreta desde tu servidor y nunca la pública en el cliente. Pero entonces tu backend tiene que hacer todas las comprobaciones de permisos que RLS haría. Para la mayoría de apps, RLS es menos trabajo y más difícil de romper.

¿RLS afecta al rendimiento?

Poco si las políticas están bien escritas: (select auth.uid()) en lugar de auth.uid(), índices en las columnas que usan las políticas y sin subconsultas pesadas. Una política mal escrita sí puede hacer lenta una tabla grande.

¿Qué pasa con las tablas que crea la IA después de lanzar?

Nacen sin RLS. Añade a tu rutina (o al prompt de tu IA) "activa RLS y escribe las políticas" cada vez que aparezca una tabla nueva, y usa un re-escaneo automático para enterarte si se olvida.

¿Y tu web, cómo va?

Pásale Scanaris y compruébalo en 30 segundos, gratis.

Escanear mi web

Checks relacionados

Guías de seguridad por stack