CreaRack-SL

Cierre de deudas Etapa 3 SA3 — DNS-rebinding + clasificación de insights

Cierre de deudas Etapa 3 SA3

Comisión: Edu (con Claude Code Opus) | Fecha: 2026-06-05 | Commit: cb4fe5e

Resumen

Cierre de dos deudas operacionales de Etapa 3 anotadas tras la auditoría Suprema de Monitoring SA3:

M15: DNS-rebinding / TOCTOU en webhooks y HTTP probes

Problema: Al validar destinos SSRF, la aplicación resolvía + validaba el hostname una vez. Pero el cliente HTTP (httpx o requests) re-resolvía el hostname al conectar — una ventana TOCTOU donde un atacante DNS podría cambiar la respuesta a una IP interna.

Solución: Nuevo módulo centralizado net_guard.resolve_and_validate() que:

  • Resuelve el hostname una única vez y devuelve TODAS las IPs válidas.
  • Valida que ninguna de ellas esté en rangos bloqueados.
  • Callers (http_service.py, notification_service.py) conectan a esas IPs pineadas, nunca re-resuelven.
  • Presenta el hostname original para Host header + TLS SNI + certificate verification.
  • Retry entre IPs (multi-IP resiliencia) sin abandonar el pin.

Implementación:

  • net_guard.py: resolve_and_validate(), pinned_url(), try_each_ip[_async](), httpx_pin_kwargs(), pinned_requests_session().
  • http_service.py: refactor de check() y check_async() para usar pinning vía try_each_ip[_async]().
  • notification_service.py: nuevo _post_pinned() que aplica pinning a todos los webhooks (generic, Slack, Teams).
  • SSL check (_check_ssl_cert) también respeta el pin en lugar de re-resolver.

Verificación smoke (sa3 suite): 5/5 destinos reales (Cloudflare, Discord, Google, webhooks internos) verificados. Limitación documentada: un destino CDN-fronted que rechaza conexiones IP-directas (example.com-like) se reporta down — trade-off aceptado (pin estricto sin fallback).

B18: auto_resolve de insights por campo estructurado

Problema: auto_resolve_connectivity_insights() filtraba por substrings del texto de summary y anomaly_trigger — frágil, escalable solo agregando keywords, y asumía que todo text-match era connectivity.

Solución: Nuevo campo AIInsight.category (enum: CONNECTIVITY / SECURITY / PERFORMANCE / OTHER):

  • Clasificado en create_insight() vía classify_insight_category() (même lógica, pero estructurada).
  • Indexado en base de datos.
  • Migración 0020 backfill de insights existentes (snapshot de keywords al día de la migración).
  • auto_resolve_connectivity_insights() filtra ahora category=CONNECTIVITY directamente en la query (sin text-match en bucle).

Implementación:

  • models_insight.py: AIInsight.Category choices + campo category (indexed).
  • insight_service.py: classify_insight_category() + aplicación en create_insight().
  • Migración 0020: backfill autónomo vía RunPython.
  • Tests: 13 tests nuevos (31 en total) en test_monitoring_sa3.py.

Impacto

ComponenteCambioRiesgo
net_guard.pyNueva capa de pinning DNS-rebindingBajo (nuevo módulo, bien testado)
http_service.pyRefactor para usar resolve_and_validate + try_each_ipBajo (lógica de retry preservada)
notification_service.pyWebhooks ahora pineados (generic, Slack, Teams)Bajo (SSRF + DNS-rebinding cerrados)
AIInsight modelNuevo campo category, indexedBajo (migration aditiva, backfill autónomo)
auto_resolveFiltra por category en lugar de text-matchBajo (lógica equivalente, más eficiente)

Cobertura de pruebas

  • 18 + 13 = 31 tests en test_monitoring_sa3.py.
  • Suite de API verde.
  • Sin cambios de migraciones complejas — la 0020 es aditiva (add field + backfill vía RunPython).

Regla 22 (auditoría Suprema)

“Todos arreglados en una tanda”: explotando 7 raíces comunes (net_guard, scoping org, sanitización, bug Tutor, cost guard, parseo compartido, async diferido). Monitoring: 4 de 6 sub-áreas cerradas (sa1, sa2, sa3, sa6).

Véase también

  • [[entity—monitoring—service—net-guard]]
  • [[entity—monitoring—model—ai-insight]]
  • [[entity—monitoring—service—http-service]]
  • [[entity—monitoring—service—notification-service]]
  • [[entity—monitoring—service—insight-service]]