FixVibe

// docs / baas security / supabase storage

Lista de comprobación de seguridad de buckets de Supabase Storage: 22 elementos

Supabase Storage es una capa fina sobre un bucket compatible con S3 más el mismo modelo de seguridad a nivel de fila que la base de datos. Eso significa que los mismos errores de RLS que afectan a las tablas afectan al acceso a archivos — más algunos específicos de storage que aparecen cuando las herramientas de codificación con IA configuran las subidas. Esta lista de comprobación son 22 elementos en cinco secciones: configuración del bucket, políticas RLS, validación de subidas, URLs firmadas e higiene operativa. Cada uno es verificable en menos de 15 minutos.

Cada elemento de abajo es esencial. Para los fundamentos de RLS, consulta Escáner de RLS de Supabase. Para la clase de exposición de claves adyacente al storage, consulta Clave de rol de servicio de Supabase expuesta en JavaScript.

Configuración del bucket

Empieza con los valores predeterminados correctos. Un bucket mal configurado filtra archivos tanto si tu RLS es correcto como si no.

  1. Marca cada bucket como privado por defecto. En el panel de Supabase → Storage → Buckets, desactiva el interruptor Public bucket a menos que tengas una razón explícita (assets de marketing, avatares públicos sin PII). Los buckets públicos se saltan RLS para operaciones de lectura — cualquiera con el nombre del bucket puede listar y descargar.
  2. Pon un límite de tamaño de archivo estricto en cada bucket. Panel → Configuración del bucket → Límite de tamaño de archivo. 50 MB es un predeterminado razonable para subidas de usuarios; súbelo deliberadamente para casos de uso de vídeo / archivos grandes. Sin un límite, una sola subida maliciosa puede agotar tu cuota de storage o tu ancho de banda mensual.
  3. Restringe los tipos MIME permitidos por bucket. Lista de tipos MIME permitidos — lista de permitidos explícita, no de bloqueados. image/jpeg, image/png, image/webp para buckets solo de imágenes. Nunca permitas text/html, application/javascript ni image/svg+xml en un bucket de contenido de usuarios — se ejecutan en el navegador cuando se sirven vía URL firmada.
  4. Usa un bucket por tipo de contenido, no uno compartido. Los ajustes por bucket (tamaño, tipos MIME, políticas RLS) son la granularidad de la que dispones. Un bucket user-avatars, un bucket document-uploads y un bucket public-assets son más fáciles de blindar que un único bucket mixto.
  5. Verifica la configuración CORS si hay subidas desde el frontend. Si los usuarios suben directamente desde el navegador a una URL firmada, los CORS del bucket deben listar tu origen de producción. * solo es aceptable para buckets públicos — nunca para buckets con PII de usuarios.

Políticas RLS sobre storage.objects

Supabase Storage almacena los metadatos de los archivos en la tabla storage.objects. RLS sobre esa tabla controla quién puede leer, subir, actualizar o borrar archivos. Sin RLS, la bandera público/privado del bucket es tu única protección.

  1. Confirma que RLS está habilitado en storage.objects. SELECT rowsecurity FROM pg_tables WHERE schemaname = 'storage' AND tablename = 'objects'; debe devolver true. Supabase lo habilita por defecto en proyectos nuevos; verifica que no se haya desactivado.
  2. Escribe una política SELECT acotada a auth.uid() para buckets privados. CREATE POLICY "users_read_own_files" ON storage.objects FOR SELECT USING (auth.uid()::text = (storage.foldername(name))[1]);. La convención es guardar los archivos bajo [user-id]/[filename] y usar storage.foldername() para extraer el propietario de la ruta.
  3. Escribe una política INSERT que imponga la misma convención de ruta. CREATE POLICY "users_upload_own" ON storage.objects FOR INSERT WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);. Sin WITH CHECK, un usuario autenticado puede subir a la carpeta de otro usuario.
  4. Añade políticas UPDATE y DELETE si tu aplicación soporta edición o borrado de archivos. Cada comando necesita su propia política. Saltarse DELETE significa que los usuarios autenticados no pueden eliminar sus propios archivos; saltarse UPDATE significa que las sobreescrituras de archivos fallan en silencio.
  5. Prueba el aislamiento entre usuarios en dos sesiones de navegador. Inicia sesión como Usuario A, sube un archivo, copia la ruta. Inicia sesión como Usuario B en otro navegador, intenta obtener el archivo vía la API REST. La respuesta debe ser 403 o 404, nunca 200.
sql
-- Confirm RLS on storage.objects
SELECT rowsecurity
FROM   pg_tables
WHERE  schemaname = 'storage' AND tablename = 'objects';

-- SELECT policy: scope reads to the owning user's folder.
CREATE POLICY "users_read_own_files"
  ON storage.objects
  FOR SELECT
  USING (auth.uid()::text = (storage.foldername(name))[1]);

-- INSERT policy: enforce the [user-id]/[filename] path convention.
CREATE POLICY "users_upload_own"
  ON storage.objects
  FOR INSERT
  WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);

Validación de subidas

Valida cada subida en el servidor, incluso cuando el bucket tenga restricciones de MIME y tamaño. Las herramientas de codificación con IA generan validación solo en el cliente por defecto; eso no protege nada.

  1. Verifica de nuevo el tipo MIME en el servidor a partir de los bytes reales del archivo, no de la cabecera Content-Type. Usa una librería como file-type (Node) o sniffing de bytes mágicos. Un atacante puede declarar Content-Type: image/jpeg en un archivo que en realidad es un payload poliglota de HTML / JavaScript.
  2. Elimina los metadatos EXIF de las imágenes subidas. EXIF puede contener coordenadas GPS, números de serie de dispositivos y marcas de tiempo. Usa sharp con .withMetadata(false) o exif-parser para limpiar antes de almacenar.
  3. Rechaza los SVG que contengan etiquetas script o handlers onload. SVG es XML — y muchas aplicaciones generadas por IA permiten subidas de SVG como "solo una imagen". Usa DOMPurify en el servidor o rechaza las subidas de SVG por completo.
  4. Usa nombres de archivo deterministas e imposibles de adivinar. No conserves el nombre de archivo original. Usa un UUID o un hash del contenido. Los nombres originales filtran información ("passport_scan_2024_01_15.jpg") y los nombres predecibles permiten la enumeración.

URLs firmadas

Las URLs firmadas son el modo en que los clientes acceden a los buckets privados. La caducidad, el ámbito del bucket y lo que se registra importa.

  1. Pon la caducidad de las URLs firmadas en 1 hora o menos por defecto. La función createSignedUrl(path, expiresIn) del SDK JS de Supabase toma segundos. Nunca uses valores como 31536000 (un año) — la URL se convierte en un enlace semi-público permanente.
  2. Nunca almacenes URLs firmadas en tu base de datos. Genera nuevas en el servidor en cada solicitud. Una URL firmada almacenada con caducidad de 1 año que se filtra vía un volcado de base de datos otorga acceso a largo plazo.
  3. Registra la generación de URLs firmadas, no solo las subidas de archivos. Si más tarde sospechas de un compromiso, necesitas saber quién generó qué URL y cuándo. Registra auth.uid() + bucket + ruta del objeto + marca de tiempo.
  4. Usa la opción downloadAs al servir archivos subidos por usuarios. createSignedUrl(path, expiresIn, { download: '.jpg' }) fuerza una cabecera Content-Disposition: attachment para que el archivo se descargue en lugar de renderizarse — derrota la clase de ejecución HTML / SVG / HTML-en-PDF.

Higiene operativa

La configuración de storage se deteriora con el tiempo. Estos cuatro elementos operativos mantienen la superficie ajustada.

  1. Audita los buckets trimestralmente. Panel → Storage → Buckets. Confirma que el estado público/privado y las listas de tipos MIME coinciden con lo que la aplicación espera. Los buckets creados "temporalmente" se vuelven permanentes si nadie los elimina.
  2. Monitorea las operaciones de listado anónimas. Los logs de storage (Panel → Logs → Storage) registran las solicitudes LIST. Un pico de solicitudes de listado anónimas contra un bucket privado significa que alguien lo está sondeando desde fuera.
  3. Establece una política de retención para subidas efímeras. Los buckets temporales (vista previa de imagen, borradores de subida) deberían auto-eliminarse tras 24-72 horas mediante una función programada. La retención indefinida es un pasivo bajo las obligaciones de minimización de datos del GDPR / CCPA.
  4. Ejecuta un escaneo de FixVibe mensual. La verificación baas.supabase-storage-public sondea buckets que responden a GET + LIST anónimos. Se añaden buckets nuevos; los antiguos cambian de visibilidad — solo el escaneo continuo detecta el desplazamiento.

Próximos pasos

Ejecuta un escaneo de FixVibe contra tu URL de producción — los listados de storage anónimos aparecen bajo baas.supabase-storage-public. Combina esta lista de comprobación con Escáner de RLS de Supabase para la capa de tablas y Clave de rol de servicio de Supabase expuesta en JavaScript para la adyacencia de exposición de claves. Para configuraciones erróneas de storage en otros proveedores BaaS, consulta Escáner de configuraciones erróneas de BaaS.

// escanea tu superficie de baas

Encuentra la tabla abierta antes que otra persona lo haga.

Introduce una URL de producción. FixVibe enumera los proveedores de BaaS con los que habla tu aplicación, identifica sus endpoints públicos y reporta lo que un cliente no autenticado puede leer o escribir. Gratis, sin instalación, sin tarjeta.

  • Plan gratuito — 3 escaneos al mes, sin tarjeta de registro.
  • Identificación pasiva de BaaS — no se requiere verificación de dominio.
  • Supabase, Firebase, Clerk, Auth0, Appwrite y más.
  • Prompts de corrección con IA en cada hallazgo — pégalos de vuelta en Cursor / Claude Code.
Lista de comprobación de seguridad de buckets de Supabase Storage: 22 elementos — Docs · FixVibe