Seguridad en las subidas de archivos para aplicaciones prototipo: salvaguardas prácticas
Seguridad en subidas de archivos para prototipos: establece límites de tamaño, valida el tipo real de archivo, almacena en privado, escanea en busca de malware y evita fugas por buckets públicos.

¿Qué puede salir mal con las subidas en un prototipo?
Las subidas de archivos son una vía de ataque favorita porque cruzan muchas fronteras a la vez: tu app, tu almacenamiento y aquello que luego lee el archivo. Un botón de "subir" aparentemente simple puede convertirse en una forma de ejecutar código en un navegador, filtrar archivos privados o generar una factura enorme en la nube.
La mayoría de los problemas vienen de atajos propios de prototipos. Los equipos confían en la extensión del nombre de archivo (como .png), omiten comprobaciones cuando un archivo es “suficientemente pequeño” o suben directamente a un bucket compartido en la nube con permisos amplios. Otro atajo común es servir el archivo subido desde el mismo lugar donde se almacenó, sin pensar en control de acceso o en el comportamiento del navegador.
Cuando las subidas están descuidadas, los resultados son previsibles:
- Toma de cuentas (por ejemplo, un archivo que provoca un fallo en el manejo de imágenes/PDF)
- Fugas de datos (una subida “privada” acaba pública, o los usuarios pueden adivinar URLs)
- Facturas enormes en la nube (archivos grandes, reintentos repetidos o bots abusando del endpoint)
- Daño a la marca (malware alojado bajo tu dominio o bucket)
Un modelo mental sencillo ayuda: cada flujo de subida tiene cuatro pasos: aceptar, validar, almacenar y servir.
- Aceptar es donde pones límites y bloqueas el abuso obvio.
- Validar es donde confirmas qué es realmente el archivo, no lo que dice ser.
- Almacenar es donde los permisos importan más, porque un bucket mal configurado puede exponerlo todo.
- Servir es donde cabeceras y comprobaciones de acceso evitan que tu app se convierta en un servicio de alojamiento de archivos.
Ejemplo: tu prototipo permite a usuarios “subir una foto de perfil”. Un atacante sube avatar.png que en realidad es HTML o un script. Si lo guardas en un bucket público y lo sirves con el Content-Type incorrecto, puede ejecutarse en el navegador de otra persona y robar datos de sesión.
Establece límites claros de tamaño y ritmo primero
La mayoría de los incidentes con subidas empiezan con “pondremos límites después”. Los límites son algunos de los controles más simples que puedes añadir y te protegen de facturas sorpresivas, ralentizaciones y abuso.
Empieza con lo que los usuarios realmente necesitan. Si solo necesitas fotos de perfil, un límite de 5–10 MB suele ser suficiente. Si aceptas vídeos o archivos de diseño, elige un límite mayor a propósito y espera añadir más salvaguardas.
No confíes en un único ajuste. Pon límites en varios lugares para que una mala configuración no lo anule todo:
- Un tamaño máximo por archivo
- Un tamaño total máximo por solicitud (para que 20 archivos “pequeños” no se conviertan en algo enorme)
- Timeouts de subida
- Límites diarios por usuario (archivos/día o MB totales/día)
- Límites de ráfaga (subidas/minuto)
Los timeouts importan más de lo que se les da crédito. Sin ellos, un atacante puede filtrar datos lentamente y bloquear tus workers. Si usas cargas con prefirmado directamente al almacenamiento, pon una expiración corta para que la URL no pueda reutilizarse para siempre.
Haz que los mensajes de error sean claros para que los usuarios reales puedan arreglar el problema rápido. Di qué falló y cuál es el límite (por ejemplo, “Archivo demasiado grande. Máx. 10 MB.”). Mantén “demasiadas subidas hoy” separado de “tiempo de subida agotado”, porque suelen significar cosas diferentes.
Valida el tipo real del archivo (no solo el nombre)
Un nombre como invoice.pdf no te dice casi nada. Cualquiera puede renombrar invoice.exe a invoice.pdf y subirlo. Trata las extensiones como una pista, no como una regla.
Los navegadores también envían un MIME declarado (por ejemplo, image/png). Revísalo, pero no confíes en él. Es dato controlado por el usuario.
Confirma el tipo desde el contenido del archivo
Usa “magic bytes” (el encabezado del archivo) para identificar qué es realmente el archivo. Un PNG real tiene una firma específica al inicio del archivo. Esto atrapa trucos comunes donde el nombre y el MIME declarado dicen una cosa, pero el contenido es otra.
Mantén la decisión simple: acepta solo los tipos exactos que soportas y rechaza todo lo demás. Una allowlist es más segura que intentar bloquear tipos “malos”.
Para muchos prototipos, una lista blanca razonable de inicio es image/jpeg, image/png y application/pdf. Añade text/plain solo si tienes un caso de uso claro y ten cuidado al renderizarlo.
Ten cuidado con ZIP y formatos de Office
Los archivos ZIP y los documentos de Office (DOCX/XLSX/PPTX) son de mayor riesgo porque son contenedores. Pueden ocultar scripts, macros o estructuras sorprendentes. Si debes aceptarlos, trátalos como “requieren comprobaciones extras”: límites más estrictos, escaneo de malware y no servirlos directamente.
Ejemplo concreto: si alguien sube profile.png pero los magic bytes indican que es un ejecutable de Windows, recházalo inmediatamente y registra el intento.
Maneja nombres de archivo y rutas con seguridad
Muchos errores en subidas no tienen que ver con malware. Tienen que ver con confiar demasiado en nombres de archivo. Si tu app usa el nombre proporcionado por el usuario para construir una ruta, una subida simple puede sobrescribir archivos, romper páginas o filtrar datos.
Trata cada nombre de archivo como entrada no confiable. Los usuarios pueden incluir caracteres de ruta, caracteres de control o Unicode confusos que parecen normales en pantalla pero se resuelven distinto en disco.
Reglas más seguras para nombres de archivo
El patrón más seguro es ignorar el nombre del usuario para almacenamiento. Genera un nombre del lado servidor (como un ID aleatorio o UUID) y guarda el nombre original solo como texto para mostrar en la base de datos tras limpiarlo.
Reglas prácticas que previenen la mayor parte de los problemas con nombres:
- Genera tu propio nombre de almacenamiento y decide tú la extensión
- Elimina separadores de ruta (
/,\\), puntos iniciales y caracteres invisibles - Normaliza Unicode para que los parecidos no creen duplicados raros
- Bloquea dobles extensiones (como
photo.jpg.exe)
Entradas de ejemplo para diseñar contra: ../../app.env o avatar.png\u202Egnp.exe. Si guardas esos nombres directamente, puedes escribir fuera de la carpeta prevista u ocultar un ejecutable tras un nombre que parece inocuo.
Mantén las subidas fuera de rutas públicas
Nunca almacenes subidas de usuarios dentro de la carpeta pública de tu app. Ponlas en una ubicación privada (un bucket privado o una ruta de disco privada) y sírvelas a través de un endpoint de descarga controlado.
También separa las subidas sin procesar de las salidas procesadas. Guarda el archivo original en un lugar y las imágenes redimensionadas o las vistas previas convertidas en otro. Hace que los permisos y la limpieza sean más sencillos.
Almacena subidas de forma segura con buckets privados y permisos estrictos
El valor predeterminado más seguro es sencillo: trata cada subida como privada hasta que tengas una razón clara para hacerla pública. La mayoría de las “fugas en prototipos” ocurren porque el almacenamiento se deja en public-read por comodidad y nunca se corrige.
Mantén el contenido de usuarios separado de los assets estáticos de tu app. Pon el contenido de usuarios en un bucket (o contenedor) y los assets del sitio (logos, CSS, archivos de build) en otro. Así, un error de permisos en las subidas no expone toda la app y tu pipeline de build no puede sobrescribir archivos de usuarios.
Restringe quién puede escribir
Sube a través de un pequeño servicio del lado servidor con los mínimos privilegios posibles. Ese servicio debería poder escribir nuevos objetos y leer solo lo que necesite. Evita permisos que permitan listar todo, borrar todo o cambiar la política del bucket.
Apunta a algunos ajustes aburridos por defecto: objetos privados, buckets separados para dev/staging/prod, cifrado habilitado donde se soporte y logs de auditoría activados para políticas y acceso a objetos.
Permite descargas sin hacer archivos públicos
Cuando un usuario necesite descargar un archivo, usa URLs firmadas de corta duración generadas por tu servidor. Eso da acceso limitado en el tiempo sin poner el objeto como público.
Mantén secretos fuera del cliente. No envíes claves de acceso, permisos de escritura ni tokens de administrador en código del navegador.
Procesa y sirve subidas sin exponer a los usuarios
Subir un archivo es solo la mitad del riesgo. La otra mitad es lo que hace tu app después: procesar, generar vistas previas y servir.
Un valor predeterminado seguro es tratar cada nueva subida como no confiable y ponerla en cuarentena hasta que terminen las comprobaciones. Si algo se escapa, no se servirá públicamente por accidente.
Para imágenes, evita servir el original. Redimensiona y re-codifica (decodifica la imagen y vuelve a escribirla como un PNG o JPEG nuevo). Esto elimina muchas cargas ocultas y metadatos problemáticos.
Para documentos, impón límites estrictos en páginas, tamaño y tiempo de extracción. Los PDFs y archivos de Office pueden ser enormes, anidados o malformados. Falla cerrando si el procesamiento tarda demasiado. Si necesitas vistas previas, generar una vista segura en imagen suele ser más seguro que renderizar el documento en el navegador.
Nunca renderices HTML o SVG proporcionado por el usuario directamente. Si debes aceptarlo, conviértelo primero a un formato seguro (como una imagen ráster) o permite solo la descarga con cabeceras que impidan la ejecución inline.
Al servir archivos, unas pocas reglas hacen la mayor parte del trabajo:
- No sirvas subidas desde el origen principal de tu app
- Usa URLs firmadas o un endpoint de descarga que verifique autorización
- Fuerza la descarga para tipos riesgosos y añade cabeceras para prevenir ejecución inline
- Rastrea el estado (en cuarentena, aprobado, rechazado, eliminado) y registra eventos clave (usuario, IP, hash, decisión)
Ejemplo: un fundador sube un “logo.svg” para una landing. Si lo renderizas inline, puede ejecutarse en algunos navegadores. Si lo conviertes a PNG y sirves solo el PNG, el riesgo baja mucho.
Opciones de escaneo de malware y compensaciones prácticas
El escaneo de malware no siempre es obligatorio para un prototipo, pero se vuelve útil rápidamente cuando las subidas se comparten con otros usuarios, las abre tu equipo o se procesan para vistas previas. Si tu app maneja facturas, currículums, ZIPs, archivos de Office o cualquier cosa que se descargue, el escaneo es una parte práctica de la seguridad de subidas.
Si las subidas son solo imágenes usadas como avatars, a veces puedes omitir el escaneo al principio y reducir el riesgo re-codificando imágenes y rechazando todo lo que no sea una imagen real. Una vez que tengas usuarios reales, el escaneo suele ser un seguro barato.
La mayoría de los equipos eligen una de tres aproximaciones: escaneo gestionado en su stack cloud, una API de escaneo de terceros o antivirus autohospedado en un worker. La "mejor" elección suele ser la que realmente vas a mantener en funcionamiento y monitorizar.
Un valor predeterminado seguro es escanear al subir antes de cualquier procesamiento (thumbnailing, parsing, generación de vista previa) y permitir servir solo después de que el archivo esté marcado como limpio. Si añades escaneo más tarde, escanear los archivos “en reposo” ayuda a limpiar datos antiguos.
Cuando un escaneo da positivo, trátalo como un evento de seguridad, no como un error de UI: bloquea el acceso inmediatamente, pon el objeto en cuarentena, notifica al propietario y a tu equipo, y guarda una pista de auditoría (usuario, hora, hash, resultado).
Paso a paso: un flujo de subida seguro que puedes implementar
La buena seguridad en subidas consiste en hacer las mismas comprobaciones aburridas cada vez, en el mismo orden, antes de que un archivo sea accesible por otros usuarios.
1) Recibir en un área temporal
Acepta subidas solo en una ubicación temporal (bucket temporal o carpeta temporal) que nunca sea pública.
- Aplica límites en la puerta: tamaño, número de archivos, timeouts y límites básicos de tasa
- Trata el nombre de archivo como no confiable y genera tu propio nombre de almacenamiento
- Valida el tipo real de archivo usando magic bytes y una lista blanca
2) Inspeccionar, almacenar y servir con seguridad
Antes de que nada sea visible en la app, analiza y aísla. Si no puedes escanear todavía, mantenlo privado y restringe el acceso.
- Escanea y pon en cuarentena; si el escaneo falla o marca el archivo, no lo adjuntes a contenido de usuario
- Mueve los archivos aprobados a almacenamiento privado con permisos cerrados y metadatos claros (owner, purpose, created_at)
- Sirve mediante URLs firmadas de corta duración o un proxy que verifique autorización
Configura limpieza automática. Expira las subidas temporales que nunca se usaron y borra archivos antiguos que ya no necesites. Esto evita crecimiento misterioso en el almacenamiento y reduce el radio de impacto.
Errores comunes que causan accidentes con buckets públicos
La mayoría de los incidentes de “bucket público” no son hacks ingeniosos. Son pequeñas decisiones de conveniencia que se vuelven permanentes y luego se copian a producción.
Un error clásico es poner todo el bucket como público solo para que las vistas previas funcionen en una demo. Otro es devolver URLs públicas directas desde tu API porque es más simple que usar URLs firmadas o un endpoint de descarga. Si los nombres son predecibles (como invoice.pdf), la gente puede enumerarlos. Incluso si los nombres son aleatorios, un enlace filtrado se convierte en una fuga permanente cuando el objeto es público.
Algunas señales de alarma que se repiten:
- Un SDK cloud basado en navegador que puede escribir en el almacenamiento
- Una clave con permisos amplios de “leer/escribir todo”
- URLs firmadas o tokens que acaban en logs o analytics
- Ajustes de ACL “temporales” copiados a producción
Los patrones más seguros suelen ser igual de rápidos:
- Mantén el almacenamiento privado por defecto y sirve mediante URLs firmadas o tu app
- Sube a un área privada de “incoming” y luego promociona archivos aprobados
- Mantén credenciales en el servidor; da a los clientes tokens de subida estrechos y de corta duración
Lista rápida antes de enviar a producción
Si solo haces una pasada antes del lanzamiento, céntrate en los controles que evitan los errores más costosos.
Límites y validación
- Aplica un tamaño máximo estricto en el servidor y añade límites básicos de tasa por usuario o IP
- Verifica el tipo de archivo usando magic bytes y trata las discrepancias de MIME como sospechosas
- Re-codifica formatos riesgosos cuando sea posible en lugar de servir los originales
Almacenamiento y acceso
- Almacena las subidas en almacenamiento privado por defecto, con roles de mínimo privilegio
- Nunca envíes claves de almacenamiento ni secretos de firmado en código frontend
- Si aceptas PDFs, Office, ZIPs o contenido externo, planifica escaneo y cuarentena
Reversibilidad
- Ten un interruptor de emergencia: rota credenciales, revoca tokens, bloquea el acceso público a nivel de política del bucket
- Haz que la eliminación sea rápida: borra un archivo y sus derivados, y guarda logs para encontrar usuarios impactados
Siguientes pasos si tu app generada por IA ya tiene riesgos en subidas
Los prototipos creados por IA a menudo tienen las subidas funcionando, pero les faltan las barreras que importan en producción. Síntomas típicos: URLs públicas permanentes, subidas que funcionan sin estar logueado, sin tamaño máximo en el servidor y servidores que confían en el MIME del navegador sin revisar el contenido del archivo.
Si necesitas que alguien verifique el riesgo rápido, ayuda reunir: el repo (o una copia mínima con el código y la configuración de subida), tus ajustes de almacenamiento (público/privado y reglas de acceso), dónde van las subidas (navegador→bucket vs pasando por tu servidor), algunos logs de subida, los tipos de archivo que permites y los límites que quieres.
Si heredaste un codebase generado por IA de herramientas como Lovable, Bolt, v0, Cursor o Replit, FixMyMess (fixmymess.ai) se centra en reparar precisamente estos bloqueos para producción: comprobaciones de auth faltantes, validación débil, manejo inseguro de nombres de archivo, permisos de almacenamiento en la nube demasiado amplios y ausencia de puertas de cuarentena o escaneo. Una auditoría de código gratis suele ser suficiente para darte una lista concreta de qué arreglar primero, sin adivinanzas.
Preguntas Frecuentes
What’s the first thing I should do to make uploads safer in a prototype?
Empieza por límites estrictos de tamaño, timeouts y límites básicos de tasa en el servidor, incluso para una demo. Esos tres controles evitan facturas sorpresivas, lentitud y abuso por bots antes de añadir nada más complejo.
Is checking the file extension or MIME type enough?
No. Las extensiones y los MIME enviados por el navegador son fáciles de falsificar. Valida el archivo comprobando su contenido real (magic bytes) y permite solo los tipos exactos que soportas.
Which file types are safest to accept early on?
Usa una lista blanca y rechaza todo lo demás por defecto. Para muchos prototipos, ceñirse a JPEG/PNG para imágenes y PDF para documentos es una base práctica; amplía solo cuando tengas una necesidad clara.
Why are filenames a security risk, and what’s the safer pattern?
Si guardas usando el nombre que proporciona el usuario, puedes sufrir traversal de rutas, sobrescrituras y trucos con Unicode confusos. Genera tu propio nombre de almacenamiento (por ejemplo, un ID aleatorio), conserva el nombre original solo para mostrarlo tras limpiarlo, y nunca construyas rutas de sistema de archivos con él.
Should my upload bucket be public so users can view files easily?
Mantén las subidas privadas por defecto y evita almacenarlas en cualquier carpeta web pública o bucket público. Sirve los archivos mediante un endpoint que verifique acceso o con URLs firmadas de corta duración para que un enlace filtrado no sea una fuga permanente.
What’s risky about ZIP or Office files compared to images?
ZIP y formatos de Office son contenedores, por lo que pueden ocultar scripts, macros o estructuras inesperadas. Si debes aceptarlos, aplica límites de tamaño más estrictos, cuarentena, escaneo de malware y evita renderizarlos inline en el navegador.
How do I safely show previews for uploaded files?
Trata las subidas nuevas como no confiables hasta que terminen las comprobaciones. Luego crea salidas seguras para visualizar. Re-codifica imágenes (decodifica y vuelve a escribir como JPEG/PNG) y prefiere generar vistas previas en imagen en vez de renderizar documentos directamente en el navegador.
What limits should I set to avoid abuse and big cloud bills?
Empieza con un tamaño máximo por archivo, un tamaño máximo total por solicitud y timeouts de subida; luego añade límites diarios por usuario y límites por ráfaga. Mensajes de error claros ayudan a usuarios reales a corregirlo rápido, mientras que los límites y timeouts reducen abusos de drip y reintentos.
Do I need malware scanning for a prototype?
Si las subidas se comparten, se descargan o las abre tu equipo, el escaneo merece la pena pronto. Si solo aceptas avatars y las re-codificas, a veces puedes retrasarlo, pero planifica añadirlo en cuanto aparezcan usuarios reales y documentos reales.
How can I tell if my AI-built app has dangerous upload settings?
Señales comunes: URLs públicas permanentes, subidas que funcionan sin iniciar sesión, ausencia de tamaño máximo en el servidor, confiar en el MIME enviado por el navegador y permisos amplios en el almacenamiento en la nube. Si tu app generada por IA tiene alguno de estos problemas, FixMyMess puede hacer una auditoría de código gratuita y darte una lista concreta de cosas a arreglar.