Funciones de exportación seguras para CSV y JSON sin filtraciones
Las funciones de exportación seguras te ayudan a entregar CSV/JSON con comprobaciones por tenant, límites de filas, jobs asíncronos y enlaces de descarga seguros.

Por qué los exportes son una fuente común de fugas de datos
Los exportes son donde los errores de seguridad se convierten en archivos que se comparten, reenvían y almacenan. En una app multi-tenant, un filtro faltante en una exportación CSV o JSON puede exponer los registros de otro cliente con un solo clic. Las funciones de exportación merecen la misma atención que login y pagos.
La mayoría de las fugas ocurren de maneras aburridas. Una consulta de exportación olvida el filtro de tenant. Un endpoint acepta un ID predecible y recupera la exportación guardada equivocada. Un job en background reutiliza una clave de caché como export:123 que no está ligada al usuario y al tenant. O un archivo viejo queda en el almacenamiento de objetos con una URL permanente, de modo que cualquiera que lo encuentre lo puede descargar después.
Una exportación segura implica que todas estas condiciones se cumplan:
- Datos correctos (la consulta coincide con lo que muestra la UI)
- Usuario correcto (permisos reales, no solo “está logueado”)
- Tenant correcto (un límite estricto, no un filtro aproximado)
- Tiempo correcto (expira y no puede reutilizarse para siempre)
La gente quiere exportes rápidos con un clic y enlaces para compartir. Los controles estrictos añaden fricción: más comprobaciones, límites de filas, generación asíncrona y descargas de corta duración. La meta es mantener la conveniencia sin convertir los exportes en una puerta trasera alrededor de tus reglas de acceso.
Esto también es un modo de fallo común en prototipos generados por IA. FixMyMess suele encontrar endpoints de exportación que “funcionan” en demos pero saltan comprobaciones de permisos o almacenan archivos de maneras que facilitan demasiado las fugas entre tenants.
Decide qué pueden exportar los usuarios (y qué no)
Antes de construir botones y endpoints, decide qué significa “exportar” en tu producto. Las exportaciones seguras empiezan con reglas claras, no con código.
Elige formatos según los trabajos reales. CSV es ideal para hojas de cálculo y análisis rápidos, pero es una tabla plana. JSON es mejor para backups, integraciones y datos anidados, pero es más fácil incluir campos adicionales por accidente.
Define el alcance en lenguaje claro y haz que se aplique en el código: qué registros, qué columnas y qué ventana temporal. Si no pones límites, los usuarios pedirán “todo” y tu sistema acabará intentando entregarlo.
Apunta un conjunto pequeño de decisiones:
- Qué objetos son exportables (por ejemplo: facturas y clientes, pero no notas internas)
- Qué campos están permitidos (y cuáles nunca se exportan)
- Rango de tiempo máximo por petición (como “últimos 90 días”)
- Filtros y orden por defecto que mantengan resultados previsibles
- Quién puede exportar (solo admins, o todos los usuarios)
Los campos sensibles necesitan mayor consideración. Algunos deben excluirse por completo (hashes de contraseñas, claves de API). Otros se pueden enmascarar (mostrar los últimos 4 dígitos). Algunos pueden requerir un segundo paso, como aprobación de un manager o volver a autenticarse.
Ejemplo: un agente de soporte puede exportar tickets a CSV con nombre del cliente y asunto, pero no email, IP o etiquetas internas. Un admin puede exportar JSON para integraciones, pero solo después de confirmar su contraseña.
Si heredaste una app generada por IA, las fugas suelen empezar aquí: los exportes reflejan el esquema de la base de datos por accidente. FixMyMess frecuentemente encuentra campos ocultos y secretos que se filtran en exportes “rápidos”, así que escribir estas reglas primero ahorra rehacer el trabajo más adelante.
Comprobaciones de acceso que coincidan con los permisos reales
Los fallos en exportes a menudo ocurren cuando el endpoint usa reglas más simples que las pantallas en la app. Un usuario puede ver una lista filtrada en la UI, pero la exportación silenciosamente omite esas restricciones y devuelve muchos más datos. Trata la exportación como una “ruta de lectura” separada, con su propio riesgo.
Verifica la identidad antes de construir la consulta: quién es el usuario, qué roles tiene y si es un miembro activo del tenant que dice representar. Si soportas usuarios suspendidos, invitaciones expiradas o miembros removidos, asegúrate de que esos estados bloqueen las exportaciones también.
Separa “puede ver en la app” de “puede exportar fuera de la app”. Exportar es acceso masivo y a menudo incluye campos que la gente nunca ve en pantalla. Muchos equipos añaden permisos explícitos como can_export_billing o can_export_users y los mantienen desactivados para la mayoría de roles.
Un conjunto simple de reglas que funciona:
- Requerir un permiso explícito de exportación masiva para cada conjunto de datos.
- Volver a comprobar permisos cuando el job de exportación se ejecuta, no solo cuando se solicita.
- Aplicar las mismas reglas a nivel de campo que la UI (ocultar columnas sensibles por defecto).
- Fallar seguro: si no puedes confirmar un permiso, no devuelvas nada.
- Mantener una política breve y legible que mapee roles a datasets exportables.
Ejemplo: un agente de soporte puede ver el perfil de un cliente para ayudar con un ticket, pero no puede exportar la lista completa de clientes. Si permites exportar, restríngelo a Admin y Billing Admin y documenta exactamente qué tablas y campos pueden exportar esos roles.
Si heredas un código generado por IA, esta es un área donde desajustes pequeños son comunes y vale la pena auditar con cuidado.
Aislamiento por tenant: haz imposible exportar datos de otros tenants
En productos multi-tenant, los exportes son donde el aislamiento falla con frecuencia. Un dropdown en la UI puede parecer correcto mientras la consulta del backend devuelve filas de otro cliente.
La regla más importante: aplica el scope del tenant en la propia consulta, no en el frontend. El backend debería derivar el tenant de la sesión autenticada, no de un parámetro de la petición. Si un usuario puede enviar tenant_id=otra_empresa y tu servidor lo cree, tienes una fuga lista para ocurrir.
Usa reglas estables que no cambien por pantalla. Una buena base es: el tenant debe coincidir, y el rol del usuario debe permitir las filas y campos solicitados. Eso significa que tu consulta de exportación siempre incluye un filtro de tenant además de las mismas restricciones de permisos que usas en las pantallas normales.
Comprobaciones prácticas que bloquean exportes entre tenants:
- Toma el tenant ID del contexto de autenticación, no del body o la URL de la petición
- Añade
WHERE tenant_id = :tenantFromSessiona cada consulta de exportación - Aplica reglas de rol en la misma consulta (por ejemplo, “los agentes solo pueden exportar cuentas asignadas”)
- Rechaza atajos de “admin” a menos que el usuario sea realmente admin del tenant
- Escribe una prueba que intente exportar de otro tenant y debe fallar
Si tu base de datos lo soporta, row-level security (RLS) añade un segundo candado. Incluso si un cambio de código futuro olvida un filtro, la base de datos sigue bloqueando filas que no pertenecen a ese tenant.
Ejemplo: un manager de soporte exporta “todos los tickets”. Si el backend aplica scope por tenant y las reglas de rol, la exportación nunca puede incluir tickets de otra empresa, incluso si alguien edita la petición.
Si heredaste un endpoint de exportación generado por IA, FixMyMess suele encontrar el mismo fallo: comprobaciones de tenant en la UI mientras la consulta SQL no tiene scope. Arreglar ese error elimina la mayor parte del riesgo de fugas entre tenants.
Límites de filas, paginación y comportamiento de consulta predecible
Los exportes parecen inofensivos hasta que alguien pide “todos los datos” y la base de datos pasa minutos procesando una consulta enorme. Una exportación segura tiene límites claros: cuántos datos, en qué orden y con qué filtros.
Empieza con un límite duro de filas por job de exportación. Elige un número que puedas soportar (por ejemplo, 50,000 filas), aplícalo en el servidor y muéstralo en la UI para que los usuarios no se sorprendan. Si alguien necesita más, ofrece dividirlo en exportes más pequeños en lugar de generar silenciosamente un archivo masivo.
Para conjuntos grandes de datos, no confíes en una consulta gigantesca. Usa paginación o particionamiento por fecha (por ejemplo, un archivo por mes). Esto también reduce problemas de “falló al 99%” y hace que los reintentos sean más baratos.
Los resultados deben ser predecibles. Aplica siempre un orden en el servidor (como created_at luego id) para que las páginas no cambien entre peticiones. Sin un orden estable, los usuarios pueden obtener duplicados o perder filas, lo que genera problemas de soporte y puede parecer una fuga de datos.
Para evitar que los exportes se conviertan en un riesgo de denegación de servicio:
- Requerir filtros seguros (rango de fechas, estado) y rechazar consultas sin límites.
- Establecer timeouts de consulta y cancelar exportes que se ejecuten demasiado.
- Asegurarse de que filtros comunes estén indexados.
- Limitar el tamaño de página para que una petición no pueda extraerlo todo.
Ejemplo: si un usuario exporta “órdenes de los últimos 30 días”, puedes partir por semana, ordenar por created_at,id y detenerte al límite, dando un resultado consistente cada vez.
Paso a paso: un flujo simple de exportación que se mantiene seguro
Una exportación segura consiste en hacer las comprobaciones rutinarias en el orden correcto, cada vez. La meta es sencilla: un usuario solo puede exportar lo que puede ver y siempre en una cantidad controlada.
Un flujo práctico para CSV y JSON:
- Acepta la petición y valida entradas: tipo de archivo, rangos de fecha, filtros y el permiso del usuario para los datos subyacentes.
- Construye la consulta aplicando primero el scope (tenant, reglas de propiedad, pertenencia a equipo), luego aplica filtros. Si un filtro puede cambiar el scope, recházalo.
- Decide síncrono vs asíncrono: exportes pequeños pueden transmitirse rápido; los más grandes deben convertirse en jobs en background con progreso claro.
- Genera el archivo de forma defensiva: usa una lista blanca fija de columnas, escapa celdas CSV que comiencen con
=,+,-,@, y usa UTF-8. - Devuelve un identificador de estado o un token de descarga de corta duración, no un archivo crudo en una URL predecible.
Ejemplo: un agente de soporte exporta “Tickets últimos 30 días”. Aunque intente cambiar un filtro a otro cliente, la consulta sigue fija al tenant del que forma parte, así que la exportación devuelve sus datos o nada.
Si heredaste una app generada por IA, verifica que los exportes reutilicen el mismo código de permisos que la UI, no una “consulta rápida” separada que omita reglas.
Generación asíncrona de exportes sin romper la experiencia de usuario
Si un export puede tardar más de unos segundos, hazlo asíncrono. Esto es especialmente cierto para conjuntos grandes, consultas lentas o exportes que requieran transformaciones pesadas (joins, formateo de fechas, enmascarado de campos). Los jobs asíncronos mantienen tu app responsiva y reducen la tentación de relajar comprobaciones de seguridad para acelerar exportes.
Un modelo simple de jobs sigue siendo comprensible. La mayoría de equipos solo necesita unos pocos estados:
- queued (solicitado, esperando un worker)
- running (generando activamente)
- completed (archivo listo para descargar)
- failed (error, mostrar mensaje seguro)
- expired (demasiado viejo, hay que regenerar)
Las actualizaciones de progreso deben ser útiles sin filtrar contenido. En lugar de mostrar filas de ejemplo o valores de campos, muestra metadatos seguros como porcentaje completado, filas procesadas y tiempo estimado.
Los reintentos importan porque los exportes fallan por razones mundanas: timeouts, reinicios de BD o caídas de workers. Haz que las peticiones sean idempotentes para que los usuarios no creen exportes duplicados al hacer clic repetido. Un patrón práctico es calcular una clave de deduplicación a partir de (tenant_id, user_id, export_type, filters, time_window) y reutilizar el mismo job si ya está en ejecución.
Mantén la UX simple:
- Mostrar una etiqueta de estado y un botón de refrescar, no un spinner infinito
- Enviar una notificación cuando el job termine
- Permitir cancelar un export en ejecución
- Poner un tiempo de expiración claro en exportes completados
Si arreglas prototipos generados por IA, los bugs suelen ocultarse aquí: filtros de tenant faltantes dentro del worker en background. FixMyMess encuentra workers que ejecutan consultas “admin” por accidente, lo que rompe el aislamiento.
Entrega segura: descargas que no se conviertan en secretos compartidos
Generar un export seguro es solo la mitad del trabajo. La otra mitad es asegurarse de que el archivo no se convierta en una clave permanente que cualquiera pueda pasar.
Un buen enfoque por defecto es mantener los archivos de exportación fuera de la base de datos principal y ponerlos en object storage (o un almacén de archivos similar). Trata el bucket como privado y permite descargas solo mediante URLs firmadas de corta duración.
Cómo debería ser una entrega segura:
- Usa nombres de archivo largos y aleatorios (no IDs secuenciales como
export-123.csv) para que adivinar sea inútil. - Emite un enlace de descarga firmado que expire rápido (minutos, no días).
- Ata el enlace al usuario y al tenant que lo solicitó. Si alguien lo reenvía, la siguiente petición debería fallar a menos que sea la misma identidad.
- Establece expiración automática del archivo en el almacenamiento (horas o días, según el producto).
- Considera tokens de descarga de un solo uso para exportes sensibles (finanzas, listas de usuarios, datos regulados).
Ejemplo: un agente de soporte exporta clientes del Tenant A y pega el enlace en un chat. Si tu enlace está “firmado para cualquiera”, creaste un secreto compartido. Si está firmado para ese agente y se verifica de nuevo al descargar, el enlace reenviado es inútil.
Limpia con agresividad. Los exportes antiguos se acumulan y se convierten en un accidente esperando suceder. Si heredas apps generadas por IA donde los exportes se almacenan para siempre o se exponen públicamente, FixMyMess puede auditar la ruta de entrega y bloquearla sin reescribir todo tu producto.
Trucos de CSV y JSON que pueden convertirse en problemas de seguridad
CSV y JSON parecen simples, pero pequeñas decisiones de formato pueden volverse problemas reales de seguridad. Muchas fugas ocurren después de que los datos salen de la app, cuando se abren en Excel, se comparten en un chat o se guardan en la carpeta de descargas de alguien.
CSV: la hoja de cálculo es parte del modelo de amenaza
CSV tiene un riesgo silencioso: inyección de fórmulas en hojas de cálculo. Si una celda empieza con =, +, - o @, algunas aplicaciones de hojas de cálculo pueden tratarlo como fórmula. Un usuario malicioso puede poner algo como =HYPERLINK("...") en su campo de nombre y, cuando un admin abra el export, podría ejecutarse.
Una mitigación simple es escapar esos valores antes de escribir el CSV. Enfoques comunes son prefijar con una comilla simple ' o un tab \t cualquier campo que empiece con esos caracteres.
También normaliza el formato para que los exportes no fallen de formas inesperadas. Usa UTF-8, siempre entrecomilla campos que contengan comas, comillas o saltos de línea, y duplica las comillas dentro de un valor. Si no lo haces, las filas pueden desplazarse, los filtros mentir y la gente puede copiar datos equivocados.
JSON: es fácil sobrecompartir
JSON tienta a volcar todo el objeto. Ahí es donde tokens de acceso, claves API, hashes de restablecimiento y campos de debug se cuelan en exportes. Trata los exportes como un contrato público: construye una lista blanca explícita de campos.
También ayuda añadir un pequeño bloque de encabezado para que el archivo se explique a sí mismo:
- Hora de exportación (UTC)
- Filtros aplicados (como el usuario los configuró)
- Conteo de filas (y si se truncó)
- Alcance de datos (nombre del tenant o workspace)
Ejemplo: un manager de soporte exporta Customers filtrados por un workspace. Si el JSON incluye stripeSecretKey porque vive en el mismo modelo, acabas de crear una fuga de credenciales portátil. Los campos explícitos lo previenen.
Logs de auditoría y monitoreo que realmente usarás
Los logs de auditoría son tu cinturón de seguridad para exportes. Cuando algo falla, necesitas respuestas claras a tres preguntas: quién lo hizo, qué exportó y adónde fue. Registra la exportación como dos eventos: la petición y la descarga. La petición te dice la intención (y el alcance). La descarga confirma la entrega (y desde qué usuario e IP). Mantén los logs ligeros y buscables para que realmente los consultes en un incidente.
Captura suficiente detalle para investigar sin almacenar los datos exportados:
- Quién: user ID, rol, tenant ID y método de autenticación usado
- Qué: nombre del dataset, filtros aplicados, conteo de filas y columnas incluidas
- Cuándo/dónde: timestamps, IP, user agent e ID del job de exportación
- Resultado: éxito/fallo, códigos de error y tamaño del archivo
El monitoreo trata sobre patrones, no perfección. Alerta sobre cosas que raramente ocurren en el trabajo normal: muchas exportaciones en poco tiempo, exportes inusualmente grandes, fallos repetidos o descargas fuera del horario típico del tenant. Ata las alertas a acciones, como forzar límites de fila más pequeños temporalmente.
Da a los admins controles simples para responder: revocar un job de export específico, invalidar un token de descarga y acortar expiración durante un incidente. Si heredas una app generada por IA con logs faltantes o ruidosos, FixMyMess puede ayudarte a añadir trazas confiables sin rehacer todo el producto.
Errores comunes y trampas a evitar
La mayoría de fugas en exportes no son hacks sofisticados. Son huecos entre lo que muestra la UI y lo que el servidor hace. Trata cada petición de exportación como una lectura directa a la base de datos, porque eso es en lo que se convierte.
Trampas comunes:
- Confiar en filtros de la UI (como un campo tenantId oculto) en lugar de aplicar comprobaciones de tenant en el servidor para cada consulta.
- Cachear archivos de exportación generados y servir accidentalmente el mismo archivo a otro usuario o tenant.
- Permitir rangos de fechas ilimitados o “exportar todo” sin un tope duro, lo que puede causar lecturas enormes, timeouts y sobrecompartición accidental.
- Devolver errores crudos que expongan nombres de tablas, fragmentos SQL o IDs internos que ayuden a un atacante a adivinar tu esquema.
- Copiar código de exportación de un prototipo generado por IA y lanzarlo sin revisión de seguridad.
Un ejemplo realista: un agente de soporte selecciona “Últimos 90 días” en la UI. El backend construye una consulta solo con el rango de fechas y olvida tenant_id = currentTenant. El export aún “se ve bien” en pruebas porque el tester solo tiene datos de un tenant. En producción, extrae silenciosamente otros tenants.
Guardas prácticas evitan la mayoría de esto:
- Construye la consulta desde el contexto del servidor primero (usuario actual, rol, tenant), luego aplica los filtros del usuario.
- Da a cada job de export un propietario y tenant únicos, y verifica ambos al generar y al descargar.
- Establece límites duros (filas, rango de fechas, tamaño de archivo) y requiere filtros más específicos para exportes grandes.
- Normaliza los mensajes de error para usuarios; guarda los detalles en logs.
Si heredaste código de exportación de herramientas como Lovable, Bolt, v0, Cursor o Replit, FixMyMess suele encontrar estos mismos problemas durante una auditoría rápida y te ayuda a parchearlos antes de que sean un incidente.
Lista rápida antes de lanzar exportes
Haz una pasada rápida con mentalidad de seguridad antes de lanzar exportes CSV o JSON. La mayoría de fugas vienen de una suposición pequeña que se filtra, como confiar en un filtro de la UI o dejar un enlace de descarga usable para siempre.
Empieza con acceso y alcance. Cada petición de exportación debe verificar el usuario actual, su rol y el tenant al que pertenece. Luego revisa que tenga un permiso específico para exportar (no solo “puede ver”). Si un agente de soporte puede ver un registro pero no debería descargarlo masivamente, el export debe imponer esa restricción.
Haz imposible eludir el aislamiento por tenant. La consulta del backend debe siempre aplicar scope por tenant, incluso si la petición incluye tenantId, workspaceId o accountId. Escribe al menos una prueba que intente exportar datos de otro tenant y pruebe que devuelve cero filas.
Confirma límites predecibles. Aplica el límite de filas en el servidor y muéstralo en la UI para que los usuarios no reciban archivos parciales sin aviso.
- El servidor aplica un conteo máximo de filas (y tiempo máximo) por export.
- El orden es estable (mismos filtros + mismo periodo = mismo orden).
- La paginación no se controla de forma que permita saltarse el límite.
Planifica para exportes grandes. Usa jobs asíncronos cuando pueda tardar más que una petición normal y da estados claros: queued, running, finished, failed.
Trata la entrega como sensible. Los enlaces de descarga firmados deben expirar pronto, los archivos deben auto-eliminarse y las descargas no deben ser adivinables. Si tu export salió de un prototipo generado por IA que parece “casi listo” pero le faltan estas comprobaciones, FixMyMess puede auditar rápido y reparar las partes riesgosas antes de producción.
Un ejemplo realista: exportes en SaaS multi-tenant hechos correctamente
Imagina una pequeña agencia que usa una app SaaS para gestionar facturación de múltiples clientes (tenants). Un account manager tiene acceso a Cliente A y Cliente B, pero solo a los proyectos a los que está asignado.
Va a Facturas y hace clic en Export para Cliente A. Detrás, el job de export se crea con un scope del servidor como tenant_id = Cliente A, además de los mismos filtros de rol y proyecto que usa la UI.
Ahora lo interesante: el usuario modifica parámetros. Cambia un valor de la petición de Cliente A a Cliente B, o intenta otro rango de IDs de factura. Si tu sistema confía en que el cliente envíe el tenant correcto, pierdes. Si tu sistema deriva tenant y permisos de la sesión autenticada, la petición solo podrá exportar datos de Cliente A, aunque se manipulen parámetros.
La generación asíncrona mantiene la experiencia limpia. En lugar de timeouts o archivos a medio crear, el usuario ve “Preparando export” y recibe el archivo terminado cuando el job completa.
Una configuración “correcta” suele incluir:
- El job de export guarda
user_id,tenant_idy una snapshot de permisos - La consulta siempre aplica scope por tenant y los mismos filtros de permiso
- Tope duro de filas (o paginación por ventanas) para evitar jobs descontrolados
- Token de descarga de un solo uso que expira rápido
- Entrada en el log de auditoría para inicio, fin, descarga e intentos denegados
Si un usuario reporta “vi la factura equivocada”, esos logs te permiten rastrear quién pidió la exportación, qué scope se aplicó y si hubo intentos bloqueados justo antes.
Próximos pasos: endurece lo que tienes y luego itera
Trata tus endpoints de exportación actuales como una lista de verificación de fallos posibles. Busca dónde un filtro faltante, una consulta reutilizada o un atajo “temporal” de admin podrían permitir a alguien exportar datos que no debería ver.
Escribe los modos de fallo que realmente te preocupan: acceso entre tenants, exportar más filas de las previstas, exportar registros eliminados u ocultos y descargas que se pueden reenviar a cualquiera.
Mejora una pieza a la vez, en este orden:
- Aplica scope a cada consulta de export por tenant y por permisos reales del usuario
- Añade límites de filas y paginación predecible (y decide qué ocurre cuando los usuarios alcanzan el límite)
- Mueve exportes grandes a jobs asíncronos con estado claro y expiración
- Entrega archivos con descargas firmadas de corta duración ligadas al usuario y revoca al cambiar permisos
- Añade logs de auditoría sobre quién exportó qué y cuándo
Mantén un pequeño plan de pruebas que puedas ejecutar antes de cada release. Una buena prueba: inicia sesión como Tenant A, lanza un export y luego intenta cambiar la petición (o reutilizar la descarga) como Tenant B. Si algo funciona, tienes una fuga.
Si tus exportes vinieron de un prototipo generado por IA, asume que las protecciones están incompletas. Si quieres una segunda opinión rápida, FixMyMess (fixmymess.ai) hace auditorías y reparaciones enfocadas para bases de código generadas por IA, incluyendo flujos de exportación, brechas de aislamiento por tenant y entregas de archivos inseguras.