You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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_DIRhasta 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.