Volver a la wiki

Auditoría Suprema 2 · Cola terminal: Agente 2.27.0 (guard de destino, CORS vivo, DPAPI fail-closed) e ingest honesto

Cuándo

05-09-2026 · commit e8d2c088 (PR #509, v1.116.0, Agente local → 2.27.0). Cierra el dominio terminal de la cola MEDIA de la Auditoría Suprema 2 (task #286): de 38 hallazgos totales de este dominio, los 12 ALTA y varios MEDIA ya se habían cerrado en tandas anteriores (5 y 6, v1.98.0); este PR revisa uno a uno los 24 restantes — 15 arreglados, 2 ya resueltos de antes (#28 lock del deep-discovery, #32 caché del liveness), 1 con premisa falsa (#25), 5 diferidos con motivo documentado y 1 sin examinar (#33). Implementación con un agente Opus en worktree (sin shell — aislamiento perdido) y verificación de la sesión principal: 246 tests de tests/agent + terminal + 89 vecinos del ingest, mypy limpio.

Síntomas visibles

El Agente que corre dentro de la red del cliente actuaba como un proxy sin ninguna barrera: podía apuntar sus propios comandos de red contra sí mismo, contra metadata de nube o contra el servidor de CreaRack; un fallo del almacén seguro de Windows dejaba contraseñas en claro en disco; el servidor rechazaba métricas del Agente sin decir por qué (y una con la hora mal puesta podía pintar un equipo como sano sin dato real); y la subida de capturas de pantalla de cartelería no tenía tope de tamaño ni comprobaba que fuera una imagen de verdad.

Hallazgos concretos cerrados en este PR:

  1. #19 — sin guard de destino: /check, /network/ping-icmp, /network/banner, /signage/deploy y /signage/configure aceptaban loopback, 0.0.0.0, direcciones link-local/metadata (169.254.0.0/16, fe80::/10, ::1) y el propio host del SaaS como destino.
  2. #13 — CORS congelado: allowed_origins_for se evaluaba una sola vez al arrancar; un Agente recién instalado (sin vincular) arrancaba con ["*"] y seguía así después de emparejarse por /saas/setup, justo cuando ya había credenciales que proteger.
  3. #17 — DPAPI en fallo abierto: si el cifrado de Windows fallaba, encrypt_secret registraba el error y devolvía el secreto EN CLARO, que acababa persistido en metrics.db (comunidades SNMP, claves SNMPv3 de la flota del cliente).
  4. #26/#27 — ingest mudo: una métrica con timestamp futuro rescribía igualmente last_status/last_latency del target aunque fuese rechazada por la ventana de ingesta; y el campo rejected no traía ningún motivo (el lote se purgaba a las 48h sin pista de qué había fallado).
  5. #35/#36 — respuestas que mentían: receive_agent_metrics escribía packet_loss=0 inventado para tipos sin ese dato, y dos handlers devolvían str(e) crudo al cliente en vez de un mensaje genérico.
  6. #34 — subida de capturas sin tope: el endpoint de screenshots de signage aceptaba cualquier tamaño y cualquier contenido, sobre el mismo volumen que sirve el instalador y los paquetes firmados del Agente.
  7. Además: ai_remediate con blocklist evaluada antes de normalizar el comando (#18), conexiones aiohttp sin cerrar por conector (#15), la tarea de connect() sin cancelar en force_reset (#16), VACUUM de SQLite bloqueando el loop async (#14), /check con IP por defecto 127.0.0.1 si el host venía vacío (#20), y sin topes en la ejecución de comandos en lote (#24: ahora 50 equipos, 20 órdenes, 1 MB de salida por equipo, semáforo 5).

Causa raíz

Un solo patrón de fondo, repetido en capas distintas del Agente: el borde de confianza está mal trazado. El Agente asume que quien le habla (el SaaS que lo vincula, el navegador que abre su consola local, el propio sistema operativo al cifrar) es fiable y no revalida en cada punto de contacto:

Fix aplicado

Commit e8d2c088 (PR #509):

Diferidos con motivo: #21 (.verified en carpeta escribible, cruzado con Authenticode), #22 (exención de /traps/recent, decisión de Edu pendiente), #29 (promote sin ack, producto), #31 (E/S del ingest en el request, ADR aparte), #19-lote/subred (arriba). Sin examinar: #33 (prefiltro del re-enlace).

Lecciones

Preventivos futuros

Véase también

Subir