Volver a la wiki

UI local del Agent y terminal con Bearer enforcement (Agent 2.1.3)

Resumen

Corrección de dos superficies de la UI local del Agent que quedaron inaccesibles tras activar el enforcement del Bearer local (v2.1.2):

  1. Página de diagnóstico local (localhost:5050/metrics/ui, /licenses) — servidas sin sesión del SaaS, no pueden llevar token. Se eximen seis rutas (dos páginas + cuatro GET de estado).
  2. Iframe del terminal — ahora recibe y envía el Bearer recibido por postMessage, permitiendo que el fetch a /terminal/sessions se autentique.

Resultado: Fase 3 (Raíz 3) CERRADA. La política fail-closed intacta: acciones y GETs sensibles siguen protegidas.


Problema y contexto

Con el enforcement del token local activado en Agent 2.1.2 (cierre de la Raíz 3 de la Auditoría Suprema), la mayoría de endpoints pasaron a exigir Authorization: Bearer <token>. Sin embargo, dos superficies que el propio Agent muestra en el ordenador del cliente (localhost:5050) quedaron devolviendo 401:

  1. Página de diagnóstico del Agent (assets/debug.html, metrics.html):

    • Se sirven sin sesión del SaaS — el cliente accede vía navegador local sin JWT.
    • No tienen forma de presentar un token (no son javascript que intercepte SaaS auth).
    • Consumían GET de solo lectura (/sentinel/status, /traps/stats, /metrics/stats).
    • Aparecía error: “Sentinel status not available” aunque el servicio funcionaba.
  2. Iframe del terminal (assets/terminal.html):

    • Empotraba un fetch a /terminal/sessions sin header de autenticación.
    • El SaaS sí le pasaba el token local vía postMessage(SET_AUTH_TOKEN), pero el iframe no lo usaba.

Solución (Agent 2.1.3)

Exempt paths en local_token.py

Se añadieron 6 rutas a EXEMPT_PATHS:

EXEMPT_PATHS = frozenset({
    # ... bootstrap existente ...
    "/metrics/ui",          # Página HTML de métricas locales
    "/licenses",            # Página de licencias
    "/licenses.txt",        # Endpoint de descarga de licencias
    "/sentinel/status",     # GET estado del Sentinel (lo-lectura)
    "/traps/stats",         # GET estadísticas de traps
    "/traps/recent",        # GET eventos recientes de traps
    "/metrics/stats",       # GET estadísticas de métricas
    # ... resto de exentos ...
})

Política: todas son GET de solo lectura consumidas por la propia UI local. El estado del Sentinel ya se expone en /info, por lo que no se añade lógica nueva — solo se abre lo que ya existía.

Terminal iframe y Bearer en terminal.html

El fetch a /terminal/sessions ahora añade el header Authorization:

// Antes:
fetch(`/terminal/sessions`).then(r => r.json())...

// Después:
fetch(`/terminal/sessions`, 
  localAuthToken ? { headers: { Authorization: `Bearer ${localAuthToken}` } } : undefined
).then(r => r.json())...

El localAuthToken ya se recibía vía postMessage(SET_AUTH_TOKEN) — ahora se usa.

Política fail-closed intacta

Todas las acciones y GETs sensibles siguen detrás del token:

RutaProtección
/cluster/*✅ Bearer obligatorio (RCE)
/network/*✅ Bearer obligatorio (SSRF/scan)
/sftp/*✅ Bearer obligatorio (acceso de ficheros)
/signage/*✅ Bearer obligatorio
/admin/uninstall✅ Bearer obligatorio
/sentinel/start|stop|reset✅ Bearer obligatorio
/metrics/purge*✅ Bearer obligatorio
/terminal/sessions✅ Bearer obligatorio (iframe la envía)
/terminal/session/*✅ Bearer obligatorio

Cambios detallados

1. terminal/agent/core/local_token.py

2. terminal/agent/assets/terminal.html

3. terminal/agent/version.py

4. tests/agent/test_agent_local_auth.py


Validación


Implicaciones


Véase también

Subir