CreaRack-SL

Auditoría Suprema Etapa 3 network sub-área 3: Vendor/MIB/OUI (s114)

Descripción

Tercera sub-área de network (sesión 114, 2026-06-08): el catálogo de fabricantes y MIBs (las “fichas técnicas” que usa el sistema para reconocer dispositivos SNMP/HTTP) y los endpoints de perfiles auto-descubiertos. Auditoría identificó 25 hallazgos confirmados (8 ALTA, 5 MEDIA, 12 BAJA) en:

  • network/api/vendor.py (endpoints MIB y asistente IA)
  • network/api/profiles.py (endpoints de perfil de dispositivo)
  • network/services/mib_manager.py (compilación, extracción OIDs)
  • network/services/mib_assistant.py (análisis con Gemma 4)
  • network/services/vendor_registry.py (detección de vendor)
  • Comandos IEEE/IANA import (descarga de catálogos)

Esta PR merge todos los hallazgos viables en una tanda (Regla 22), diferiendo a PRs propias:

  • Rate-limit en upload/IA
  • upload_mib → async con Huey
  • HttpReader remoto (toca frontend/infra)
  • Trocear profiles.py/mib_manager.py (>500 LOC cada uno)
  • Decisión de diseño: campo organization de CustomMib (catálogo global vs tenant-scoped)

Hallazgos resueltos

Raíz A: Autorización ausente en endpoints MIB (6 ALTA)

Problema: upload_mib, delete_custom_mib, apply_custom_oids, mib_assistant_analyze, mib_assistant_apply no exigían rol admin. El catálogo de MIBs/OIDs es GLOBAL y compartido por todas las orgs del SaaS. Un usuario normal de cualquier org podía:

  • Subir/borrar MIBs (mutar catálogo compartido)
  • Escribir/compilar ficheros en disco del servidor
  • Gastar IA de pago en análisis

Solución: Los 5 endpoints anteponen if not is_admin(request.user): return 403 + 403 declarado en el response del decorador.

Raíz B: Path traversal / arbitrary file write en upload_mib (ALTA)

Problema: module_name (nombre interno del MIB) se extraía del contenido del fichero con regex \S+ sin validación. Un MIB malicioso podía incluir ../ en el nombre → escritura fuera de custom_dir (potencial RCE).

Solución:

  • Validación strict: module_name debe coincidir con ^[A-Za-z0-9._-]+$
  • Verificación realpath: el destino no escapa del directorio permitido

Raíz C: Broken Access Control en profiles.py → require_perm (ALTA)

Problema: Ningún endpoint del router profiles gateaba por rol (solo get_current_org). Un usuario readonly podía:

  • Lanzar escaneos SNMP-walk, SSH activos (refresh-interfaces, read-port-config)
  • Mutar DeviceProfile/Device.model_data (update, delete, configure_observatory, propagate_ports)

Solución: Cada endpoint exige:

  • require_perm(request, "network", "edit") para mutaciones y activos
  • require_perm(request, "network", "view") para GET

Patrón coherente con discovery.py/scripts.py.

ReDoS en regex editables del vendor (MEDIA)

Problema: Las expresiones de detección de marca (sys_descr_patterns, sys_descr_model_regex, sys_descr_version_regex) son campos editables del VendorProfile. Un patrón con backtracking catastrófico + un sysDescr largo colgaba el worker.

Solución: Capar sysDescr a 2048 caracteres antes de aplicar patrones en:

  • detect_vendor_from_sys_descr
  • get_model_from_sys_descr
  • get_version_from_sys_descr

Paridad con el body[:2048] HTTP.

SSRF defense-in-depth en refresh-interfaces/read-port-config (MEDIA)

La IP del profile se valida con check_scan_ip (rechaza loopback/metadata) antes de abrir SNMP/SSH.

Prompt injection OID→LLM (MEDIA)

Problema: Los nombres/sintaxis de OIDs subidos se enviaban al LLM sin sanitización.

Solución: Wrapping con wrap_untrusted() (reutiliza sanitize.py de CNS) + redacción de secretos antes de llegar a Gemma 4.

Code-exec latente documentado (MEDIA)

get_mib_dir() deja constancia: los .py compilados por pysmi derivan de input no confiable y NUNCA deben importarse (hoy solo se leen como texto).

Cambios de robustez / pulido

  • Errores genéricos al cliente: mib_assistant_* y profiles.py dejan de devolver str(e), detalles → log
  • Borrado de MIB por nombre exacto: delete_custom_mib borra por set exacto de extensiones, no startswith
  • Validación de formato OID: Regex numérico antes de persistir
  • Mass-assignment acotado: create_vendor_profile filtra por lista blanca (constante VENDOR_UPDATABLE_FIELDS)
  • Cap de descarga IEEE/IANA: 64 MB (evita agotar RAM ante upstream comprometido)
  • Docstrings honestos: “Gemini Flash” → “Gemma 4 (google_genai)”
  • Dead code: ParseSshVlanRequest eliminado
  • apply_proposal robusto: omite entries sin oid en vez de KeyError

Tests

Nuevos tests en tests/api/test_network_sa3.py (12 tests):

  • Gates de autorización de los 5 endpoints MIB (viewer/operator 403, admin pasa)
  • Path traversal rechazado (400)
  • require_perm en profiles.py (readonly 403 en mutaciones, view pasa GET)

CI improvements

  • gitleaks download resilience: descargar a fichero antes de pipe-tar (evita “stdin: not in gzip format” por body truncado)
  • Reintentos + extracción desde fichero (--fail --retry 5 --retry-delay 3 --retry-all-errors --retry-max-time 90)

Véase también

  • [[entity—network—model—vendor-profile]]
  • [[entity—network—model—device-profile]]
  • [[entity—network—service—mib-manager]]
  • [[entity—network—service—mib-assistant]]
  • [[entity—network—service—vendor-registry]]
  • [[concept—saas—require-perm-network]]