s129: Fix arranque Agent 2.1.1 (dashboard SaaS desbloqueado)
Resumen ejecutivo
Sesión 129 · 2026-06-11 · Fix crítico al Agent 2.1.0 que lo hacía inservible en arranque.
El 2.1.0 introdujo Fase 3: autenticación local (token Bearer opaco) pero fue demasiado estricto desde el primer segundo: antes de tener el token depositado, rechazaba legítimas llamadas del frontend (POST /saas/setup 401) → dashboard SaaS mostraba “Sentinel no disponible” → el Agent nunca se autenticaba → 401 en cascada → inservible.
Este fix cambia el enfoque: el enforcement arranca DESACTIVADO por defecto, gatekeeping con flag AGENT_LOCAL_AUTH_ENFORCE (env var, default OFF). El .exe deposita el token (inocuo) pero ni API local ni WS de consola exigen nada hasta verificación E2E y activación explícita → .exe recién instalado es compatible con el frontend actual.
Version: 2.1.0 → 2.1.1
Problema (2.1.0)
El commit anterior (s128, Auditoría Suprema Etapa 3) añadió un middleware HTTP + WS que gateaba TODO con Bearer local: si no hay token depositado (aún no hay sesión), se rechazan todas las llamadas.
En el bootstrap del Agent recién arrancado:
.exeintentaPOST /saas/setup(base.js fleetReauthAgent) para sincronizar sesión- Gate rechaza: 401 Unauthorized (aún sin token local)
- Frontend nunca se autentica → no deposita el token → 401 perpetuo hasta F5
- Dashboard queda en blanco: “Sentinel status not available”
A1: EXEMPT_PATHS era incompleto. El 2.1.0 solo eximía /saas/configure|status|connect, pero la reautenticación usa POST /saas/setup (faltaba).
A2: El gate no distinguía entre bootstrap y uso normal. Exigir token desde el primer segundo rompe el flujo inicial.
A3: Frontend (agent_auth.js) memoizaba el resultado del token UNA vez. Si la 1ª obtención devolvía null (Agent arrancando), la página se quedaba sin token para siempre (401s hasta recargar).
Solución (2.1.1)
(1) Flag de enforcement centralizado
Archivo: terminal/agent/core/local_token.py · LOCAL_AUTH_ENFORCE
# Fase 3 — el enforcement del Bearer local arranca DESACTIVADO por defecto.
LOCAL_AUTH_ENFORCE = os.environ.get("AGENT_LOCAL_AUTH_ENFORCE", "").strip().lower() in ("1", "true", "yes", "on")
def auth_required(auth_manager) -> bool:
"""Exige token solo con flag activo Y Agent vinculado a SaaS."""
if not LOCAL_AUTH_ENFORCE:
return False # Bootstrap compatible, token inocuo
try:
return bool(auth_manager and auth_manager.saas_url and auth_manager.agent_id)
except Exception:
pass
return False
Efecto:
.exedeposita el token sin problemas (inocuo).- HTTP middleware + WS de consola respetan el flag a la vez (gate centralizado).
- Default OFF → no rompe arranque; dev puede activar en entorno cuando E2E esté verificado.
(2) EXEMPT_PATHS completo
Archivo: terminal/agent/core/local_token.py · EXEMPT_PATHS
Ahora exime TODO el flujo /saas/* real:
/saas/configure— configuración inicial/saas/status— estado del Agent/saas/connect— conexión/saas/disconnect— desconexión/saas/reconnect— reconexión/saas/setup— reautenticación inicial (antes bloqueada)/saas/profiles— perfiles de sesión/saas/switch— cambio de perfil/saas/register-profile— registrar nuevo perfil
Además /agent/role (estado de solo lectura).
Justificación: estos endpoints transportan JWT del SaaS (claims agent_id/tenant_id firmados por crearack.com), no token local. Su seguridad descansa en ese JWT, no en el Bearer local.
(3) Frontend: token sin caché de fallos
Archivo: static/js/modules/agent_auth.js · getAgentLocalToken()
function getAgentLocalToken() {
if (!tokenPromise) {
tokenPromise = fetchToken().then((token) => {
if (!token) tokenPromise = null; // Reset si falla
return token;
});
}
return tokenPromise;
}
Efecto:
- Antes: si
fetchToken()devolvíanull(Agent sin token), la promesa se memoizaba → página condenada a 401s perpetuos. - Ahora: un
nullresetea la promesa → siguiente llamada reintenta → eventualmente el Agent deposita el token → éxito sin F5.
Testing
Archivo: tests/agent/test_agent_local_auth.py
Nuevos test cases:
test_auth_required_off_by_default(): Flag OFF →auth_required()siempre devuelve False (bootstrap compatible).test_auth_required_when_enforced_and_linked(): Flag ON + Agent vinculado →auth_required()devuelve True.test_exempt_paths_cover_bootstrap(): EXEMPT_PATHS cubre todos los/saas/*+/agent/role.
✅ 7 tests verdes + ruff check/format limpios (Docker, entorno CI).
Pasos de despliegue (Edu)
- Build:
build_agent.bat(escribeAGENT_VERSION = "2.1.1") - Smoke test: verificar que Agent arranca sin 401s en dashboard
- E2E verificado: comprobar circuito frontend ↔ Agent sin flag
- Activar flag:
AGENT_LOCAL_AUTH_ENFORCE=1en entorno de validación - Publicar:
agent-v2.1.1release; borrar 2.1.0 (nunca fue publicado a clientes)
Impacto en tiempo de ejecución
- SaaS dashboard: desbloqueado (bootstrap funciona sin flag)
- Seguridad local: token Bearer depositado e inocuo por defecto; enforcement vía flag + JWT del SaaS
- Compatibilidad:
.exe2.1.1 + frontend actual = cero rotura
Véase también
- [[entity—terminal—service—local-token]]
- [[entity—terminal—service—agent-auth-js]]
- [[concept—saas—multi-tenancy]]
- [[concept—terminal—bootstrap-agent]]
- [[decision—20260610—fase-3-auth-local]]