Seguridad - Apps con IA - Python - Subida de archivos
5 min lectura

Tu app de IA acepta imágenes: valida el archivo antes de llamar al modelo

Ejemplo Python probado para rechazar imágenes falsas, demasiado grandes o con MIME incorrecto antes de enviarlas a una API de visión. Límites y pruebas reales.

Tu app de IA acepta imágenes: valida el archivo antes de llamar al modelo

Tu app hecha con IA pide una foto para analizar un producto. El formulario solo acepta .png, así que envías los bytes a un modelo de visión. Pero el nombre y el Content-Type los controla el cliente: un archivo de texto renombrado puede atravesar ese filtro. Antes de pagar por una llamada al modelo o guardar contenido de usuarios, el servidor debe comprobar qué recibió realmente y cuánto puede consumir al decodificarlo.

La guía de OWASP sobre subidas recomienda varias defensas combinadas: tipos permitidos, validación de contenido, límites, permisos y almacenamiento seguro. Ningún chequeo aislado basta. Para una app que solo necesita fotos, una decisión razonable es permitir PNG/JPEG, imponer un máximo de bytes antes de leer el cuerpo completo y limitar dimensiones antes de procesar la imagen. No aceptes documentos, SVG o archivos comprimidos si el caso de uso no los necesita.

Validar tamaño, contenido y píxeles antes de enviar una imagen a la API de IA
La extensión del archivo no demuestra que sea una imagen.

Una función pequeña para el backend Python

Instala Pillow en tu entorno y guarda el siguiente ejemplo como validate_image_upload.py. La función recibe los bytes que tu endpoint ya leyó y el MIME declarado. Comprueba tamaño, abre el contenido con Pillow, contrasta el formato detectado, limita píxeles, verifica y decodifica la imagen, y devuelve una versión reescrita. La documentación de Pillow explica que verify() puede detectar un archivo roto sin decodificar píxeles; por eso el código reabre y carga la imagen antes de usarla. También trata la advertencia de imagen descomprimida excesiva como error.

"""Validate a small PNG/JPEG before sending it to an image AI API."""
from io import BytesIO
import warnings
from PIL import Image, UnidentifiedImageError

MAX_BYTES = 5 * 1024 * 1024
MAX_PIXELS = 20_000_000
MIME = {"PNG": "image/png", "JPEG": "image/jpeg"}


def validate_image(data: bytes, claimed_mime: str) -> tuple[bytes, str]:
    if not data or len(data) > MAX_BYTES:
        raise ValueError("Image must be 1 byte to 5 MiB")
    if claimed_mime not in MIME.values():
        raise ValueError("Only PNG or JPEG is allowed")

    Image.MAX_IMAGE_PIXELS = MAX_PIXELS
    with warnings.catch_warnings():
        warnings.simplefilter("error", Image.DecompressionBombWarning)
        try:
            with Image.open(BytesIO(data)) as image:
                fmt = image.format
                if fmt not in MIME or MIME[fmt] != claimed_mime:
                    raise ValueError("Declared type does not match image content")
                if image.width * image.height > MAX_PIXELS:
                    raise ValueError("Too many pixels")
                image.verify()

            with Image.open(BytesIO(data)) as image:
                image.load()
                clean = BytesIO()
                image.save(clean, format=fmt)
                return clean.getvalue(), MIME[fmt]
        except (UnidentifiedImageError, OSError,
                Image.DecompressionBombError,
                Image.DecompressionBombWarning) as error:
            raise ValueError("Invalid or unsafe image") from error

Integra la función después de un límite de tamaño en el servidor o proxy, porque cuando recibe data el cuerpo ya está en memoria. Si rechaza el archivo, responde con un error de cliente apropiado sin revelar trazas internas; si lo acepta, envía solo los bytes reescritos al servicio de IA. La función no implementa autenticación, cuota por usuario, escaneo antimalware ni almacenamiento: eso depende de tu app. Tampoco debes enviar una foto de un cliente a un proveedor de IA sin una base clara de consentimiento y retención.

Cuatro casos que sí probé

  1. Un PNG válido de 20 × 20 píxeles devuelve bytes de imagen y image/png.
  2. Texto plano declarado como PNG se rechaza.
  3. Un PNG real declarado como JPEG se rechaza porque el MIME no coincide con el contenido.
  4. Un cuerpo mayor de 5 MiB y un PNG de 5.000 × 5.000 píxeles se rechazan por tamaño y límite de píxeles, respectivamente.

Esas pruebas ejecutaron la función localmente, no el endpoint de tu app ni un modelo de visión. Al integrarla, añade una prueba de extremo a extremo: una foto permitida debe procesarse; un archivo falso y otro demasiado grande deben detenerse antes de llamar a la API de IA. Comprueba en los logs que el rechazo no produjo un cargo del proveedor. Revisa además que una extensión doble, un nombre extraño o un MIME enviado por el navegador no salten la validación del contenido.

El límite que importa

Un PNG válido puede contener datos sensibles o maliciosos para otro componente; este ejemplo reduce una clase de errores, no certifica la seguridad de toda la cadena. OWASP aconseja almacenamiento fuera de la ruta pública cuando sea posible, nombres generados por el servidor, permisos mínimos y límites de descarga. Si la app acepta PDF, audio o video, necesitas una política y un validador específicos para esos formatos. Para un MVP de análisis de imágenes, empezar con dos formatos, límites medibles y pruebas de rechazo es una mejora concreta que tu agente de código puede implementar y tú puedes verificar.

Recibe lo nuevo en tu correo

Workshops gratis, herramientas y novedades de IA aplicadas a negocio, sin humo.

🎓 Workshops en vivo🛠️ Herramientas nuevas⚡ Novedades de IA

Sin spam. Te das de baja cuando quieras.

Comentarios

Aún no hay comentarios. Sé el primero.

Deja un comentario