Skip to content

Security: pmirall/NeoImage2Pattern

Security

docs/SECURITY.md

Seguridad y privacidad (modelo de amenaza local-first)

Por defecto

  • Sin cuentas, analíticas ni rastreadores, y sin subida a ninguna nube propia.
  • Datos locales. Proyectos, fotos y exportaciones se guardan en DATA_DIR: SQLite más ficheros con nombre UUID.
  • Borrado. El botón «Borrar proyecto y fotos» llama a DELETE /api/v1/projects/{id} y elimina las revisiones, los ZIP y la foto (si ningún otro proyecto la usa). Después, la descarga devuelve 404.
  • Red.
    • El backend escucha en 127.0.0.1.
    • Docker publica 127.0.0.1:8000 (API) y 127.0.0.1:5173 (web). Dentro del contenedor la API escucha en 0.0.0.0 solo para que funcione la publicación del puerto, que queda en loopback; CI lo comprueba.
  • Exposición en red local. Abrirla a la LAN exige cambiar ALLOWED_HOSTS/ALLOWED_ORIGINS y añadir autenticación, que no existe en v1. No la expongas sin protección.

Política de retención

  • Duración. Los proyectos, las revisiones, las fotos saneadas y los ZIP se conservan en DATA_DIR hasta que los borres. No caducan solos y no se suben a ningún sitio.
  • Borrar un proyecto elimina sus revisiones y exportaciones, y también su foto si ningún otro proyecto la usa. Una foto suelta sin proyecto se borra con DELETE /api/v1/images/{id}.
  • Análisis de IA. En la base de datos solo se guarda el JSON validado y el hash de la respuesta en bruto, nunca la respuesta completa del modelo.
  • Proveedor remoto. Si lo usas, la política de retención es la de ese proveedor. Revísala antes de dar tu consentimiento.

Defensas implementadas (app/security.py)

  • DNS rebinding. Se rechaza cualquier Host fuera de la lista (400 HOST_NOT_ALLOWED).
  • Orígenes. Se rechaza cualquier Origin fuera de la lista (403 ORIGIN_NOT_ALLOWED).
  • CSRF. Toda mutación exige la cabecera X-NeoImage-Request: 1 (403 CSRF_HEADER_MISSING).
    • Un formulario de otro sitio no puede añadirla.
    • Un fetch desde otro origen que la añada necesita una preflight CORS, y esa preflight se deniega.
    • CORS no se usa como autenticación.
  • Límite de peticiones en memoria por cliente (429 RATE_LIMITED).
  • Cabeceras: X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer y una CSP restrictiva en la API, el SVG y el informe HTML.
  • Imágenes (app/services/image_service.py):
    • lista blanca JPEG/PNG/WebP comprobada por firma, con 10 MB como máximo;
    • guarda contra bombas de descompresión, tanto por píxeles antes de decodificar como por el aviso de Pillow tratado como error;
    • normalización de la orientación;
    • recodificación, que elimina EXIF/GPS/XMP/ICC; nunca se guarda el original;
    • redimensionado a 2048 px como máximo.
  • SVG de la vista previa. Lo genera el propio servidor (sin scripts ni referencias externas) y la UI lo carga con <img>, nunca inyectado en el DOM. No se interpreta ningún XML ni SVG aportado por la persona usuaria.
  • Sin SSRF. El cliente no puede pedir al servidor que descargue URLs.
  • Rutas seguras. Los ficheros usan UUID y los nombres de entrada del ZIP están en una lista blanca, sin / ni ...
  • Claves.
    • AI_API_KEY solo vive en el .env del backend, como SecretStr.
    • Nunca se envía al navegador ni aparece en logs, audit.json, manifest.json o el ZIP.
    • El ZIP se escanea en busca del valor de la clave antes de entregarlo, y hay tests que lo comprueban.
  • Salida de modelos: dato no confiable.
    • Se valida con un esquema estricto (extra = forbid) sin campos numéricos de medidas.
    • Las notes se muestran como texto plano y nunca se ejecutan ni se interpretan.
    • Un texto en la foto del tipo «ignora las validaciones» no cambia medidas, geometría ni claves (test F14).
  • Errores. Siempre con la forma {code, message, details, request_id}, sin trazas de pila ni secretos.

IA y datos personales

  • Por defecto no se usa IA (AI_PROVIDER=none).
  • Nunca hay paso automático de local a remoto.
  • Proveedor remoto (openai_compatible, u Ollama en un host no local): se pide consentimiento explícito en cada envío (consent_remote_upload). Sin él, la respuesta es 422 CONSENT_REQUIRED.
  • Llamadas a la IA: como máximo 1 análisis y 1 reparación por foto, con una pausa acotada (≤ 5 s) y un tope de tokens de salida por llamada (AI_MAX_OUTPUT_TOKENS). El resultado se guarda en caché por sha256(imagen) + proveedor + modelo + versión del prompt + parámetros. Iterar, auditar o exportar no llama nunca a la IA.
  • Comprobar conexión (POST /providers/{id}/check) no envía la foto ni genera texto: solo lista modelos y capacidades. La clave de una API remota viaja únicamente en la cabecera Authorization hacia la URL configurada en el backend.
  • Tokens y coste se guardan por análisis, sin contenido de la foto ni del prompt.
  • Logs. Solo identificadores y hashes de imagen, nunca la foto ni las medidas.
  • Licencias. Usar un modelo por API no convierte en open source al servicio remoto. La opción totalmente abierta es un modelo con licencia compatible ejecutado en local.

Cadena de suministro

  • Lockfiles: uv.lock y package-lock.json. La instalación es uv sync --locked y npm ci.
  • Imágenes base fijadas por digest y GitHub Actions fijadas por SHA.
  • make license-check genera el inventario de licencias y sbom.json (CycloneDX).
  • make audit-deps busca vulnerabilidades conocidas con pip-audit (todas las dependencias bloqueadas, incluidos los extras) y npm audit; corre dentro de make preflight y del CI. Si no hay red, se informa como NOT_RUN, nunca como aprobado.
  • Limitación. No se promete seguridad absoluta: un escaneo solo conoce las vulnerabilidades ya publicadas.

Extras opcionales

  • Motor GarmentCode experimental: corre en un proceso aparte, con su propio intérprete (GARMENTCODE_PYTHON) y sin red durante el uso; desactivado por defecto. El backend no importa su código.
  • Servidor MCP (make mcp): habla con la API local por HTTP con la misma cabecera anti-CSRF y las mismas restricciones de Host; no tiene acceso directo a la base de datos.

There aren't any published security advisories