Volver al blog
13 de mayo de 2026

Mala configuración de RLS de Supabase: Cómo la ausencia de una política expuso los perfiles de todos los usuarios

Viktor Bulanek
Founder & CTO, Penetrify
MSc IT Security · 20+ years in security · 4x Ex-CTO

La aplicación parecía lista para producción. UI limpia, integración de Stripe funcional, un flujo de incorporación pulido. Había sido destacada en Product Hunt, consiguió más de 200 usuarios en la primera semana y ya estaba generando MRR. El fundador la había construido en 48 horas durante un fin de semana de hackathon usando Next.js y Supabase, la pila de elección para cualquiera que quiera lanzar rápido.

Ocho minutos después de que comenzara el escaneo de Penetrify, identificamos un hallazgo Crítico: el registro de perfil de cada usuario —nombre, correo electrónico, metadatos de la cuenta— era legible por cualquier otro usuario autenticado a través de la API REST autogenerada de Supabase. No se requería sofisticación del atacante. Simplemente intercambie su ID de usuario por otro en la URL.

Esta es la historia de cómo sucedió, por qué es más común de lo que cualquiera quiere admitir y exactamente cómo fue la solución.


Qué es Supabase Row Level Security — Y qué sucede cuando está desactivada

Supabase está construido sobre PostgreSQL y expone sus tablas de base de datos directamente a través de una API REST (mediante PostgREST) y una biblioteca cliente de JavaScript. Esto es realmente potente para el desarrollo rápido: puede consultar su base de datos desde el frontend sin escribir una sola ruta de API.

El mecanismo que impide que los usuarios lean los datos de los demás se llama Row Level Security (RLS). RLS es una característica de PostgreSQL que le permite definir políticas que controlan qué filas un usuario de base de datos dado puede SELECT, INSERT, UPDATE o DELETE. En una aplicación Supabase, normalmente escribiría una política como:

-- Solo permitir a los usuarios leer su propio perfil
CREATE POLICY "Users can view own profile"
  ON profiles
  FOR SELECT
  USING (auth.uid() = user_id);

Cuando RLS está habilitada y una política como esta está implementada, una consulta para el perfil de otro usuario devuelve cero filas — la base de datos aplica el control de acceso a nivel de fila, antes de que cualquier dato llegue a la aplicación.

Cuando RLS está deshabilitada o no existe ninguna política, la tabla se comporta como si cada fila fuera pública para cualquier solicitud autenticada. La API REST de Supabase, accesible en https://[project].supabase.co/rest/v1/profiles, devolverá todas las filas de la tabla —los datos de cada usuario— a cualquiera que tenga un JWT válido de ese proyecto.


Lo que encontramos: Cinco hallazgos, dos de ellos Críticos

El escaneo se ejecutó durante ocho minutos en modo rápido contra la aplicación en vivo. Aquí están los hallazgos en orden de severidad:

CRÍTICO — Supabase RLS no habilitada en la tabla de perfiles

La tabla de perfiles tenía RLS deshabilitada. Cualquier usuario autenticado podía consultarla a través del endpoint REST de Supabase y recuperar todos los registros de usuario. La solicitud que lo demostró:

GET /rest/v1/profiles?select=*
Authorization: Bearer [cualquier JWT de usuario válido]

HTTP/1.1 200 OK
[
  {"id": "uuid-1", "email": "user1@example.com", "full_name": "...", ...},
  {"id": "uuid-2", "email": "user2@example.com", "full_name": "...", ...},
  ... (todos los más de 200 registros de usuario)
]

Según el GDPR, esto es una exposición de datos personales que afecta a cada usuario registrado. La clave anónima de Supabase está incrustada en el paquete JavaScript del frontend por diseño — es seguro exponerla públicamente cuando RLS está configurada correctamente, porque la clave por sí sola no otorga acceso elevado. Sin RLS, se convierte en una clave maestra para todos los datos de usuario.

CRÍTICO — Verificación de correo electrónico no aplicada en endpoints protegidos

Supabase permite registros con correo electrónico y contraseña por defecto, y envía un correo electrónico de confirmación para verificar la dirección. Sin embargo, las rutas de la API de backend de la aplicación no estaban verificando si el usuario solicitante había confirmado su correo electrónico antes de otorgar acceso a la funcionalidad protegida.

Un atacante podría registrarse con victim@example.com (una dirección de correo electrónico de un usuario real), omitir por completo la verificación de correo electrónico y acceder inmediatamente a las rutas protegidas de la API de la aplicación como si fuera el propietario de ese correo electrónico. Combinado con el problema de RLS, esto significaba que un atacante no autenticado podría extraer toda la base de datos de usuarios creando una cuenta desechable con cualquier dirección de correo electrónico.

MEDIO — IDOR en /api/export

La aplicación tenía un endpoint de exportación que aceptaba un ID de usuario como parámetro de consulta:

GET /api/export?userId=abc123

No se realizó ninguna verificación de propiedad en el lado del servidor. Cualquier usuario autenticado podría exportar los datos de cualquier otro usuario sustituyendo su propio ID por el de un objetivo. Los ID de usuario estaban expuestos en las respuestas de la API en toda la aplicación, lo que hacía que la enumeración fuera trivial.

MEDIO — Sin limitación de tasa en el endpoint de inicio de sesión

El endpoint de inicio de sesión aceptaba solicitudes de autenticación a aproximadamente 500 solicitudes por segundo sin estrangulamiento ni bloqueo. Un ataque de relleno de credenciales contra la base de usuarios de la aplicación no encontraría fricción.

MEDIO — Tokens JWT almacenados en localStorage sin rotación

Los JWT de Supabase se almacenaban en localStorage, accesibles para cualquier JavaScript que se ejecutara en la página. No se produjo rotación de tokens en los cambios de privilegios. Un ataque XSS exitoso en cualquier página le daría a un atacante una sesión persistente y válida.


Por qué sucede esto: La trampa de los valores predeterminados de Supabase

Esta no es una historia sobre un desarrollador descuidado. Es una historia sobre los valores predeterminados.

Cuando creas una nueva tabla en Supabase, RLS está deshabilitado por defecto. La propia documentación de Supabase lo describe claramente y recomienda habilitarlo, pero los ejemplos de inicio rápido —los que los desarrolladores realmente siguen al construir a las 2 a.m. durante un hackathon— a menudo omiten RLS por brevedad. Ves una consulta que funciona en el ejemplo, copias el patrón, lo implementas.

El panel de Supabase muestra un icono de advertencia amarillo en las tablas sin RLS. Es fácil pasarlo por alto cuando estás concentrado en la interfaz de usuario, la integración de pagos, el flujo de incorporación. No hay error, no hay fallo en tiempo de ejecución, no hay síntoma obvio. Todo funciona perfectamente. Los usuarios pueden registrarse, iniciar sesión y usar la aplicación. La vulnerabilidad es completamente silenciosa.

Los errores de seguridad más peligrosos son aquellos que se parecen exactamente a un comportamiento correcto.

Este patrón aparece en casi todas las aplicaciones respaldadas por Supabase que escaneamos y que fueron construidas para la velocidad. No porque a los desarrolladores no les importe la seguridad, sino porque la presión de tiempo de un hackathon o un sprint de lanzamiento no deja espacio para leer cada sección de la documentación.


La solución: Dos horas, sin reescritura de código

El fundador solucionó los cinco hallazgos en menos de dos horas la misma noche en que llegó el informe. Esto es exactamente lo que se hizo:

RLS — 15 minutos en el panel de Supabase

Habilitar RLS y añadir políticas adecuadas no requirió cambios de código. En el panel de Supabase, bajo Editor de tablas → perfiles → Políticas RLS:

-- Habilitar RLS en la tabla (un interruptor en el panel)
-- Luego añadir políticas:

CREATE POLICY "Los usuarios pueden ver su propio perfil"
  ON profiles FOR SELECT
  USING (auth.uid() = user_id);

CREATE POLICY "Los usuarios pueden actualizar su propio perfil"
  ON profiles FOR UPDATE
  USING (auth.uid() = user_id);

-- Para acceso de administrador (si es necesario):
CREATE POLICY "El rol de servicio tiene acceso completo"
  ON profiles
  USING (auth.role() = 'service_role');

Después de habilitar RLS y añadir estas políticas, la consulta masiva devolvió cero filas para cualquier usuario autenticado que realizara una solicitud de datos que no fueran suyos.

Verificación de correo electrónico — una comprobación de middleware

Las rutas de la API de Next.js ya estaban leyendo el objeto de usuario de Supabase del JWT en cada solicitud. Añadir la aplicación de la verificación de correo electrónico fue una comprobación de una sola línea en el middleware de autenticación:

const { data: { user } } = await supabase.auth.getUser()
if (!user?.email_confirmed_at) {
  return res.status(403).json({ error: 'Email verification required' })
}

IDOR en /api/export — arreglo de middleware de una línea

La solución fue reemplazar el parámetro userId proporcionado por el usuario con el propio ID del usuario autenticado, extraído del JWT verificado:

// Antes (vulnerable)
const userId = req.query.userId

// Después (corregido)
const { data: { user } } = await supabase.auth.getUser()
const userId = user.id  // siempre el usuario autenticado — no puede ser suplantado

Limitación de tasa — limitación de tasa integrada de Vercel

La aplicación estaba alojada en Vercel. Añadir limitación de tasa al endpoint de inicio de sesión requirió añadir el paquete @upstash/ratelimit y envolver la ruta — aproximadamente 20 líneas de código. El fundador implementó esta solución a la mañana siguiente.


La Exposición al GDPR

El problema de RLS afectó a más de 200 usuarios registrados. Sus registros de perfil completos — direcciones de correo electrónico, nombres de visualización y cualquier otro campo almacenado en la tabla de perfiles — eran legibles por cualquier otro usuario autenticado durante todo el tiempo que la aplicación había estado activa.

Según el Artículo 33 del GDPR, una violación de datos personales que sea "probable que resulte en un riesgo para los derechos y libertades de las personas físicas" debe ser notificada a la autoridad de control pertinente dentro de las 72 horas siguientes a tener conocimiento de ella. Si esta violación en particular cruzó ese umbral dependería de la sensibilidad de los datos almacenados y de cuántos datos de usuarios fueron realmente accedidos — pero la ventana de exposición estaba abierta.

El fundador rotó todos los valores de configuración sensibles, habilitó RLS y solucionó los problemas restantes la misma noche. No se encontró evidencia de acceso no autorizado en los registros de Supabase. El problema fue contenido antes de que se convirtiera en un incidente.


Cómo Verificar Tu Propia Aplicación Supabase

Si estás ejecutando una aplicación respaldada por Supabase, aquí tienes una auditoría rápida que puedes hacer en cinco minutos:

  1. Abre el panel de control de Supabase → Editor de tablas. Cualquier tabla sin un icono de escudo verde tiene RLS deshabilitado. Si esa tabla contiene datos de usuario, es probable que esté expuesta.
  2. Abre las herramientas de desarrollador de tu navegador en tu aplicación de producción → pestaña Red → filtra por la URL de tu proyecto Supabase. Busca solicitudes a /rest/v1/[tablename]?select=*. Si ves solicitudes que devuelven muchos registros, verifica si deberían estar limitadas al usuario autenticado.
  3. Verifica tu flujo de verificación de correo electrónico. Registra una nueva cuenta, omite el correo electrónico de confirmación e intenta acceder directamente a partes protegidas de tu aplicación. Si puedes acceder a funcionalidades protegidas sin confirmar tu correo electrónico, tu backend no está aplicando la verificación.
  4. Verifica tus endpoints de exportación/descarga. Cualquier endpoint que acepte un ID de usuario como parámetro debe verificarlo contra el JWT del usuario autenticado. Si el parámetro puede ser sustituido libremente para acceder a los datos de otro usuario, eso es un IDOR.

Estas cuatro verificaciones toman menos de diez minutos y cubren las brechas de seguridad más comunes de Supabase que vemos en aplicaciones de producción.


El Patrón General

Este estudio de caso no es inusual. Es representativo de lo que encontramos en la mayoría de las aplicaciones respaldadas por Supabase que fueron construidas durante un hackathon, un sprint de lanzamiento rápido o por un fundador individual sin experiencia en seguridad.

La combinación de herramientas rápidas, una excelente experiencia de desarrollador y configuraciones predeterminadas públicas que favorecen la facilidad de uso sobre la seguridad significa que muchas aplicaciones se lanzan con estas brechas. Las brechas son solucionables — a menudo en minutos, rara vez requiriendo algo más que una política SQL o una verificación de middleware de una línea. La parte difícil es saber que existen en primer lugar.

El equipo de Supabase ha realizado un trabajo significativo para hacer que RLS sea más prominente en el panel de control y la documentación. Pero la brecha entre "la documentación dice que se habilite esto" y "cada aplicación en producción realmente lo tiene habilitado" sigue siendo amplia. Hasta que las pruebas de seguridad automatizadas formen parte de la lista de verificación de lanzamiento estándar, este patrón seguirá apareciendo.

Frequently Asked Questions

¿Qué tipos de vulnerabilidades detecta Penetrify?

Penetrify detecta todas las categorías de vulnerabilidades del OWASP Top 10, incluyendo inyección SQL, XSS, CSRF, IDOR, autenticación rota, configuraciones de seguridad incorrectas y exposición de datos sensibles. También prueba la seguridad de APIs, la gestión de sesiones y configuraciones incorrectas comunes en Supabase, Firebase y Bubble.

¿Cuánto tiempo dura un test de penetración con IA?

Un escaneo rápido se completa en 15–30 minutos. Un escaneo estándar dura 1–2 horas con mayor cobertura. Un escaneo profundo puede durar varias horas en aplicaciones complejas.

¿Qué incluye un informe de Penetrify?

Cada informe incluye un resumen ejecutivo, una puntuación general de seguridad, hallazgos clasificados por severidad (Crítico, Alto, Medio, Bajo), pasos de reproducción detallados y orientación concreta de remediación escrita para desarrolladores, no para responsables de cumplimiento.

Related articles

CI/CD Penetration Testing: Cómo integrar la seguridad en cada despliegue
Aprenda cómo integrar Penetration Testing en su pipeline de CI/CD. Cubre SAST, DAST, puertas de calidad y pruebas impulsadas por IA sin ralentizar la entrega.
Análisis Autónomo de Vulnerabilidades OWASP: Cómo la IA Reemplaza las Pruebas de Seguridad Basadas en Reglas
Aprenda cómo el escaneo autónomo de vulnerabilidades OWASP utiliza IA para ir más allá de la coincidencia de firmas. Cubre el OWASP Top 10 2025, las pruebas agénticas y por qué los escáneres basados en reglas no son suficientes.
Simulación de Cadenas de Ataque de Múltiples Pasos: Por Qué el Escaneo de una Sola Vulnerabilidad No es Suficiente
Aprenda cómo la simulación de cadenas de ataque de múltiples pasos encuentra los exploits encadenados que los escáneres de vulnerabilidades pasan por alto. Ejemplos reales, mapeo de MITRE ATT&CK y guía de implementación.

Explore more

Compare alternatives →Security glossary →CI/CD integration →Security statistics →
Volver al blog