Volver a la wiki

Prescan explícito de Windows Defender ANTES del swap (ADR: cerrar s201 · cierre diagnóstico auto-update)

Contexto

El auto-update del Agente sufría fallos aleatorios no-deterministas que NO se producían con instalación manual del mismo binario. Diagnóstico s201 (activo desde 03-07-2026).

Carrera crítica: El instalador del navegador tarda minutos entre descargar el .exe y ejecutarlo (el usuario hace click). En ese tiempo, Windows Defender completa su análisis cloud. El auto-update descarga y ejecuta en segundos → el análisis está a medias → MpCmdRun (protección cloud de Defender) retiene la extracción del onefile del binario congelando el arranque sin matarlo.

Observación del watchdog de 2.13.4: Detectó “process alive but not responding” tras 30 segundos y decidió: “debe estar roto, killamos y hacemos rollback”. Pero el proceso NO estaba roto, estaba retenido. Resultado: rollback de un binario funcional.

Decisión

Prescan proactivo de Defender ANTES del swap (nuevo en 2.13.5):

  1. En _apply_swap(), antes de _selftest_new_exe(), llamar a _defender_prescan(NEW_EXE)
  2. _defender_prescan ejecuta MpCmdRun -Scan -ScanType 3 -File <path> (síncrono, 180 s timeout)
  3. Al terminar, el análisis es “conocido” y el primer arranque post-swap behave como el de la instalación manual
  4. Best-effort: sin MpCmdRun (Linux CI, otro AV), se salta con traza

Rationale

Alternativas consideradas (y rechazadas)

AlternativaMotivo del rechazo
Esperar 60 s entre descarga y swapDejaría el Agente sin servicio 60 s innecesariamente; el prescan es más eficiente
Bajar la confianza del certificado de código (pagar)Costo O(100 €/año); Defender es gratis
Habilitar análisis offline (descarga local de sigs)O(150 MB) en el onefile; sin garantías offline en máquinas con blue screen
Usar VirusTotal o similarDependencia externa + latencia + límites de rate; incompatible con aire cerrado

Implementación

Archivo: terminal/agent/core/updater.py

def _defender_prescan(path) -> None:
    """Fuerza el análisis de Defender ANTES del primer arranque post-swap."""
    import subprocess
    
    mpcmdrun = os.path.join(os.environ.get("PROGRAMFILES", r"C:\Program Files"),
                            "Windows Defender", "MpCmdRun.exe")
    if not os.path.exists(mpcmdrun):
        _wlog("prescan Defender omitido (MpCmdRun no encontrado)")
        return
    
    try:
        proc = subprocess.run(
            [mpcmdrun, "-Scan", "-ScanType", "3", "-File", str(path)],
            capture_output=True,
            timeout=180,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
        )
        # rc 0 = limpio · rc 2 = amenaza (self-test posterior fallará → swap abortado)
        _wlog(f"prescan Defender del staged: rc={proc.returncode}")
    except Exception as e:
        _wlog(f"prescan Defender fallo (no bloqueante): {type(e).__name__}: {e}")

Invocado en _apply_swap() antes del self-test.

Verificación

3 tests en tests/agent/test_agent_updater_trace.py:

  1. Watchdog contiene WaitHealthy y pollea cada 10 s (regresión contra 2.13.4)
  2. Prescan se salta sin MpCmdRun (CI + otro AV)
  3. version.txt se reconcilia tras rollback

Impacto

Véase también

Subir