§1Descripción general y principios básicos
RoamSwitch Server Edition es una suite de protección de integridad y seguridad de red autónoma residente y sin cabeza diseñada específicamente para infraestructuras de servidores Linux en proveedores de nube (AWS, GCP, Azure, VPS bare-metal) y centros de datos locales.
A diferencia de las ediciones de estaciones de trabajo cliente centradas en la movilidad y la adaptación del perímetro Wi-Fi, la edición Server se basa en cinco principios inflexibles: Caída predeterminada de entrada, Monitoreo de integridad de archivos de ruta crítica (FIM), LPE de kernel preventivo y refuerzo de escalada de privilegios, Telemetría de amenazas en tiempo de ejecución eBPF, y Aislamiento autónomo del host por espacio de aire con alertas de emergencia previas al despido.
El principio Zero Telemetry —ninguna biblioteca de comunicación externa en el propio demonio— se aplica a nivel de código (por defecto lo único que sale del host es la recepción de datos firmados (los datos de CVE del kernel, las reglas web y, en servidores Ubuntu, los datos de CVE de paquetes de Ubuntu que usa la comprobación de CVE de los paquetes del sistema; manifiesto comprobado cada día, los datos de CVE en sí aproximadamente cada 30 días), que además se puede desactivar, y toda otra excepción es algo que el operador activa de forma explícita; véanse los principios de diseño del §1), y los secretos (tokens de bot y configuración) se protegen con permisos 0600.
1.1 Comparación: Edición Cliente versus Edición Servidor
| Dimensión | Edición de cliente (escritorio/móvil) | Edición de servidor (sin cabeza/infraestructura) |
|---|---|---|
| Implementación | Computadoras portátiles, estaciones de trabajo móviles para desarrolladores | VM en la nube, nodos bare-metal, hosts de contenedores |
| Comportamiento de la red | Cambio de perfil dinámico basado en Wi-Fi SSID/Gateway MAC | Descarga predeterminada de entrada estática. Sólo se exponen los puertos permitidos explícitamente |
| Interfaz de usuario | GUI de escritorio GTK3 + indicador de bandeja | CUI puro sin cabeza. Comandos CLI + asistente de configuración interactivo + interfaz de terminal (roamswitch-server-tui) |
| Notificaciones de incidentes | Notificaciones de escritorio (libnotify) | Envío directo a Telegram Bot, API de mensajería LINE y Webhooks (Slack/Discord) |
| Defensas Primarias | Suplantación de ARP de Wi-Fi pública, BadUSB, cifrado de documentos ransomware | Caída de reconocimiento externo, detección de manipulación FIM, fugas de contenedores, kernel LPE (CVE-2026-53362) |
1.2 Principios arquitectónicos básicos
- Zero Telemetry: por defecto no se envía al exterior nada sobre el usuario ni el entorno. El propio demonio no incluye ninguna biblioteca de comunicación HTTP/TLS; solo para las notificaciones y reenvíos que el operador ha configurado ejecuta, como subprocesos, el
curldel sistema (Telegram / LINE / webhook; los secretos como tokens y URL se pasan en una configuración por la entrada estándar, nunca en la línea de comandos) yopenssl(reenvío de syslog por TLS). La única comunicación externa activada por defecto, aparte de las actualizaciones del gestor de paquetes, es la siguiente. (1) Mientrascve_kernel_map_updates_enabled(true por defecto) esté activado,roamswitch-updaterrecibe de forma anónima desde lafine.net datos firmados. Son los datos de CVE del núcleo para la auditoría de aislamiento de contenedores, las reglas de detección de la capa de aplicaciones web (reglas web) y, en servidores Ubuntu, los datos de CVE de paquetes de Ubuntu que usa la comprobación de CVE de los paquetes del sistema. El manifiesto firmado y su firma se comprueban cada día (dos GET); los datos de CVE del núcleo y los de paquetes de Ubuntu solo se obtienen cuando han pasado unos 30 días desde la última actualización (un servidor que aún no tiene los datos de Ubuntu los obtiene en la siguiente comprobación diaria), y las reglas web solo cuando el manifiesto tiene una versión más reciente. Es un simple HTTPS GET sin cadena de consulta ni cookies, cuya única cabecera que nombra el software es unUser-Agent: RoamSwitch-Updaterfijo; solo se aceptan datos cuya firma Ed25519 se verifica, y no se envía nada sobre este host. El operador puede desactivarla conroamswitch server config set cve_kernel_map_updates_enabled false, el asistente de configuración o la TUI del servidor; un false explícito en unserver.confexistente se respeta tal cual, y la auditoría sigue funcionando con la línea base incorporada cuando está desactivada; en cambio, la comprobación de CVE de los paquetes del sistema en un servidor Ubuntu indica entonces «sin datos de CVE locales» y no devuelve ningún resultado. Toda otra excepción la activa el operador de forma explícita. (2) El reenvío de eventos (forward_enabled, false por defecto) solo envía a los destinos syslog / archivo JSON Lines / webhook firmado que el operador configuró. (3) Solo sinpm_audit_signatures_enabled(false por defecto) está activado y el operador ejecutaroamswitch audit-npm-signatures <DIR> --confirm, esnpmquien contacta con registry.npmjs.org. (4) Solo sisensor_pairing_enabled(false por defecto) está activado se conecta, y únicamente a la dirección del Sensor que introduce el operador (el Sensor es un producto aparte, no cubierto por Zero Telemetry). (5) Fleet (nivel Business) es solo código del lado del dispositivo: un dispositivo no inscrito no envía nada ni resuelve ningún nombre; el servicio de gestión no está desplegado ni publicado y, por diseño, no existe ningún canal de comandos remotos. (6) La integración con LLM de la herramienta independiente incluidaaccessscope(diagnóstico de seguridad de registros de acceso) está desactivada por defecto. Solo si se activa (en la línea de comandos, cuando se indica--llm) pueden enviarse un resumen del diagnóstico, líneas de registro como evidencia e información del entorno (en la función de búsqueda de registros) a un servicio externo mediante el comando de LLM que haya elegido el operador. El demonio (roamswitch-server) no participa en esta comunicación, AccessScope puede ejecutar su análisis con LLM también como root (el LLM se ejecuta entonces con los derechos de root; se recomienda un usuario sin privilegios), y queda fuera de Zero Telemetry. La herramienta independiente incluidapersonalsoc(revisión de registros y defensas) funciona igual: solo cuando se indica--llm, un resumen del diagnóstico puede enviarse a un servicio externo a través del comando LLM elegido por el operador (fuera de Zero Telemetry). El comandopathscopeno llama a ningún LLM. (7) Solo si se activaclamav_enabled(false por defecto), el demonio ejecutafreshclampara recibir las firmas de ClamAV (la primera vez, cuando aún no existe la base de datos de firmas, y después cadaclamav_freshclam_interval_secs(7200 segundos por defecto) mientras se especifiqueclamav_scan_dirs). - Descarte entrante por defecto (Inbound Default Drop) descarta con
nftableslas conexiones entrantes nuevas (TCP) hacia cualquier puerto que no esté permitido, sin devolver siquiera un RST. Un escaneo de puertos solo ve los puertos permitidos (se admiten el loopback, las conexiones establecidas, ICMP e ICMPv6). - Cierre fallido (fail-closed) & autorreparación funciona así. Las reglas de denegación por defecto y de aislamiento permanecen en el kernel, por lo que se mantienen aunque el demonio se detenga o se reinicie (
Restart=always). La protección de los puertos publicados de Docker se vuelve a comprobar cada 60 segundos, y la lista de bloqueo de egreso y el bloqueo de escaneo de puertos cada 300 segundos, reinstalando las reglas que hayan desaparecido. Las reglas de aislamiento en modo degradado (DEGRADED) se verifican en una evaluación cada 15 segundos y se reinstalan si faltan. Si alguien elimina desde fuera la propia regla de denegación por defecto, no se vuelve a aplicar automáticamente, así que compruébelo con el diagnóstico «Descarte entrante por defecto» (roamswitch server status). - Protección mediante permisos 0600: al archivo de configuración
/etc/roamswitch/server.confse le fuerzan automáticamente los permisos 0600 al crearlo y actualizarlo. Los secretos como tokens y URL de webhook también se pasan acurlen una configuración por la entrada estándar y nunca se colocan en la línea de comandos (que otros usuarios pueden ver mediante/proc/<pid>/cmdline).
§2Threat Landscape & Adversary Model
Modern internet-facing servers confront relentless automated scanning, container breakout vectors, and zero-day kernel privilege escalations.
- Reconocimiento automatizado y exposición de bases de datos: escáneres de Internet que localizan puertos de bases de datos internos expuestos involuntariamente (MySQL 3306, Redis 6379, Postgres 5432).
- Manipulación de ruta crítica e inserción de puerta trasera: Intrusos que establecen persistencia modificando los archivos binarios de autenticación centrales (
/bin/login,/usr/bin/sudo,/etc/pam.d/*,/etc/shadow). - Escalada de privilegios del kernel (CVE-2026-53362 Frag Gap): atacantes sin privilegios que utilizan espacios de nombres de usuario como armas (
unshare -U -r) para desencadenar la corrupción de la memoria del kernel y escalar a la raíz. - Container Socket Breakout: contenedores mal configurados con indicadores
/var/run/docker.socko--privilegedmontados que ejecutan acciones raíz en el sistema operativo host. - Espionaje de memoria de procesos: los atacantes adjuntan
ptraceo leen/proc/$pid/mementre procesos para extraer claves privadas o credenciales.
§3System Architecture
RoamSwitch Server Edition operates as native Rust binaries designed for minimal memory and zero CPU overhead.
┌─────────────────────────────────────────────────────────────┐
│ Espacio de usuario & administrador │
│ │
│ roamswitch (CLI) roamswitch-mcp (AI Agent) │
│ · server status (34 puntos) · herramientas de auditoría │
│ · server config / setup · JSON-RPC por stdio │
│ · fim verify / update │
│ │ │ │
└────────┼───────────────────────────────┼────────────────────┘
│ (comandos CLI & config) │ (stdio)
┌────────▼───────────────────────────────▼────────────────────┐
│ Base del sistema (systemd + privilegio root) │
│ │
│ roamswitch-server-daemon │
│ ├── CriticalPathFim (barrido SHA-256 en segundo plano) │
│ ├── módulo de integración eBPF / Falco │
│ ├── Notifier (alertas Telegram / LINE / Webhook) │
│ └── Air-Gap Controller (reglas de emergencia nftables) │
│ │
│ Configuración: /etc/roamswitch/server.conf (permiso 0600) │
│ BD de integridad: /var/lib/roamswitch/fim.db │
└─────────────────────────────────────────────────────────────┘
§4Core Defense Mechanisms
4.1 Boundary Security (Inbound Default Drop)
Aplica policy drop a la cadena input de la tabla inet roamswitch_server de nftables. Solo permite las conexiones TCP nuevas entrantes hacia los puertos indicados por el administrador (ssh_ports, allowed_ports); la comunicación hacia los puertos no permitidos expira por tiempo de espera (se descarta). Se permiten el loopback, las conexiones establecidas, ICMP e ICMPv6.
Aunque se active un Air-Gap (aislamiento de emergencia), las rutas de administración —los puertos SSH de mantenimiento (ssh_ports) y las IP de origen de mantenimiento registradas (whitelist_ips), tanto conexiones nuevas como establecidas— se mantienen en el conjunto de reglas, lo que evita quedarse sin posibilidad de recuperación remota (preserve_ssh_on_isolation, true por defecto). Cualquier otra conexión establecida (por ejemplo, un C2 o una reverse shell ya abiertos por el atacante) se corta con las cadenas default-drop. Si no se indica ninguna fuente de administración (whitelist_ips y las direcciones permitidas de guard.yaml están vacías), los puertos SSH de mantenimiento siguen aceptando conexiones nuevas desde cualquier origen durante el aislamiento, y el servidor emite una advertencia al arrancar. Con preserve_ssh_on_isolation=false, el aislamiento corta también el SSH. La edición de servidor hasta la 1.10.4 tenía un defecto. Las reglas de aislamiento descartaban las respuestas SSH, por lo que el SSH del operador no funcionaba durante el aislamiento. Se corrigió en la 1.10.5.
4.2 Critical Path File Integrity Monitoring (FIM)
Supervisa la integridad por hash SHA-256 de los archivos más críticos para el arranque, la autenticación, la red y la administración del sistema. El alcance incluye 23 rutas fijas (/etc/shadow, /etc/passwd, /etc/sudoers, /etc/ssh/sshd_config, /bin/login, /usr/bin/sudo, /usr/sbin/sshd, /usr/lib/systemd/systemd, etc.); los archivos situados directamente dentro de 12 directorios (/etc/pam.d, /etc/sudoers.d, /etc/ssh/sshd_config.d, /etc/cron.d y /etc/cron.{hourly,daily,weekly,monthly}, /etc/systemd/system, /etc/ld.so.conf.d, /etc/profile.d, /root/.ssh), donde también se detecta la aparición de archivos nuevos (en /root/.ssh se excluyen known_hosts y known_hosts.old, porque se reescriben cada vez que se establece una conexión SSH); los binarios propios de RoamSwitch; el authorized_keys de cada usuario que pueda iniciar sesión; y /etc/ld.so.preload, que no debe existir (se detecta su aparición). Tras una actualización legítima de paquetes, la línea base sana se actualiza automáticamente. En los sistemas basados en Debian, el hook /etc/apt/apt.conf.d/99roamswitch-fim (apt upgrade, etc.) y, en los basados en RHEL y en openSUSE, una unidad systemd.path (roamswitch-fim-update.path) que vigila /var/lib/rpm, ejecutan roamswitch fim update.
Determinación de la vía de intrusión (quién hizo el cambio y mediante qué inicio de sesión SSH)
Las notificaciones sobre un archivo modificado (FIM de rutas críticas y FIM de archivos de bloqueo) incluyen ahora el proceso que hizo el cambio (cuenta, ejecutable, comando) y el inicio de sesión SSH al que pertenece (IP y puerto de origen, hora de inicio de sesión, método de autenticación y huella de la clave pública). Está activado por defecto (fim_intrusion_context_enabled). En el momento en que se lee un evento, el demonio toma una instantánea de /proc/<pid> del proceso que escribe (ejecutable, usuario, loginuid, sessionid, cgroup, cadena de padres), porque un proceso como tee o sed desaparece un milisegundo después. Un proceso que ya había terminado se completa con el registrador de ejecuciones (si está activado). La asociación con un inicio de sesión usa primero la tabla de sesiones de la guardia SSH y, si no, las líneas Accepted de logind y journald. Funciona aunque la guardia SSH esté desactivada. No se envía nada al exterior.
Solo se confía en los valores que informa el kernel: el ejecutable sale de /proc/<pid>/exe (no de argv[0]), el inicio de sesión de loginuid (se fija una sola vez al iniciar sesión y se hereda a través de sudo y su) y del session-N.scope del cgroup, y el resto de la cadena de padres. Los valores que un proceso puede cambiar por sí mismo, como comm, la línea de comandos y el entorno, se muestran pero nunca se usan para decidir; un proceso que solo se llama sshd no se toma por un inicio de sesión SSH. Una notificación distingue tres respuestas: encontrado (origen, hora, método, clave); «No se encontró ningún inicio de sesión SSH» (la comprobación mostró un servicio de systemd, un contenedor, una sesión de inicio local o un proceso sin sesión de inicio); y «No se pudo determinar la vía de acceso» (el proceso ya no existía y no está en el registrador de ejecuciones, logind no respondió, el cambio lo halló el escaneo periódico y el proceso se desconoce, etc.). Lo que no se pudo comprobar nunca se presenta como «no es un inicio de sesión». La notificación sale aunque falle la consulta.
/etc/ssh/sshd_config: modified
Proceso: root (inició sesión como admin) / /usr/bin/tee / comando: tee /etc/ssh/sshd_config (pid 4121)
Inicio de sesión SSH: la sesión que admin abrió desde 203.0.113.25:51544 a las 2026-10-17 03:12:08 (autenticación: publickey, clave SHA256:AbCdEf0123456789)
/etc/sudoers: modified
No se pudo determinar la vía de acceso (el cambio lo encontró el análisis periódico, así que no se sabe qué proceso lo hizo)
Ahora también se detectan las ediciones que colocan un archivo nuevo bajo el mismo nombre: install, sed -i y cp seguido de mv en el mismo directorio. La marca por archivo se asocia al inodo, por lo que antes este tipo de edición no generaba ningún evento. Ahora el demonio marca también los directorios que contienen archivos vigilados y, cuando se cierra un archivo con otro nombre en el mismo directorio, vuelve a consultar el nombre unos milisegundos después. Límite: un archivo escrito en otro directorio y movido con mv solo lo encuentra el escaneo periódico (fim_check_interval_secs). Entonces no se conoce el proceso que escribió, y la notificación lo indica.
Las mediciones se hicieron en una VM con Ubuntu 24.04 con el origen del operador en un espacio de nombres de red (netns) aparte. Con sudo tee, con un proceso desligado mediante nohup (su IP de origen se siguió identificando tras cerrar el operador la sesión, con la nota «Esa sesión ya ha terminado»), con una cadena sudo -i y con un cat de vida muy corta, la notificación mostró la IP, el usuario y el método de autenticación. Un inicio de sesión local dio «No se encontró ningún inicio de sesión SSH». Un proceso que disfrazó su nombre con exec -a "sshd: …" no se tomó por SSH.
Además de la notificación, el hallazgo se conserva en la cronología de incidentes (tipo fim) y en /var/lib/roamswitch/fim_intrusions.json (solo root, los últimos 100). La herramienta MCP verify_fim lo devuelve en un campo añadido, recentIntrusionContext (solo si se ejecuta como root). El veredicto sobre si un archivo fue manipulado no cambia. Límites: con el registrador de ejecuciones desactivado y un proceso escritor que vive solo un instante, se desconoce el ejecutable y se muestran el ejecutable y la línea de comandos del proceso padre. Las notificaciones de la vigilancia de persistencia (persistence_watch) no llevan información de proceso, por lo que no reciben vía de intrusión. La vía muestra el proceso que hizo el cambio y su inicio de sesión, y nada más: la puerta de entrada en sí (la vulnerabilidad explotada o cómo se robó una clave) no se muestra. Para desactivarlo, pon fim_intrusion_context_enabled=false.
4.3 Kernel & Container Hardening
Frag Gap (CVE-2026-53362) Mitigation: Enforces user.max_user_namespaces = 0 via sysctl, neutralizing LPE vectors. Yama LSM Protection enforces kernel.yama.ptrace_scope = 2, preventing unauthorized memory inspection. Container workloads are audited for docker.sock exposure. En la edición de servidor, esta prohibición de los espacios de nombres sin privilegios está activada por defecto (harden_userns_enabled). Impide Podman rootless y similares; póngala en false si depende de ellos.
Los puertos publicados de Docker (-p) eluden, como es sabido, el cortafuegos estándar del host. Por ello, con protect_docker_ports=true (por defecto) se descarta el tráfico procedente de la interfaz externa cuyo puerto de destino previo al DNAT no esté entre los puertos permitidos (ssh_ports, allowed_ports) (el tráfico de retorno, el loopback y el tráfico entre contenedores no se ven afectados). Con el backend iptables / iptables-nft, las reglas van en la cadena DOCKER-USER (IPv4, y también mediante ip6tables si el IPv6 de Docker está activado); con el backend nftables nativo de Docker (Docker 29 y posteriores, configuración sin DOCKER-USER), en una tabla propia inet roamswitch_docker_guard (hook forward, prioridad -10), y ese único conjunto de reglas cubre IPv4 e IPv6. El demonio vuelve a comprobar periódicamente y reinstala las reglas que desaparecen. Además, un guardián siempre activo detecta en tiempo real, mediante docker events, el inicio de un contenedor --privileged o el montaje de /var/run/docker.sock y lo notifica de inmediato como riesgo de escape de contenedor (sin configuración; solo notificación, sin bloqueo automático). En hosts que manejan archivos destinados a terceros, como relés de correo o carpetas compartidas, el File Scan Guard, activable opcionalmente (YARA integrado + ClamAV opcional), ofrece análisis periódico y cuarentena automática.
Container Exec Guard (container_exec_guard_enabled, true por defecto) — coloca fanotify FAN_OPEN_EXEC (clase de notificación) en la raíz overlay2 de cada contenedor Docker en ejecución e inspecciona el contenido de cada archivo ejecutado con las reglas YARA integradas, notificando si hay coincidencia. Como se suscribe a docker events y después enumera los contenedores en ejecución con docker ps, los contenedores que ya estaban en ejecución cuando arrancó el demonio también quedan cubiertos. El contenido del archivo ejecutado se lee del descriptor de archivo que entrega fanotify, por lo que puede inspeccionarse aunque el archivo se borre a sí mismo justo después de ejecutarse. Solo notifica y no detiene ni deniega la ejecución (la edición Server no tiene ningún fanotify de clase de permisos). Docker 29 eliminó el campo obsoleto Status de los mensajes de docker events, por lo que hasta la 1.11.0 la suscripción fallaba de inmediato y Container Exec Guard se reconectaba cada 5 segundos sin llegar a obtener una suscripción. Desde la 1.11.1 usa Action, que tiene el mismo valor también en versiones anteriores de Docker. Cuando la suscripción sigue fallando, el intervalo de reconexión se duplica de 5 segundos hasta un máximo de 5 minutos, y la advertencia por la misma causa se registra solo la primera vez.
4.4 eBPF Runtime Threat Telemetry & Anti-Bloat Tuning
Utiliza sondas eBPF modernas (CO-RE/BTF) para interceptar intentos de escalada de privilegios y ruptura de contenedores en milisegundos. Para eliminar los puntos débiles estándar de Falco ( hinchazón de registros, agotamiento del disco y sobrecarga de CPU), RoamSwitch estandariza un perfil optimizado (/etc/falco/config.d/99-roamswitch-optimized.yaml):
- Filtrado de gravedad (
priority: warning): suprime la telemetría operativa ruidosa (información/aviso) en un 95 %, lo que revela incidentes de seguridad estrictamente procesables. - Precaída a nivel de kernel (
drop_failed_exit: true): Descarta las salidas de llamadas al sistema fallidas directamente en la sonda del kernel, lo que reduce los cambios de contexto de la CPU en más del 50 %. - Optimización del espacio de memoria (
cpus_for_each_buffer: 2): comprime los buffers circulares de 8 MB a 2 MB, limitando estrictamente el uso de memoria a 30–50 MB. - IPC directo por socket de dominio UNIX: coordinación inmediata mediante un socket en memoria (
/run/roamswitch/events.sock), sin pasar por E/S de disco. - Límite automático de registros (
/etc/logrotate.d/roamswitch-falco): conmaxsize 50My una rotación comprimida de 7 generaciones (ejecutada a diario), evita que los registros de Falco crezcan sin control.
Precisión y rapidez del aislamiento eBPF (sin dejarlo solo en manos de las reglas de Falco)
La precisión y la rapidez del aislamiento eBPF dependen de las reglas del sensor (Falco o Tetragon). De hecho, las reglas estándar de Falco 0.45.0 (29 en total) no incluyen ninguna que detecte un reverse shell en el host: las reglas de reverse shell son solo para contenedores, y algunas se descartan por la condición priority: warning de RoamSwitch. RoamSwitch no da por buenas las reglas; lo compensa de tres maneras: mide cuánto tarda el aislamiento, comprueba con regularidad que la vía de alertas está viva y detecta por sí mismo los reverse shells.
Medición de la latencia del aislamiento: por cada incidente eBPF el demonio registra el tiempo desde la detección de Falco hasta que lee la alerta (detect_to_receive_ms), desde que la lee hasta que termina la respuesta (receive_to_action_ms, medido cuando vuelve la llamada de aislamiento; en el Air-Gap del host, hasta que se carga el conjunto de reglas) y la suma de ambos (detect_to_action_ms). Los valores aparecen como campos añadidos en ebpf_incidents.json y en la herramienta MCP get_ebpf_incidents. La notificación de un proceso aislado incluye una línea «de la detección a la respuesta: N ms». Medido en una VM con Ubuntu 24.04 (Falco 0.45.0, modern eBPF): de 1 a 2 ms de la detección a la recepción, de 41 a 62 ms de la recepción al aislamiento del proceso más SIGKILL (42 a 64 ms en total) y 109 ms en total para el Air-Gap del host (dentro del objetivo de 150 ms). Las cifras son de esa VM. Tetragon pone la marca de tiempo al recibir, por lo que no tiene detect_to_receive_ms.
Heartbeat (ebpf_heartbeat_interval_secs; 600 segundos por defecto, 0 lo desactiva, en otro caso de 60 a 86400): el paquete incluye una regla de Falco propia de RoamSwitch, 96-roamswitch-heartbeat.yaml. En cada intervalo el demonio abre una ruta desechable que solo root puede usar (un nombre aleatorio bajo /run/roamswitch/heartbeat/) y mide cuánto tarda la alerta de esa regla en volver por la salida de Falco. Un solo viaje de ida y vuelta muestra a la vez que la regla está cargada, que Falco funciona y que la vía de salida está viva. Solo cuando dos comprobaciones seguidas no vuelven (la segunda un minuto después de la primera) avisa: «las reglas no están cargadas o la salida no llega», como máximo una vez cada 24 horas. Una línea de heartbeat se descarta sin llegar al aislamiento, la notificación, la lista de permitidos ni el registro de incidentes. No se envía nada al exterior. Los viajes de ida y vuelta medidos fueron de 2 a 6 ms; al quitar la regla el aviso llegó tras unos 164 segundos, y al restaurarla el estado volvió a la normalidad en unos 75 segundos.
El estado se guarda en /var/lib/roamswitch/ebpf_heartbeat.json y lo leen el campo heartbeat de la herramienta MCP get_ebpf_incidents y el diagnóstico K-04 (agente de ejecución eBPF; el número de puntos de diagnóstico sigue siendo 34). Aunque Falco esté funcionando, K-04 falla si el heartbeat no vuelve. Límites: se comprueba un viaje de ida y vuelta de una sola regla (las llamadas al sistema de la familia open); no se puede saber si otra regla está rota por su cuenta. Lo que se rompe entre dos comprobaciones (10 minutos por defecto) no se ve hasta la siguiente. No se hace para Tetragon. En un host donde Falco no está en marcha o falta el archivo de la regla, no se comprueba nada ni se avisa de nada (de eso se encarga la notificación existente de «guardia detenida»). Falco escribe una línea de aviso en syslog por cada comprobación (144 líneas al día con el valor por defecto).
Detección de reverse shell (propia de RoamSwitch)
Sin apoyarse en las reglas de Falco, RoamSwitch lee por sí mismo /proc cada 2 segundos y busca una shell o un intérprete (sh, bash, python, perl, ruby, php, node y similares) cuya entrada y salida estándar sean una única conexión TCP que este host abrió hacia una dirección externa. Se cubren bash -i >& /dev/tcp/…, python, perl, nc -e y socat exec:. Solo se informa de lo que se ve dos veces seguidas, una vez por proceso. Por defecto solo cuentan las direcciones externas públicas; las privadas (RFC 1918, CGNAT, enlace local) cuentan solo con reverse_shell_watch_include_private=true, porque el ProxyCommand de ssh, los contenedores y el desarrollo local las hacen frecuentes y legítimas. El loopback nunca se vigila. Las conexiones que el host aceptó (por inetd o socat TCP-LISTEN) tampoco se cubren. Medido en una VM con Ubuntu 24.04, bash /dev/tcp, python, perl, nc -e y socat exec: se detectaron cada uno unos 2,4 segundos después de la conexión. No se detectan ssh, ssh con ProxyCommand, git sobre ssh ni un cliente nc.
Una detección entra por la misma vía que una alerta de Falco (lista de permitidos, enfriamiento de notificaciones, acciones por gravedad de guard.yaml, registro de incidentes) como un evento eBPF Critical llamado RoamSwitch Reverse Shell. Por defecto aísla el proceso y lo mata (kill). Un reverse shell propiedad de root no se puede aislar por proceso, así que, como cualquier evento Critical, pasa al Air-Gap de todo el host. Hay dos salidas si preocupa que se marque por error un programa legítimo: reverse_shell_watch_notify_only=true (un Warning con solo notificación; no se aísla ni se mata nada; false por defecto) y una entrada en la lista de permitidos, sudo roamswitch server allowlist add "RoamSwitch Reverse Shell" <nombre del proceso>. Las tres claves de ajuste son reverse_shell_watch_enabled (true por defecto), reverse_shell_watch_include_private (false) y reverse_shell_watch_notify_only (false).
Límites: se filtra por nombre (nombre del proceso y del ejecutable), así que un binario casero con un nombre distinto del de una shell o un intérprete que ate su entrada y salida estándar a un socket no se encuentra. Las conexiones por loopback (como el retorno de ssh -R) y las de menos de 2 segundos no se ven, ni tampoco los procesos que no son visibles desde el espacio de procesos del host. Esta detección existe solo en la Server Edition; el Mac y el cliente Linux no la tienen.
4.5 Aislamiento de emergencia por espacio de aire con alertas previas al despido
Inmediatamente antes de cortar las interfaces de red ante una manipulación crítica, el demonio envía sincrónicamente alertas de emergencia a Telegram, LINE y Webhooks. Las alertas agregan automáticamente el servidor Hostname y Primary IP Address.
🚨 [RoamSwitch IMMINENT AIR-GAP] CRITICAL: IMMINENT AIR-GAP HOST ISOLATION (cve_2026_53362_lpe)
🚨 CRITICAL SECURITY INCIDENT DETECTED: cve_2026_53362_lpe
Host: production-node-01 (IP: 192.168.1.10)
Process: exploit_payload (PID: 4921)
ACTION: Initiating host Air-Gap network isolation (SSH preservation active).
WARNING: Outbound internet connectivity will be cut immediately after this message.
Timestamp: 2026-09-06 18:00:00
⚠️ Network connectivity will be severed immediately.Las condiciones de levantamiento dependen de la confianza del activador. Los activadores de baja confianza (por ejemplo, un evento eBPF de nivel Error o inferior) se restauran por completo con un temporizador corto (server_safeguards.safety_timer_secs en guard.yaml, 300 segundos por defecto). Los activadores de alta confianza (eBPF Critical/Emergency/Alert, escalada a aislamiento del host por una regla que se repite, manipulación de un canario, etc.) nunca restauran en silencio. Tras el temporizador corto se vuelve a comprobar la causa (si el PID congelado sigue vivo, si la regla se disparó de nuevo, etc.) y el aislamiento se levanta, con notificación, solo cuando se verifica que la causa ha desaparecido. Si no se puede verificar y no hay ACK al alcanzar el límite (isolation_hard_cap_secs, 3600 segundos por defecto; 0 = sin límite), pasa a DEGRADED. DEGRADED conserva la ruta de administración (puertos SSH de mantenimiento e IP de origen de mantenimiento, conexiones nuevas y establecidas), permite tráfico saliente nuevo solo hacia las IP de origen de mantenimiento y nunca se reabre por temporizador. Solo se levanta mediante una reverificación que confirme que la causa desapareció, con roamswitch server ack (un ACK durante DEGRADED significa levantamiento) o con roamswitch emergency-restore. Un aislamiento con ACK en estado FULL se considera asumido por el operador: ni se restaura por temporizador, ni se levanta automáticamente cuando se confirma que la causa desapareció, ni pasa a DEGRADED, y se mantiene hasta que lo levanta emergency-restore. Cada transición se anota en la cronología de incidentes y se notifica.
Registrador de ejecuciones de procesos & reenvío de eventos
Un registrador estrictamente local que anota exec mediante el conector de procesos de netlink funciona activado por defecto (compartido con la edición Client). Guarda la ruta del ejecutable, los argumentos de la línea de comandos (con longitud limitada y caracteres de control escapados), la ascendencia de procesos padre, el uid, el ID del contenedor, el SHA-256 del ejecutable, etc. en /var/lib/roamswitch/exec_log como JSON Lines con cadena de hashes (0600; 200 MB / 14 días por defecto). La cadena de hashes hace que la manipulación sea detectable pero no evitable (un atacante que obtenga root puede reescribir toda la cadena). Las reglas de correlación solo de notificación EXR-001 a EXR-010 (shell lanzada por un servicio web, ejecución desde /tmp o memfd, cadenas de herramientas de ataque, etc.) nunca aíslan por sí solas. Solo si un operador activa explícitamente exec_rules_isolation_optin (false por defecto), una regla de gravedad alta se trata como activador de alta confianza del aislamiento del host.
El reenvío de eventos (forward_enabled, false por defecto) solo envía a los destinos que el operador configuró: syslog (RFC 5424 o CEF, por UDP / TCP / TLS), un archivo JSON Lines o un webhook firmado con HMAC-SHA256. El syslog TLS pasa por el openssl s_client del sistema y el webhook por el curl del sistema (los secretos se pasan en una configuración por la entrada estándar, nunca en la línea de comandos); el demonio no incluye ningún crate TLS/HTTP. Lo que se envía se limita a los campos nombre de host, ID de evento, marca de tiempo, origen, gravedad, regla, acción, PID, nombre del proceso y resumen —para los eventos de detección que aparecen en la cronología de incidentes y el historial de notificaciones (eBPF, transiciones del estado de aislamiento, FIM, canarios, etc.) y para las notificaciones de reglas de ejecución— más los hashes de sellado de segmentos del registro de ejecuciones. El registro bruto de ejecuciones no se incluye.
Detección de tunelización DNS / exfiltración
Al activar dns_tunnel_detect_enabled (valor predeterminado: true), se inspeccionan las consultas DNS salientes propias de este host (solo puerto 53 UDP/TCP) mediante un consumidor NFQUEUE dedicado. Una consulta se cuenta como de «forma sospechosa» —el patrón característico de herramientas de tunelización DNS como iodine o dnscat2— cuando su etiqueta supera los 40 bytes Y su entropía de Shannon supera 3,8, o cuando su tipo de registro es TXT/NULL (solo las consultas TXT hacia clamav.net, que usa la actualización de firmas de ClamAV, no se cuentan); solo se notifica cuando se producen 10 o más consultas de ese tipo en una ventana deslizante de 60 segundos (las notificaciones repetidas para el mismo dominio se espacian 300 segundos y, mientras la ráfaga continúa, se alargan a 600 y 1200 segundos, hasta un máximo de 1800 segundos). Solo se inspecciona el tráfico DNS (puerto 53 UDP/TCP); no se examina ningún otro tráfico. Solo notifica y nunca bloquea automáticamente. La regla nft subyacente lleva el indicador bypass, de modo que las consultas DNS siguen pasando sin verse afectadas aunque el hilo de detección esté detenido o no responda: la resolución de nombres nunca se bloquea por un fallo de esta función. Un cambio de este ajuste solo surte efecto tras ejecutar roamswitch server restart (sin recarga en caliente).
Limitación conocida — los nombres de host largos separados por guiones (por ejemplo, prod-us-east-1-web-server-node-0042) y una actividad legítima especialmente realista en un servidor —como un servidor de correo que verifica registros TXT de SPF/DKIM de muchos dominios remitentes distintos en poco tiempo— provocaban aquí falsos positivos, según se comprobó de forma empírica, porque las ráfagas TXT/NULL se contaban con un único contador global que no distinguía el dominio de destino. Ambos se corrigieron el 23/09/2026. La corrección consiste en una lista blanca de entropía configurable y en contar las ráfagas por dominio apex (una aproximación sencilla basada en las dos últimas etiquetas del QNAME, no una implementación estricta de la Public Suffix List). La lista blanca incluye de forma predeterminada 16 sufijos de CDN/nube y PaaS habituales (cloudfront.net, akamaiedge.net, akamaized.net, edgekey.net, edgesuite.net, fastly.net, azureedge.net, googleusercontent.com, amazonaws.com, r2.dev, workers.dev, vercel.app, netlify.app, supabase.co, firebaseapp.com, web.app), y los administradores pueden añadir sus propios dominios internos mediante ServerConfig.dns_tunnel_entropy_allowlist (una clave separada por comas en server.conf, también editable desde el TUI). La lista blanca solo exime la comprobación de longitud de etiqueta más entropía; la detección por tipo de registro TXT/NULL sigue aplicándose siempre, independientemente de la lista blanca, lo que impide que un atacante disfrace un túnel bajo un dominio permitido (la única excepción son las consultas TXT hacia clamav.net que usa la actualización de firmas de ClamAV, que no se cuentan porque un tercero no puede crear subdominios bajo ese dominio). El contador de ráfagas ahora se organiza por dominio apex, lo que elimina los falsos positivos causados por un único contador global que ignoraba el dominio de destino (la lógica de decisión, con su ventana deslizante de 60 segundos y su umbral de 10 consultas, no se modificó con esta corrección; el intervalo entre notificaciones repetidas se cambió después para alargarse de 300 a un máximo de 1800 segundos mientras la ráfaga continúa). El tráfico malicioso real de herramientas como iodine o dnscat2 siempre rota etiquetas y tipos de registro bajo un único dominio controlado por el atacante, por lo que limitar el recuento por dominio apex no reduce la eficacia de la detección.
Detección y bloqueo de fuerza bruta SSH (ssh_guard)
Una defensa contra fuerza bruta propia de RoamSwitch, activa con ssh_guard_enabled (true por defecto). Sigue el registro de sshd desde journald con journalctl -f (solo sin journald sigue auth.log o secure), cuenta los fallos por dirección de origen y, para un origen que alcanza el umbral (por defecto 6 en 600 segundos), descarta solo las conexiones nuevas (ct state new) hacia los puertos SSH en la tabla inet roamswitch_sshguard. En el momento del bloqueo también se cortan las conexiones que ese origen ya tenía abiertas hacia SSH (se eliminan sus entradas de conntrack y una regla descarta los paquetes del sentido de respuesta). Solo se bloquea un origen sin sesión autenticada, sin inicio de sesión con clave pública registrado y sin exención, y esto se comprueba otra vez justo antes de cortar, así que una sesión autenticada y un origen exento nunca se cortan. Un bloqueo empieza en 1 hora, se cuadruplica en cada reincidencia y tiene un tope de 7 días; el vencimiento lo gestiona el timeout de nftables. No inicia el Air-Gap y no envía nada al exterior (solo ejecuta localmente comandos como journalctl, nft y loginctl). Cuando fail2ban (con una jail sshd activada) o CrowdSec (con un firewall bouncer en marcha) ya protege sshd, le cede el bloqueo y solo detecta y notifica.
Las líneas del registro contienen una cadena que elige el atacante (el nombre de usuario). Por eso la dirección de origen se lee solo del final de la línea, en from <IP> port <N>, y una dirección de origen falsa puesta en el nombre de usuario no hace que se bloquee a un tercero. De journald solo se leen las líneas cuyas credenciales del remitente (_UID, _COMM) son las de un sshd de root, y se ignoran las escritas con logger -t sshd. Seguir un archivo (auth.log) no es lo predeterminado, porque el archivo no tiene ningún campo que avale al remitente y cualquier usuario local que pueda usar syslog puede falsificar líneas. Para no dejar fuera a los operadores, nunca se bloquean la puerta de enlace, el DNS, el propio host, el bucle local, whitelist_ips, ssh_guard_allow, el origen de una sesión SSH autenticada, cualquier dirección que haya iniciado sesión con clave pública en los últimos 30 días y un Sensor emparejado. Las direcciones privadas no están exentas. Un fallo de clave pública (Failed publickey) no se cuenta.
Medido (una VM Ubuntu 24.04 con journald y systemd reales): del 6.º fallo al bloqueo pasaron unos 0,2 segundos (de 0,17 a 0,24 segundos). La dirección de origen del operador no se bloqueó, y un intento con una dirección de origen falsa en el nombre de usuario no hizo que se bloqueara a un tercero. Un hydra real (4 en paralelo) se detuvo tras 6 fallos. También se comprobaron la restauración de un bloqueo con su tiempo restante tras reiniciar el servidor, el levantamiento de un bloqueo y el paso a solo detectar y notificar mientras fail2ban protege sshd. Estas cifras proceden de este entorno de VM. Medido con el corte de conexiones añadido: los fallos que aún pasaban tras un bloqueo eran de 16 a 24 antes y de 0 a 4 después. Eliminar solo las entradas de conntrack no cortaba las conexiones; solo funcionó al añadir una regla que descarta el sentido de respuesta.
Reglas de la capa de aplicación web (web shells y código inyectado)
La edición Servidor no tenía ningún análisis activado por escritura, solo el análisis periódico de clamav_scan_dirs. Ahora vigila las raíces de documentos que encuentra en la configuración del servidor web (Apache, nginx, Caddy) con fanotify solo de notificación (FAN_CLASS_NOTIF, que nunca puede detener a otro proceso) y comprueba los archivos PHP, JavaScript, JSP y ASP en cuanto se escriben y cierran, con las reglas de la capa de aplicación web (web shells, código ofuscado inyectado, PHP oculto en imágenes, abuso de .htaccess). Un análisis periódico cada 6 horas cubre los eventos perdidos y los archivos anteriores al inicio. Las reglas tienen tres capas (integradas, instaladas por el actualizador diario tras verificar la firma y sus archivos en /etc/roamswitch/rules.d/*.json); las 18 reglas no contienen ninguna firma extraída de una muestra real y describen solo técnicas documentadas públicamente.
En la edición Servidor, la cuarentena automática se limita a critical por defecto. Un resultado high solo notifica y dispara el hook posterior a la detección; el archivo de un sitio en producción no se toca. Las notificaciones (Telegram, LINE, webhook) están en 10 idiomas e indican la regla y si el archivo se puso en cuarentena o se dejó. La obtención de reglas va incluida en la misma obtención única del actualizador diario y no añade ningún destino. Con cve_kernel_map_updates_enabled desactivado no hay ningún contacto de red y se usan las reglas integradas. Con un fanotify real, una web shell escrita se puso en cuarentena en menos de 2 segundos y un archivo inofensivo se dejó intacto.
Tras una detección, un ejecutable colocado en /etc/roamswitch/hooks/post-detection recibe los resultados como JSON por la entrada estándar (solo se ejecuta un archivo propiedad de root, no escribible y en un directorio seguro; sin shell, entorno mínimo y parada a los 30 segundos). La respuesta opcional sobre cuentas (desactivada por defecto) revoca el shell de inicio de sesión del propietario de un archivo puesto en cuarentena. No hace nada con root, cuentas por debajo de account_min_uid (1000 por defecto), usuarios con sesión iniciada ni el usuario del demonio; el shell anterior se registra y roamswitch web-rules unlock-account lo restablece. Falsos positivos medidos: 0 en el núcleo de WordPress (2.409 archivos), phpMyAdmin (3.356 archivos) y dependencias npm (14.743 archivos). Límites: solo se leen los primeros 256 KB (al escribir/ejecutar) y 4 MB (análisis), no se siguen nombres fragmentados como 'ev'.'al' y, por falta de muestras reales, no se incluyen firmas de malware concreto.
Honeytokens (señuelos de credenciales)
Al iniciarse el daemon, RoamSwitch despliega de forma idempotente archivos de credenciales señuelo en rutas reales conocidas que los atacantes y las herramientas automatizadas de robo de credenciales realmente rastrean: /root/.aws/credentials, /root/.ssh/id_rsa, /root/.docker/config.json, /root/.pypirc y /root/.env.backup (bajo /root, ya que el daemon se ejecuta como root), cada uno con un marcador ROAMSWITCH_HONEYTOKEN (activado por defecto; honeytokens_enabled lo desactiva y honeytokens_home cambia dónde se colocan los señuelos). Un archivo real ya existente en cualquiera de esas rutas nunca se sobrescribe (el despliegue siempre comprueba primero su existencia). El acceso real a un honeytoken también puede desencadenar, con un límite máximo, el despliegue en cascada de señuelos adicionales relacionados (un perfil de AWS adicional, un config de SSH, un kubeconfig, etc.). Los paquetes hasta la 1.10.4 no incluían roamswitch-honeytokens, necesario para esta función, por lo que no se ejecutaba en las instalaciones desde los paquetes. Se incluye desde la 1.10.5, y el problema de que el sandbox de systemd bloqueara las escrituras en /root en la edición de servidor también se corrigió en la 1.10.5. Desde la 1.11.1, cada comando docker que ejecuta el propio RoamSwitch se inicia con un directorio privado vacío como DOCKER_CONFIG, por lo que no activa el señuelo /root/.docker/config.json. La lectura por cualquier otro programa se sigue detectando como antes.
Un proceso vigilante en segundo plano (roamswitch-honeytokens) supervisa la lectura y la apertura de cada señuelo mediante fanotify de clase de notificación (no detiene nada). Como el evento incluye el PID de quien accede, se puede identificar en el acto el ejecutable que accede (en entornos donde fanotify no está disponible, se cambia a inotify y fuser). Como ningún proceso legítimo tiene motivo alguno para leer estos archivos, cualquier acceso, salvo el de los binarios propios de RoamSwitch y el de los clientes OpenSSH sobre los señuelos SSH (ssh, scp, sftp, ssh-add, ssh-agent, etc.; ssh-keygen no está incluido), se trata como una señal fuerte de reconocimiento posterior a un compromiso por parte de un intruso (MITRE ATT&CK T1552, Unsecured Credentials) y se notifica. Un acceso cuyo ejecutable no se puede identificar tampoco se permite, sino que se trata como detección. Es solo de notificación: no se toman medidas adicionales, como congelar o bloquear el proceso que accede.
Limitación conocida — el contenido del señuelo /root/.ssh/id_rsa incluye texto legible y su cuerpo en base64 sigue sin ser un formato válido para una clave privada OpenSSH real. Sin embargo, la mayoría de los escáneres de secretos marcan los archivos únicamente mediante una coincidencia de patrón con la cabecera -----BEGIN ... PRIVATE KEY-----, por lo que consideramos limitado el impacto real en la tasa de detección. Por otro lado, la identificación del proceso que accede (mediante una llamada a fuser) puede no obtener el PID ni el nombre del proceso en el caso de procesos extremadamente efímeros que finalizan justo después de leer el archivo; en ese caso simplemente se registra como desconocido, lo cual no afecta a la detección del acceso en sí (el evento se dispara igualmente y se notifica).
Paquetes de evidencias (captura forense automática)
La captura de evidencias se lanza en estos casos. Un evento del sensor de amenazas en tiempo de ejecución basado en eBPF culmina en un aislamiento de red específico de un solo proceso con éxito; ese aislamiento falla y se escala a un Air-Gap de todo el host; o se ejecuta un aislamiento Air-Gap de todo el host. También se lanza en la vía del canario de ransomware, cuando se detecta una alteración, se detiene el proceso y se aísla el host. Cuando el corte afecta a todo el host, se lanza antes del corte y nunca lo retrasa. Hasta la 1.10.4 solo se ejecutaba tras un aislamiento específico de proceso con éxito; la 1.10.5 amplió esos casos. Si no se conoce ningún PID objetivo, se omite la instantánea de /proc. Se lanza roamswitch-incident-capture y guarda un paquete de evidencias en <directorio de estado>/incident-evidence/<marca de tiempo>-<id corto>/. Lo que se captura: una captura de paquetes acotada a 30 segundos mediante tshark en la interfaz de ruta predeterminada (registrada como omitida si tshark no está instalado), y una instantánea /proc/<pid> del proceso objetivo (status, cmdline, maps, un environ con los valores que parecen secretos sustituidos por [REDACTED], una lista de los destinos de los descriptores de archivo abiertos —no se captura su contenido— y la cadena de procesos ancestros). El fallo al capturar un artefacto concreto no se trata como error fatal; simplemente se registra y continúa el resto de la captura. Los paquetes hasta la 1.10.4 no incluían roamswitch-incident-capture, necesario para esta función, por lo que no se ejecutaba en las instalaciones desde los paquetes. Se incluye desde la 1.10.5.
Los resultados se recogen en manifest.json, que además incorpora el hash SHA-256 de cada uno de los demás archivos del paquete (para poder detectar manipulaciones posteriores). Los paquetes nunca se eliminan automáticamente: los paquetes antiguos solo se eliminan cuando un operador ejecuta explícitamente sudo roamswitch-incident-capture prune [--keep-days N] (retención predeterminada: 30 días). También está disponible en cualquier momento una captura manual mediante sudo roamswitch-incident-capture snapshot [--pid PID] [--reason TEXT] [--json].
Las pruebas capturadas no se verifican en el momento de la captura. Para comprobar posteriormente si han sido alteradas, ejecute sudo roamswitch-incident-capture verify <directorio del paquete> [--json]. El comando recalcula el hash de cada archivo presente actualmente en el paquete y lo compara con los valores registrados en manifest.json, informando cada discrepancia por separado como modificado, ausente o no registrado (un archivo presente que el manifiesto nunca registró).
Limitación conocida — esta verificación compara el SHA-256 de manifest.json con registros guardados fuera del paquete, en dos lugares: una cadena de hashes fuera del paquete (evidence_anchor.log, cada línea se encadena con la anterior, y se le asigna el atributo de solo anexado) y el registro del sistema (etiqueta roamswitch-evidence). Reescribir manifest.json tras la captura hace fallar verify; editar o eliminar una línea de la cadena también, porque la cadena se rompe. No detiene a un atacante con root que pueda reescribir tanto la cadena como el destino al que se reenvía el registro del sistema; se recomienda reenviar el registro del sistema a otra máquina. La firma no está implementada. Un paquete sin registro (capturado por una versión anterior) se indica como «sin registro», lo que significa que no tiene confirmación externa.
§5Paquete de diagnóstico de seguridad de 34 comprobaciones
Server Edition ejecuta 34 auditorías de estado de seguridad especializadas:
| # | Categoría | Comprobar título | Estándar y lógica |
|---|---|---|---|
| 1 | Autenticación y acceso | Autenticación de inicio de sesión y contraseña de raíz SSH | Verificación de PermitRootLogin no. Si la autenticación por contraseña también está deshabilitada, se informa como solo autenticación por clave pública; aunque esté habilitada, se considera superada si el inicio de sesión de root está prohibido |
| 2 | Autenticación y acceso | Cuentas de contraseña vacías | Garantiza que ningún usuario tenga un hash de contraseña en blanco en /etc/shadow |
| 3 | Autenticación y acceso | Higiene privilegiada de Sudoers | Auditoría de que no exista NOPASSWD: ALL, que concede todos los privilegios sin contraseña, en /etc/sudoers ni en /etc/sudoers.d |
| 4 | Red | Denegación predeterminada entrante | Auditorías nftables para la política de entrega entrante predeterminada |
| 5 | Núcleo e integridad | Espacios de nombres de usuarios sin privilegios (Frag Gap) | Bloqueo de la vía de ataque LPE mediante user.max_user_namespaces=0 (o kernel.unprivileged_userns_clone=0) |
| 6 | Núcleo e integridad | Soporte eBPF BTF | Valida la información del tipo BTF del kernel a través de /sys/kernel/btf/vmlinux |
| 7 | Núcleo e integridad | eBPF LSM activo | Comprueba bpf en el parámetro de arranque lsm= del kernel |
| 8 | Red | Compuerta XDP de arranque | Cubre con una compuerta XDP de denegación por defecto la ventana de arranque anterior a la instalación de la tabla nftables de Link Guard (experimental, solo por activación voluntaria, no se carga por defecto). Comprueba la presencia del enlace fijado; que no esté cargada no se considera un fallo |
| 9 | Núcleo e integridad | Agente de seguridad en tiempo de ejecución eBPF | Comprueba que un agente como Falco o Tetragon está en marcha. Mientras Falco funciona, comprueba también que vuelve la alerta de la regla propia de RoamSwitch (el heartbeat) y falla si no vuelve (indicando los archivos de Falco de RoamSwitch que falten) |
| 10 | Núcleo e integridad | Cronología unificada de incidentes | Comprueba que la cronología que unifica los incidentes de Link Guard, Canary, eBPF Guard y Resource Guard puede registrar (eventos existentes o almacén con permiso de escritura) y muestra el último tiempo de contención |
| 11 | Contenedor | Auditoría de montaje de zócalo Docker | Detecta montajes de contenedores de /var/run/docker.sock |
| 12 | Contenedor | Contenedores privilegiados de Docker | Auditorías para cargas de trabajo de contenedores --privileged de alto riesgo |
| 13 | Contenedor | Aislamiento del runtime de contenedores (runc/gVisor/Kata) | Comprueba el runtime de contenedores predeterminado. Un runtime basado en runc (comparte el kernel del host) no supera la comprobación; gVisor (runsc) o Kata Containers (aislamiento con una VM por contenedor) sí. No aplicable si no se detecta Docker/containerd |
| 14 | Contenedor | Protección contra la evasión del cortafuegos por Docker (DOCKER-USER) | Verifica que las reglas de RoamSwitch estén realmente presentes en la cadena DOCKER-USER (siempre evaluada primero) o en su tabla nftables dedicada de protección de Docker, para que los puertos publicados de los contenedores no eludan la denegación por defecto del cortafuegos mediante las reglas propias de Docker. Solo se evalúa si protect_docker_ports está habilitado en server.conf |
| 15 | Núcleo e integridad | Prueba real de detección de ClamAV (EICAR) | Dos minutos después del inicio y luego cada 6 horas, el demonio analiza un archivo de prueba EICAR inofensivo desde su propio entorno (por ejemplo, dentro de un sandbox de systemd) para confirmar que ClamAV lo detecta realmente. Solo se evalúa si clamav_enabled está activado en server.conf |
| 16 | Núcleo e integridad | fanotify: eventos perdidos (desbordamiento de cola) | Comprueba si el kernel ha descartado eventos de supervisión de FIM y de ejecución de contenedores por desbordamiento de la cola de eventos de fanotify. Las escrituras y ejecuciones de archivos durante un desbordamiento escapan a la supervisión en tiempo real; si ocurre con frecuencia, aumente fs.fanotify.max_queued_events |
| 17 | Núcleo e integridad | Exposición a CVE conocidas del kernel | Compara la versión del kernel en ejecución con el mapa local de CVE conocidas (línea base integrada o actualización con firma verificada). La comprobación en sí no realiza ninguna comunicación externa; las actualizaciones del mapa se descargan según cve_kernel_map_updates_enabled (true por defecto, se puede desactivar). Un resultado que no puede determinarse se notifica como no concluyente, no como un fallo |
| 18 | integridad | Integridad de archivos de ruta crítica (FIM) | Verificación de integridad SHA-256 de 23 archivos críticos fijos, de todos los archivos directamente dentro de 12 directorios, y más |
| 19 | Núcleo e integridad | Protección del proceso Yama | Prevención del espionaje de memoria mediante kernel.yama.ptrace_scope con valor 2 o 3 |
| 20 | Red | Reenvío de IP deshabilitado | Verifica net.ipv4.ip_forward = 0 |
| 21 | Red | Protección de cookies TCP SYN | Verifica net.ipv4.tcp_syncookies = 1 |
| 22 | Red | Filtrado de ruta inversa (rp_filter) | Descarte de paquetes falsificados mediante net.ipv4.conf.all.rp_filter con valor 1 (estricto) o 2 (flexible; para configuraciones con varias interfaces, multihomed) |
| 23 | Núcleo e integridad | Restricción de volcado de núcleo | Previene pérdidas de memoria de proceso a través de fs.suid_dumpable = 0 |
| 24 | Operaciones | Actualizaciones de seguridad automáticas | Comprobación de la configuración de aplicación automática de parches mediante unattended-upgrades (Debian / Ubuntu) o dnf-automatic (RHEL / Fedora), entre otros |
| 25 | Operaciones | Sincronización de hora NTP | Comprueba la sincronización a través de chrony o systemd-timesyncd |
| 26 | Operaciones | Protección de fuerza bruta SSH | Se supera mientras la guardia SSH propia de RoamSwitch (ssh_guard) esté en marcha y bloqueando, o verifica el monitoreo activo a través de fail2ban o crowdsec |
| 27 | Núcleo e integridad | eBPF sin privilegios deshabilitado | Prevención de Spectre/LPE mediante kernel.unprivileged_bpf_disabled con valor 1 o 2 |
| 28 | Red | Redirecciones ICMP ignoradas | Previene el envenenamiento de ruta vía accept_redirects = 0 |
| 29 | Red | Transmisiones de eco ICMP ignoradas | Mitiga los ataques DoS del amplificador Smurf |
| 30 | Núcleo e integridad | Endurecimiento de la memoria compartida | Comprobación de que /dev/shm tiene alguna de las opciones de montaje nodev, nosuid o noexec |
| 31 | Autenticación y acceso | Protocolos inseguros heredados ausentes | Verifica que no haya servicios en texto claro a la escucha (telnetd, rshd, tftpd, …) ni clientes en texto claro (telnet, rsh, tftp, …) instalados (los servicios y los clientes se informan por separado). |
| 32 | Núcleo e integridad | Volcados de núcleo del proceso SUID restringidos | Previene fugas de credenciales en fallas binarias SUID |
| 33 | Núcleo e integridad | Programas auxiliares (señuelos y captura de evidencias) instalados | El demonio inicia por nombre el programa de señuelos de credenciales (honeytokens) y el de paquetes de evidencias, y no hace nada, en silencio, si falta alguno. Esta comprobación verifica que ambos estén instalados. |
| 34 | Red | Archivos sensibles expuestos en el servidor web | Localiza las raíces de documentos en la configuración de Apache / nginx / Caddy y comprueba, con peticiones al propio servidor, si los .git, .env, .DS_Store, phpinfo, volcados SQL, copias de seguridad y claves privadas que hay en ellas se pueden leer realmente a través del servidor web. Un archivo solo se considera expuesto si la respuesta coincide con el comienzo del archivo en disco, de modo que un sitio que responde 200 a cualquier ruta, o un archivo ya denegado con 403, no genera una falsa alarma. Nunca se informa del contenido de los archivos, solo de las rutas. No aplica sin servidor web; si no se puede comprobar, el resultado es «no concluyente» y la puntuación no se reduce. |
Cada uno de los 34 elementos se etiqueta, cuando corresponde, con un identificador de función/categoría de NIST CSF 2.0 (por ejemplo, PR.DS-01, DE.CM-09) y, solo cuando existe una correspondencia fiable, también con un número de control de CIS Controls v8 (por ejemplo, 10.1, 4.1). Los elementos ambiguos no se fuerzan a una correspondencia, sino que quedan sin asignar. Esta correspondencia se incluye tanto en la salida de la CLI (roamswitch server status) como en la de MCP (get_security_report), permitiendo a los operadores y auditores que necesitan cobertura de marcos de cumplimiento asignar mecánicamente las comprobaciones de RoamSwitch a los marcos existentes.
§6Autoverificación (verificación reproducible en un entorno Docker)
Sobre el paquete roamswitch-server instalado desde el repositorio APT oficial, se realiza una Autoverificación que simula ataques internos y externos, con otro contenedor en Docker como atacante externo. La medición real del 2026-09-07 con la v1.3.2 fue de 18 puntos PASS en total. El 2026-09-25 se repitió con un paquete compilado a partir del código fuente de la serie 1.10 (arm64, Docker Desktop) y 19 comprobaciones fueron PASS. La nueva ejecución encontró 6 defectos del producto, todos corregidos (incluidos en la 1.10.5). Son los siguientes: el paquete no incluía los ejecutables auxiliares, el FIM informaba de los honeytokens como archivos nuevos, se ignoraba la confirmación (ack), durante el aislamiento se descartaban las respuestas del SSH de administración y el administrador quedaba bloqueado, el puerto 22 seguía abierto incluso con preserve_ssh_on_isolation=false, y el sandbox de la unidad systemd bloqueaba la escritura de los señuelos. Puede reproducir esta comprobación en su propia máquina. Consulte el procedimiento y los scripts en «Cómo reproducirlo usted mismo» más abajo.
| # | Escenario | Vector de ataque | Resultado de la verificación |
|---|---|---|---|
| PENT-S1 | Nmap SYN Escaneo sigiloso | Sonda a través de los puertos 22, 80, 443, 3306, 8080 | Con Default Drop de nftables, los puertos no permitidos como 3306 (MySQL) y 8080 no responden en absoluto y se descartan (Filtered). Bloqueo del 100% |
| PENT-S2 | Sondeo de puerto no permitido | conexiones curl directas al puerto 3306 y 8080 | Con el descarte entrante por defecto, solo el puerto permitido 80 responde con normalidad; el resto se bloquea por tiempo de espera agotado |
| PENT-S3 | Lista de bloqueo de Egress / C2 | Intento de conexión saliente a una dirección registrada en el feed de IP maliciosas | egress_ip_blocklist_enabled (true por defecto, nftables) bloquea solo las direcciones registradas. El tráfico hacia hosts no registrados no se ve afectado (no es un bloqueo general) |
| PENT-S4 | Ataque de manipulación FIM | Inyectar carga útil de puerta trasera en /bin/login |
El FIM de rutas críticas SHA-256 detecta la manipulación de inmediato. Tras una restauración legítima, la línea base se resincroniza correctamente |
| PENT-S5 | Contención basada en gravedad mediante guard.yaml |
Inyección de un evento Falco Critical que hace referencia a un proceso real | Con on_critical: isolate_process + kill_process, el proceso correspondiente se aísla de la red y después se termina con SIGKILL |
| PENT-S6 | Mecanismo de seguridad para procesos protegidos | Inyección de un evento del mismo tipo que hace referencia al PID real de sshd |
Gracias a la lista de procesos protegidos (sshd / systemd / runtimes de contenedores, etc.), sshd sigue funcionando. Solo se registra un rechazo como falso positivo |
| PENT-S7 | Temporizador de seguridad | Se dejó un incidente de Air-Gap sin confirmar (sin ACK del operador) | Con safety_timer_secs, se restaura automáticamente si no hay respuesta. Si ya se confirmó (ACK), el aislamiento continúa hasta emergency-restore |
| PENT-S8 | Brecha de fragmentación LPE del kernel | Ejecución de unshare -U -r con privilegios de usuario normal (comprobado en una VM real) |
unshare -U -r, que se permite sin el producto, se rechaza tras el arranque. user.max_user_namespaces y kernel.unprivileged_userns_clone valen 0, el ptrace_scope de Yama vale 2 y el eBPF sin privilegios está deshabilitado (2026-09-25, VM con Ubuntu 24.04; comprobado tanto con la versión publicada v1.10.4 como con la versión corregida) |
Nota: la medición de PENT-S7 es de la v1.3.2. En la versión actual, un aislamiento causado por un disparador de alta confianza se libera cuando se verifica que su causa ha desaparecido, y un aislamiento reconocido (ack) por el operador se mantiene hasta que el operador lo libera (un ack ignorado se encontró y corrigió en la nueva ejecución del 2026-09-25). Los disparadores de baja confianza se recuperan con el mismo temporizador. Véase §4.
Tipo de comprobaciones — PENT-S8 confirma que una función del kernel (prohibir espacios de nombres de usuario) está en vigor. PENT-S1 y S2 confirman las reglas nftables que instala RoamSwitch; quien descarta los paquetes es el kernel. Los puntos que ejercitan la lógica propia de RoamSwitch son PENT-S3 (aplicación del feed), S4 (FIM), S5 y S6 (contención y procesos protegidos) y S7 (temporizador de seguridad).
Prevención de bloqueo de SSH
preserve_ssh_on_isolation=true (predeterminado) está diseñado para mantener el canal SSH de administración durante un Air-Gap (on_emergency). Sin embargo, la nueva ejecución del 2026-09-25 mostró que, hasta la 1.10.4, las reglas de aislamiento descartaban las respuestas SSH, por lo que el operador quedaba sin acceso durante el aislamiento; la comprobación anterior solo había tenido éxito después de que se levantara el aislamiento. La corrección está en la 1.10.5, y se ha verificado, con la causa del aislamiento aún presente, que el SSH funciona durante el aislamiento.
Límites del alcance de la verificación (una divulgación honesta)
El endurecimiento del kernel (deshabilitar los espacios de nombres de usuario sin privilegios, aumentar el ptrace_scope de Yama) no se puede verificar de extremo a extremo desde dentro de un contenedor. Dado que estos sysctl no tienen espacio de nombres por contenedor, las escrituras desde un contenedor privilegiado (--privileged) no afectan a un entorno aislado de prueba, sino a la propia máquina real que ejecuta la verificación. Por lo tanto, esta verificación solo confirma que la escritura del sysctl se degrada correctamente (sin fallos) frente a un sistema de archivos de solo lectura; confirmar que el valor realmente se aplica requiere una máquina real o una VM, que es el destino para el que está pensado el producto. Esta comprobación se realizó una sola vez, el 2026-09-25, en una VM con Ubuntu 24.04 (véase la fila PENT-S8).
Reprodúzcalo usted mismo
Todos los resultados anteriores se pueden reproducir al 100 % en su propia máquina, utilizando únicamente el paquete de distribución oficial, mediante el conjunto de pruebas Docker publicado en el repositorio de soporte oficial (roamswitch-support); no se requiere acceso al código fuente ni confiar en nosotros.
# Obtener y reproducir la suite de verificación (instala automáticamente roamswitch-server desde el repositorio APT oficial)
git clone https://github.com/lafine1211/roamswitch-support.git
cd roamswitch-support/test/docker/server
./run.sh
Crea tres contenedores desechables (Target, Attacker y un señuelo C2) en la red bridge predeterminada de Docker, verifica automáticamente los 18 puntos y genera un informe. El resultado completo de la ejecución con la v1.3.2 está publicado en RESULTS-PENTEST-SERVER-2026-09-07.ja.md (incluyendo, por transparencia, los problemas del propio arnés de pruebas detectados y corregidos durante su construcción). El registro de la nueva ejecución del 2026-09-25 está en RESULTS-PENTEST-2026-09-25.ja.md.
§7Operaciones, implementación y ciclo de vida
7.1 Asistente de configuración y herramientas de CUI sin cabeza
Gestión remota completa a través de CLI:
roamswitch server status: genera el informe de diagnóstico de 34 puntos (admite 10 idiomas y sigue la configuración del usuario que lo ejecuta o la configuración regional del sistema operativo).roamswitch server setup: Asistente de terminal interactivo para puertos y notificaciones.roamswitch server config show / set <key> <val>: Automatización programable para Ansible/Terraform.roamswitch server test-notify [all|telegram|line|webhook]: sondeo de notificación de un extremo a otro.roamswitch server config set language <ja|en|zh-Hans|zh-Hant|ko|de|fr|es|it|pt|system>: el idioma del informe de diagnóstico periódico del demonio (cada hora) y de las notificaciones push (Telegram / LINE / webhook, incluido el mensaje de prueba), un idioma por host.systemsigue la configuración regional del sistema operativo, y una configuración regional no identificable pasa aja. El idioma de visualización deroamswitch server statusno depende de este ajuste, sino de la configuración del usuario que lo ejecuta o de la configuración regional del sistema operativo.roamswitch server bans [--json]/sudo roamswitch server unban <IP|/64 IPv6|--all>: estado de la guardia contra fuerza bruta SSH, y lista y levantamiento de sus bloqueos. La herramienta MCPget_ssh_guard_incidentses de solo lectura y no puede levantar un bloqueo.
TUI de Server (roamswitch server tui)
Una interfaz de terminal que permite manejar toda la edición Server por SSH, en 80x24 y solo con el teclado (roamswitch server tui o roamswitch-server-tui). Tiene 11 pantallas: panel, protección, incidentes, notificaciones y reenvío, Sensor, configuración, servicio, herramientas, auditoría de registros, información y ayuda, y Fleet. El editor de configuración se genera a partir de las definiciones de claves (el esquema) de server.conf y guard.yaml: valida tipo, rango y valores enumerados, indica si hace falta reiniciar, muestra un diff antes de guardar y guarda con copia de seguridad con marca de tiempo y escritura atómica (los valores secretos, como los tokens, se enmascaran y se introducen sin eco). Las operaciones de gran impacto, como restaurar un aislamiento, hacer ACK, actualizar la línea base de FIM o detener el servicio, muestran una pantalla de confirmación que indica el efecto, y las de mayor impacto exigen escribir yes (confirmación escrita). Sin root es de solo lectura e indica el comando sudo roamswitch ... necesario. No hay recarga de configuración; hace falta reiniciar el servicio para que los cambios surtan efecto. La interfaz admite 10 idiomas.
7.2 Embalaje
Se distribuye mediante paquetes .deb para Debian / Ubuntu y paquetes .rpm para RHEL / Fedora y para openSUSE.
§8Conclusión
RoamSwitch Server Edition ofrece a las infraestructuras Linux de nube y empresa, en un único paquete integrado, «Zero Telemetry (la única comunicación externa por defecto es una recepción de datos firmados, sin envío, que se puede desactivar)», «una sólida protección perimetral mediante denegación entrante por defecto», «la detección inmediata de manipulaciones en rutas críticas» y «el bloqueo preventivo de la escalada de privilegios del núcleo».
Una suite de seguridad para servidores Linux que combina detección por capas con contención autónoma (Air-Gap) en las principales fases que persiguen los atacantes: reconocimiento inicial, intrusión, escalada de privilegios y persistencia.