§1
RoamSwitch para Linux es una aplicación residente de seguridad de red y diagnóstico de sistemas que lleva el modelo de defensa de red de confianza cero de macOS RoamSwitch a Linux (entornos systemd + nftables).
Esta edición está dirigida a máquinas cliente (estaciones de trabajo, portátiles, computadoras de placa única) y asume un comportamiento autónomo que sigue a la máquina mientras se mueve entre redes. Una edición de servidor reforzado/infraestructura sin cabeza (RoamSwitch Server Edition: denegación predeterminada siempre activa, integridad FIM de ruta crítica, webhooks remotos y aplicación de políticas estáticas) se entrega como una compilación de servidor dedicado.
Identifica la red Wi-Fi/cableada conectada por su dirección MAC de puerta de enlace y cambia de forma autónoma el perfil de firewall activo nftables. Además, proporciona tres capas de defensa frente a ataques de intermediario (MITM) en redes que no son de confianza: un túnel VPN + interruptor de apagado (WireGuard o Tailscale, la defensa principal), un bloqueo preventivo ARP/NDP y la detección de suplantación de identidad ARP. A ello se suman la detección conductual de ransomware (fanotify + entropía de Shannon + canarios), una protección de enlace pasiva que avisa o bloquea conexiones de phishing (NFQUEUE + SNI), Port Anomaly Guard, mitigación BadUSB, contramedidas contra la escalada de privilegios locales (LPE) del kernel (como mitigación de Frag Gap CVE-2026-53362 se puede activar, de forma opcional, la denegación de los espacios de nombres de usuario sin privilegios en redes que no son de confianza; está desactivada por defecto), integración del sensor de amenazas en tiempo de ejecución eBPF (Falco/Tetragon) con congelación de procesos atacantes en milisegundos (SIGSTOP) y aislamiento Air-Gap automático, una evaluación del estado de seguridad de 27 elementos y un servidor de Model Context Protocol (MCP) integrado de serie.
Este documento técnico describe la arquitectura, el modelo de amenazas y el diseño de cada mecanismo de defensa y, en particular, cómo se garantiza a nivel de código el principio de «procesamiento totalmente local; por defecto, lo único que sale de la máquina es una solicitud de datos de detección firmados, que no contiene nada sobre usted». Las funciones opcionales que un operador debe activar de forma explícita (como el reenvío de eventos) son excepciones a ese comportamiento por defecto; todas figuran en la tabla siguiente y en el §9.4. Evita el lenguaje de marketing; las afirmaciones se pueden contrastar con el comportamiento real del paquete distribuido (binario) y con la autoverificación del apéndice. RoamSwitch para Linux es software gratuito propietario y su código fuente no se publica (§11).
| Principio | Detalle |
|---|---|
| Telemetría cero | Por defecto, el producto no envía nada sobre usted ni sobre su equipo. No hay telemetría, análisis, informes de fallos, estadísticas de uso, configuración remota ni servidor de activación de licencias. Solo pueden comunicarse las funciones opcionales que un operador activa de forma explícita, indicadas en la fila siguiente («Excepciones al comportamiento por defecto») y en 9.4. |
| Excepciones al comportamiento por defecto (todas opcionales) | Las funciones siguientes solo se comunican con el exterior si un operador las activa de forma explícita. Sus destinos son únicamente los que el operador configura; no hay destinos integrados. (1) Reenvío de eventos (forward_enabled, desactivado por defecto): envía los eventos de detección a un syslog / archivo JSON Lines / webhook firmado que indique el operador. (2) Fleet (nivel Business, solo código del lado del dispositivo): un dispositivo no inscrito no envía nada ni siquiera resuelve el nombre de host de Fleet; el servicio de gestión no está desplegado ni publicado. Todo lo demás, incluidas las comprobaciones de actualización iniciadas por el usuario, figura en el 9.4. |
| Recibir solo actualizaciones | La única solicitud saliente que el producto realiza por defecto es un GET diario de un manifiesto firmado en una URL fija (lafine.net/updates/v1). Si ClamAV está instalado, cada ejecución también lanza la herramienta de actualización propia de ClamAV, freshclam; ese tráfico pertenece a ClamAV y se enumera aparte en el §9.4. Del manifiesto se obtienen la lista de sitios fraudulentos del guardián de enlaces, la última versión de la aplicación, los mapas CVE del kernel, de los paquetes del sistema operativo (Debian / Ubuntu / Arch / familia RHEL / SUSE) y de los paquetes de lenguajes (npm / PyPI / crates.io / RubyGems / Packagist / Go / Maven), el mapa CVE del análisis activo de vulnerabilidades y la lista de nombres de paquetes npm populares usada para detectar typosquatting. Cada día solo se obtienen el manifiesto y su firma (dos GET); la lista del guardián de enlaces, el mapa CVE del análisis activo y la lista de npm solo cuando el manifiesto muestra una versión más reciente, y los mapas CVE del kernel y de paquetes solo cuando han pasado unos 30 días desde la última actualización. El mapa CVE de Ubuntu solo lo obtienen los equipos con una versión compatible de Ubuntu; un equipo que aún no lo tiene lo obtiene en su siguiente ejecución diaria, sin esperar los 30 días. No incluye cadena de consulta ni cookies, la única cabecera que nombra el software es un User-Agent: RoamSwitch-Updater fijo, y no incluye información sobre usted. updates.enabled = false la desactiva por completo; el producto funciona entonces con los datos incluidos y, mientras no active una función opcional, no tiene contacto externo. |
| Procesamiento totalmente local | Cada evaluación, detección y auditoría se ejecuta hasta su finalización en la máquina local. El análisis de seguridad de URL y el escaneo secreto evalúan sus entradas de forma heurística sin transmitirlas a ninguna parte. |
| Mínimo privilegio | Las operaciones privilegiadas se limitan a un demonio root; la interfaz, la CLI y el servidor MCP las solicitan mediante IPC por socket de dominio Unix. Por defecto, el demonio no abre ningún socket TCP/UDP (las funciones opcionales que activa un operador son la excepción, §9.4); el guardián de enlaces solo usa el canal de veredicto NFQUEUE sobre AF_NETLINK (núcleo ↔ espacio de usuario, sin emitir paquetes). Las llamadas IPC que cambian el estado solo se aceptan de root o de un UID con sesión de inicio local (desde la 1.10.4; las llamadas de solo lectura no cambian). |
| Fallo cerrado | Air-Gap (aislamiento de emergencia) sobrevive al reinicio del demonio y se reactiva en cuestión de segundos si sus reglas se eliminan externamente. |
| Licencias sin conexión | Las licencias Pro/Business se verifican únicamente mediante la verificación de firma Ed25519. No se produce ninguna llamada a un servidor de activación. |
§2
RoamSwitch para Linux se dirige principalmente a computadoras portátiles o estaciones de trabajo móviles que se desplazan entre redes que no son de confianza.
Atacantes en alcance
- Un atacante en la misma LAN: escaneos de puertos en Wi-Fi compartido, Suplantación de ARP/NDP (incluidos anuncios de enrutadores no autorizados) para un intermediario, DHCP no autorizado, rastreo pasivo después de una inundación de MAC, acceso no autorizado a un servidor de desarrollo o API LLM local vinculado accidentalmente a
0.0.0.0. - Dispositivos USB maliciosos (BadUSB): hacerse pasar por un teclado e inyectar pulsaciones de teclas antes de que la pantalla se bloquee; Almacenamiento masivo USB no registrado para filtración de datos o para transportar malware.
- Ransomware/malware sin archivos: ingresa a través de un archivo descargado y cifra de forma masiva los documentos del usuario.
- C2 basado en DNS y phishing: malware que utiliza resolución de nombres para llegar a un servidor C2 o un sitio de phishing.
Fuera de alcance
- Un atacante que ya tiene raíz; rootkits a nivel de kernel o firmware.
- Ataques que eliminan físicamente el disco (el dominio de cifrado de disco completo; RoamSwitch diagnostica si el cifrado está presente pero no cifra).
- Control de uso compartido inalámbrico de proximidad estilo AirDrop (sin mecanismo nativo del sistema operativo en Linux; no compatible).
§3
RoamSwitch para Linux está implementado en Rust, y el paquete incluye 8 ejecutables (roamswitch-daemon, roamswitch-app, roamswitch (CLI), roamswitch-mcp, roamswitch-updater, roamswitch-honeytokens, roamswitch-incident-capture y roamswitch-xdp-gate, experimental y desactivado por defecto). La lógica común reside en la biblioteca roamswitch-core, y roamswitchkit es el SDK de cliente. Toda la comunicación con el demonio pasa por /run/roamswitch/roamswitch.sock (un socket de dominio Unix). No hay ningún socket TCP/UDP de escucha en el demonio, la aplicación o el servidor MCP. Lo único que la aplicación enlaza con bind es un único socket Unix para evitar una segunda instancia.
# Espacio de usuario (sin privilegios) roamswitch-app Bandeja GTK3 + AppIndicator roamswitch (CLI) Frontend ligero de shell roamswitch-mcp stdio JSON-RPC (iniciado por clientes de IA) │ │ AF_UNIX /run/roamswitch/roamswitch.sock (solo IPC local) ▼ roamswitch-daemon root / systemd Type=notify + WatchdogSec=30 · Control de perfiles nftables (tabla inet roamswitch) · Bucle centinela (detección de red y aplicación de perfiles) · Túnel VPN + interruptor de corte (WireGuard: wg-quick / roamswitch_vpn, Tailscale: roamswitch_ts) · Bloqueo preventivo ARP/NDP (fijación de tabla neigh) / detección · Guardia de anomalías de puerto (roamswitch_dev_guard) · Guardia fanotify (ransomware / malware) · Guardia de enlace (NFQUEUE — inet roamswitch_linkguard) · Motor de señuelos / Vault de cuarentena · Contramedidas del kernel contra LPE (la denegación de userns sin privilegios es opcional, desactivada por defecto) · Integración en tiempo de ejecución eBPF (/var/run/roamswitch/events.sock) · Congelación SIGSTOP de procesos atacantes en milisegundos y vínculo con Air-Gap · Monitorización USB (evdev EVIOCGRAB — BadUSB) / cola de aprobación · Verificación de licencia Ed25519 roamswitch-updater Un ayudante de temporizador systemd independiente. El único crate con una pila HTTP/TLS (GET diario de un manifiesto firmado) roamswitch-honeytokens Proceso auxiliar iniciado por el demonio (coloca y vigila credenciales señuelo) roamswitch-incident-capture Proceso auxiliar iniciado por el demonio (recoge un paquete de evidencias al contener) roamswitch-core Biblioteca de lógica pura (compartida por todos los crates) roamswitchkit SDK de cliente MCP
| Componente | privilegio | Rol |
|---|---|---|
| demonio-roamswitch | raíz (systemd, Type=notify + WatchdogSec=30) | Todas las operaciones privilegiadas. Como escritor único, controla las unidades nftables, DNS y systemd. |
| aplicación-roamswitch | iniciar sesión usuario | GUI GTK3 + bandeja AppIndicator. Edición y visualización de configuración únicamente; cada acción privilegiada pasa por IPC. |
| conmutador de itinerancia (CLI) | iniciar sesión usuario | Frontal de carcasa delgada que utiliza el mismo zócalo IPC. |
| roamswitch-mcp | proceso generado por el cliente AI | estándar JSON-RPC. Proporciona información de diagnóstico de solo lectura. |
Toda la comunicación con el demonio pasa por /run/roamswitch/roamswitch.sock (el directorio es 0755 y el socket es 0666). El control de acceso no se hace con los permisos del archivo, sino comprobando quién se conecta: el demonio obtiene el UID del interlocutor con SO_PEERCRED, rechaza las conexiones de cuentas del sistema (UID del 1 al 999 y nobody) y solo acepta las llamadas que cambian el estado de root o de un UID que tenga una sesión de inicio de sesión local (§1). La configuración del usuario se almacena como texto sin formato JSON en ~/.config/roamswitch/config.json. El demonio recorre y lee /home/*/.config/roamswitch/config.json con privilegios de root (se ejecuta con HOME=/root), y solo acepta un archivo que pertenezca a la propia cuenta, que no sea un enlace simbólico y en el que otros usuarios no puedan escribir. La configuración nunca sale de la máquina; no hay registro de cuenta ni sincronización en la nube.
§4
El bucle centinela (ciclo de 3 segundos) lee la MAC de la puerta de enlace predeterminada de /proc/net/arp y ip neigh y la compara con las MAC registradas en config.json. Se utiliza MAC en lugar de SSID porque un SSID se falsifica con facilidad. La notificación de escritorio que se muestra cuando el perfil cambia indica la razón del cambio (“una red no registrada” / “su configuración para la red registrada X” / “sin conexión de red”).
| Perfil | Situación | comportamiento de nftables |
|---|---|---|
| abierto (confiable) | Una red doméstica o corporativa registrada | policy accept. Tráfico local permitido. |
| equilibrado | No registrado pero configurado como relativamente seguro | Entrada denegada por defecto. Solo se permiten las conexiones establecidas, lo e ICMP / ICMPv6; se supervisa la exposición de puertos públicos. |
| bloqueo (protección ausente) | Wi-Fi público/red no registrada | Todos los paquetes entrantes se descartan en modo sigiloso. Los servicios compartidos se detienen automáticamente y se restauran al volver a una red de confianza (activado por defecto, se puede desactivar en cualquier momento). |
Todas las reglas están aisladas en una tabla nftables dedicada llamada inet roamswitch y nunca chocan con otra configuración de firewall (ufw/firewalld). El demonio como escritor único aplica el vencimiento de la anulación manual (1 hora / 2 horas / 4 horas / hasta medianoche / hasta que se borre / hasta la próxima desconexión). La GUI nunca escribe una caducidad; vuelve a leer el config.json que el demonio reescribió en el siguiente tic y lo refleja en la interfaz de usuario (evitando un conflicto).
Air-Gap (aislamiento de red de emergencia)
Un modo de aislamiento total que establece policy drop (prioridad -100) en los ganchos de entrada y salida de la tabla inet roamswitch, permitiendo solo lo.
- Falla cerrada: al reiniciar el demonio, la detección del archivo de marcador (
/run/roamswitch/airgap.active) reafirma el aislamiento. - Autocuración: si un proceso externo elimina las reglas con
nft delete table, el bucle centinela se vuelve a aplicar en segundos. - Los activadores de baja confianza se levantan con un temporizador corto: un Air-Gap manual simple, sin contexto de alerta (un botón de la interfaz gráfica o
roamswitch airgap), es un activador de baja confianza y se restaura por completo con un temporizador corto (airgap_low_confidence_secs, 600 segundos por defecto), para que una pulsación por error o un falso positivo no deje la máquina sin red durante mucho tiempo. Una anomalía ARP fuera de lockdown, una anomalía de puertos y un evento eBPF de nivel Error o inferior también se clasifican como de baja confianza en la máquina de estados común, pero el cliente nunca activa el Air-Gap automáticamente por ellos: solo generan una notificación o un registro, o bloquean el puerto afectado. - Los activadores de alta confianza nunca restauran en silencio: la manipulación de un canario, un evento eBPF Emergency/Alert (un evento Critical solo cuando no se pudo contener el proceso concreto), la suplantación ARP que implica a la puerta de enlace durante lockdown y un Air-Gap pulsado en respuesta a una alerta ARP (y, solo si se activa, una regla de gravedad alta del registrador de ejecuciones; §5) no se levantan automáticamente, por mucho tiempo que pase. Tras el temporizador corto, cada ciclo vuelve a comprobar si la causa persiste (PID congelado aún vivo, canario reescrito de nuevo, anomalía ARP que continúa, etc.) y solo levanta el aislamiento, con notificación, cuando se verifica que la causa ha desaparecido. Una comprobación no concluyente, incluida la imposibilidad de determinarlo, cuenta como «no resuelto». Una ráfaga de entropía de ransomware activa además el Air-Gap, junto con la congelación del proceso afectado con
SIGSTOPy una notificación, y se trata igualmente como desencadenante de alta confianza. - Modo DEGRADED — si un aislamiento de alta confianza alcanza el límite (
airgap_hard_cap_secs, 3600 segundos por defecto;0= sin límite) sin que el usuario lo levante y sin causa verificada como desaparecida, pasa a DEGRADED. En el cliente, DEGRADED es tan estricto como FULL: se descarta todo salvo loopback, incluidas las conexiones establecidas; la diferencia es que nunca se reabre por temporizador. Se levanta por el usuario local (botón de la GUI oroamswitch emergency-restore) o mediante una reverificación que confirme que la causa desapareció. Air-Gap también bloquea por defecto la radio Wi-Fi con rfkill (airgapAutoRfkill); DEGRADED la mantiene bloqueada salvo queairgap_degraded_lift_rfkillsea true. - Transiciones registradas y notificadas — el paso de FULL a DEGRADED, cada tipo de levantamiento automático y un levantamiento por parte del usuario se anotan en la cronología de incidentes y generan una notificación de escritorio. El estado del aislamiento se guarda en
/run/roamswitch/airgap_isolation.jsony sobrevive a un reinicio del demonio (un reinicio pone a cero el recuento de comprobaciones ARP consecutivas, lo que inclina hacia mantener el aislamiento: fail-closed).
table inet roamswitch {
chain input {
type filter hook input priority -100; policy <accept|drop>;
iif "lo" accept
ct state established,related accept
# open es policy accept sin reglas; balanced / lockdown son policy drop (ICMP e ICMPv6 se permiten solo en balanced)
}
chain output {
type filter hook output priority -100; policy <accept|drop>;
# Durante el Air-Gap (aislamiento) se vuelve a crear la misma tabla: policy drop en input y output, solo se permite lo (tampoco pasan las conexiones establecidas)
}
}
Protección de enlace pasiva (detección de conexión de phishing)
El demonio observa hacia dónde van las conexiones salientes y señala (o, en el caso del conjunto limitado de "claramente phishing", bloquea) las conexiones a hosts peligrosos. Reemplaza el antiguo verificador de pegar una URL, que requería que el usuario actuara.
- Interception: una tabla nftables dedicada
inet roamswitch_linkguardconecta la rutaoutputy pone en cola las preguntas de DNS y las conexiones web nuevas/tempranas a un trabajador del espacio de usuario a través de NFQUEUE. NFQUEUE es un canal de veredicto del espacio de usuario del kernel↔ sobreAF_NETLINK; el demonio no vincula ningún socket y no origina ningún paquete. Las reglas de cola llevanbypass, por lo que si el trabajador no está corriendo, el tráfico pasa sin obstáculos (Air-Gap sigue siendo la parada forzosa independiente). - Hostname sources: el nombre de la pregunta DNS, TLS ClientHello SNI (por lo que un navegador que usa DoH todavía está cubierto por el nombre de host) y el encabezado HTTP
Hostde texto sin formato. ClientHello cifrado (ECH), una vez ampliamente implementado, es un punto ciego conocido. - Veredicto (
roamswitch-core, lógica pura): un host se compara con la fuente de phishing/amenazas local y un conjunto de heurísticas fuera de línea: IDN/homógrafo confuso de una marca protegida, token de marca en un dominio no oficial, host con IP sin formato, TLD de alto riesgo.
Modo (linkGuard.mode) | Hit de feed u homógrafo de marca | Otras señales sospechosas |
|---|---|---|
off | no inspeccionado | no inspeccionado |
warn | conexión retenida hasta 8 s + un mensaje de aprobación de permitir/bloquear | ni notificación ni bloqueo |
block (predeterminado) | conexión caída + un diálogo de permiso (una vez por host) | ni notificación ni bloqueo |
Solo se bloquea de forma estricta (hard-block) un acceso a un feed o un host homógrafo de marca; todo lo que la heurística señala por sí sola queda en un veredicto de nivel advertencia, sin bloqueo ni notificación. El modo warn mantiene la conexión a un host peligroso retenida hasta 8 segundos mientras pregunta al usuario; si la retención expira sin respuesta, es fail-closed (los paquetes se descartan y el veredicto no se almacena en caché). No responder a «¿está bien este sitio?» no debe significar «sí», y el siguiente intento vuelve a preguntar. El modo block muestra una sola notificación y un diálogo de permiso por host (un navegador que abre muchas conexiones al mismo host bloqueado ya no inunda de notificaciones). En un cuadro de diálogo de aprobación peligroso, el botón predeterminado es «Seguir bloqueando» y «Permitir» tiene el estilo de opción discreta y no predeterminada. Si en el diálogo de aprobación se elige «Permitir», el host bloqueado se añade de forma permanente a linkGuard.allowlist y se permite hasta que se elimine en los ajustes.
El DNS se inspecciona, nunca se falsifica: una pregunta de DNS incluida en la lista de bloqueo produce solo una notificación; la aplicación se realiza en la conexión TCP posterior, por lo que RoamSwitch nunca inyecta una respuesta DNS falsa. El feed es una lista compilada de dominios de phishing/fraude, incluida en el paquete y actualizada por el actualizador diario (§11). Sin ningún feed, la heurística fuera de línea sigue funcionando. La firma Ed25519 y el SHA-256 del feed los verifica el actualizador diario antes de instalarlo, y los datos que no coinciden no se adoptan. El guardián de enlaces carga el feed instalado (o, si no lo hay, el incluido en el paquete); si no hay ninguno, funciona en modo «solo heurísticas», nunca en «permitir todo».
Túnel VPN + interruptor de emergencia (la principal defensa MITM)
Protección anti-MITM que no depende de la integridad de la capa 2. En una red que no es de confianza, un túnel cifrado siempre activo hacia un punto final confiable hace que la suplantación de ARP/NDP local, el DHCP no autorizado y el rastreo pasivo sean irrelevantes: el atacante solo ve texto cifrado. El bloqueo ARP/NDP y la detección a continuación son secundarios a esta característica.
Dos backends para elegir: “WireGuard (archivo de configuración)” o “Tailscale (nodo de salida)”. RoamSwitch no implementa criptografía propia y no es un proveedor de VPN. Ambos backends son dependencias opcionales; si está ausente, la GUI muestra el comando de instalación.
(A) Servidor WireGuard
- No se proporciona ningún servidor VPN: utiliza una configuración WireGuard que proporciona el usuario: un
.confde un proveedor como Mullvad, IVPN o Proton VPN, o el propio servidor WireGuard del usuario (un VPS o un servidor doméstico). No se envía ninguna configuración predeterminada. - Config: el demonio valida el
.confy lo almacena en/etc/wireguard/roamswitch.conf(0600, interfazroamswitch). No puede vivir en otro lugar: el perfil AppArmorwg-quicken Ubuntu 24.04+ / Debian 13 niega quewg-quicklea un.conffuera de/etc/wireguard/, incluso como root. Una configuración de túnel dividido (AllowedIPsno0.0.0.0/0/::/0) está marcada como "no todo el tráfico está protegido". - Kill-switch: una tabla nftables dedicada
inet roamswitch_vpn(policy drop, ambos ganchos) elimina todo excepto el loopback, la interfaz del túnel (roamswitch, coincidente conoifname), UDP a la IP del punto final anclado, DHCP e ICMP. Se instala antes de que se abra el túnel (el punto final se resuelve en IP fijas a través degetent ahostsprimero), por lo que no hay ventana de fuga. Siwg-quick upfalla, el interruptor de emergencia permanece activo.
(B) Backend de escala trasera
- Para usuarios que ya ejecutan Tailscale. RoamSwitch not ejecuta
tailscale up/ inicia sesión / instalatailscaled; leetailscale status --jsony ejecutatailscale set --exit-node=<node>/--exit-node=(claro). - Se requiere un nodo de salida: la protección anti-MITM solo se mantiene con un nodo de salida que enruta todo el tráfico. Sin ninguno configurado, la protección no está activada y la interfaz de usuario advierte "no protegido". Si el nodo elegido se desconecta, la protección se desarma automáticamente y se notifica al usuario (enrutar todo a través de un nodo inactivo bloquearía la conexión).
- Kill-switch (
inet roamswitch_ts,policy drop): el transporte de Tailscale no se puede fijar a una única IP de punto final (relés DERP + pares móviles), por lo que es más flexible que el de WireGuard. Permite: loopback, la interfaz de túnel (tailscale0), los rangos CGNAT100.64.0.0/10yfd7a:115c:a1e0::/48, STUN (udp/3478), DERP (tcp/443),udp/41641, DHCP, ICMP, MagicDNS (100.100.100.100:53), conexiones establecidas. Todo lo demás (UDP arbitrario, DNS a un solucionador local, SMB, mDNS, HTTP de texto sin formato, TCP arbitrario) se descarta. - Advertencias sobre el interruptor automático: (1) lista de permitidos más amplia que el backend de WireGuard (
udp/3478,tcp/443, todo el CGNAT/10). (2) Un atacante en ruta todavía ve metadatos (que Tailscale está en uso, región DERP, tiempo) y puede eliminar/retrasar, pero no puede leer la carga útil tunelizada. (3) Cuando se bloquea el UDP directo, la confidencialidad recae en el DERP TLS de certificado anclado de Tailscale. (4) Con MagicDNS desactivado + un solucionador local, el DNS se interrumpe mientras está armado (advertencia de UI). (5) Se debe iniciar sesión en los portales cautivos antes de habilitarlos.
Común. Se arma solo cuando vpn_on_untrusted_enabled está activado y el perfil es lockdown (protección fuera de casa). En open y balanced no se levanta el túnel y, con Tailscale, se borra el nodo de salida y se elimina el interruptor de emergencia (nunca se ejecuta tailscale down). Desactivado por defecto, opt-in. El destino del tráfico saliente, tanto en WireGuard como en Tailscale, es el punto final VPN/tailnet propio del usuario y no está relacionado con la infraestructura de lafine (§9).
Bloqueo preventivo ARP/NDP
En el momento en que se une a una red que no es de confianza, las direcciones MAC de la infraestructura de enlace que un atacante MITM necesitaría suplantar se fijan en la tabla de vecinos del kernel como PERMANENT entradas (estáticas). Una vez fijado, el kernel ignora las respuestas ARP falsificadas y los anuncios de vecinos.
- Qué está anclado: (1) la puerta de enlace predeterminada IPv4, (2) el enrutador predeterminado IPv6 (a menudo una dirección
fe80::local de enlace), (3) cualquier solucionador de DNS en el enlace (se accede a una resolución pública a través de la puerta de enlace y se omite). Los vecinos IPv6 están fijados conip -6 neigh replace ... nud permanent. - Las redes confiables nunca están fijadas, por lo que un reinicio del enrutador doméstico no puede bloquear al usuario. Volver a fijar en cada conmutador de red (TOFU); se detecta una conmutación por error legítima en la siguiente reconexión. Las entradas fijadas se registran en
/var/lib/roamswitch/arp_lock.json. - Activado de forma predeterminada (política de Community Edition; mismo desglose que la versión macOS Pro). Alternar:
gateway_arp_lock_enabled.
Detección de falsificación de ARP (notificar primero)
Monitorea /proc/net/arp para detectar que una misma MAC corresponde a varias IP (MAC duplicada) y un cambio repentino sospechoso en la MAC de la puerta de enlace. En lockdown: ante una detección que implique a la puerta de enlace, cortar todo el tráfico (Air-Gap) + notificar. En redes balanced/de confianza: solo notificar, sin cortar de golpe; el usuario puede activar el corte de emergencia desde RoamSwitch. Esto (a) evita que un atacante utilice un solo paquete ARP falsificado como arma para dejar al usuario sin conexión por sí mismo (DoS), y (b) evita que el reinicio del enrutador o la itinerancia del punto de acceso lo active por error. Ajuste: arp_spoof_guard_enabled.
Verificación de la causa antes de levantar un Air-Gap (reverificación ARP)
Un Air-Gap causado por suplantación ARP (detectada bajo lockdown o pulsado en respuesta a una alerta ARP) solo se levanta como «causa desaparecida» cuando se cumple todo lo siguiente. El demonio lee la tabla de vecinos del núcleo (ip -j neigh show, o /proc/net/arp como alternativa) y la ruta por defecto; la MAC de la puerta de enlace por defecto debe coincidir con la MAC de confianza registrada al activarse (estado REACHABLE, STALE, DELAY, PROBE o PERMANENT, sin que otro host IPv4 del mismo dispositivo reclame esa MAC), durante arp_recheck_consecutive comprobaciones consecutivas (3 por defecto) que abarquen al menos arp_recheck_min_secs segundos (60 por defecto). Solo cuando la entrada de vecino falta o está en estado fallido y el enlace está activo (carrier presente, operstate up, Wi-Fi no bloqueado por rfkill), envía un único ARP who-has a la puerta de enlace (con al menos 20 segundos de separación, por AF_PACKET) para comprobarlo (arp_recheck_active_probe, true por defecto). Si alguna respuesta trae una MAC distinta de la de confianza, es una discrepancia. Discrepancia, entrada ausente, MAC duplicada, fallo de lectura o ausencia de MAC de confianza registrada cuentan siempre como «no resuelto» (fail-closed) y reinician el recuento. Limitación conocida: un atacante que clone la MAC de la puerta de enlace real no puede detectarse con ninguna comparación de MAC, ni en caché ni activa.
La detección de suplantación ARP activa el Air-Gap automático solo cuando la puerta de enlace está implicada: cuando cambia la MAC de la puerta de enlace, o cuando la IP de la puerta de enlace comparte una MAC con otra IP. Si cambia la MAC de otro host, o si una máquina responde por varias IP con una misma MAC (un PC que ejecuta máquinas virtuales, un NAS con dirección alias), solo se avisa y no se corta la red. Con la misma regla, en la comprobación de liberación solo un duplicado que implique a la puerta de enlace cuenta como «la causa persiste». Al introducir esta corrección en la 1.10.7 (2026-09-26), en una LAN real, un ARP falso de la puerta de enlace enviado desde otra máquina disparó el Air-Gap en aproximadamente 1 segundo, y se liberó automáticamente unos 11 minutos después de que el ataque cesara (10 minutos por defecto más 60 segundos de confirmación), incluso mientras dos IP sin relación seguían respondiendo con una misma MAC. Antes de esta regla, ese duplicado inofensivo bastaba para provocar un corte total que nunca se liberaba.
Guardia de anomalías del puerto
Contiene automáticamente un puerto de escucha recién expuesto a la LAN externa (una puerta trasera, C2 o un servidor de desarrollo/API LLM local vinculado accidentalmente a 0.0.0.0). El veredicto es relativo a la línea base, no "número de puerto peligroso". Al habilitarlos, los ejecutables actualmente expuestos se aprenden como "conocidos"; a partir de entonces, solo se marca un ejecutable expuesto recién aparecido (identidad = la ruta del ejecutable, ajustes normalizados a snap:<name>). Los demonios del sistema están fuera de alcance. La contención se realiza a través de la tabla inet roamswitch_dev_guard: coloque TCP en ese puerto desde cualquier lugar menos lo; la máquina sigue usándolo. Tras la contención automática, se presenta una notificación inmediata en el escritorio y un cuadro de diálogo de confirmación ("Permitir este puerto" / "Mantener bloqueado"), lo que permite la inclusión permanente en la lista blanca con un solo clic. También se puede permitir cualquier puerto individualmente y recuperar la línea de base desde la pantalla de diagnóstico de puertos. Activado de forma predeterminada. Alternar: port_anomaly_guard_enabled. Portado desde macOS.
Detección de tunelización DNS / exfiltración
Funciona permanentemente como parte de la ruta DNS existente del Link Guard pasivo (no existe un interruptor de configuración independiente). Una consulta DNS se cuenta como «de forma sospechosa» cuando coincide con alguno de los patrones característicos de herramientas de tunelización DNS como iodine o dnscat2 (una etiqueta de más de 40 bytes Y una entropía de Shannon superior a 3,8, o un tipo de registro TXT/NULL), y solo se notifica cuando se producen 10 consultas de ese tipo en los últimos 60 segundos (con un tiempo de espera de 300 segundos para volver a notificar sobre el mismo dominio apex, que se amplía a 600 y 1200 segundos, con un máximo de 1800, mientras las notificaciones continúan; si pasa un tiempo sin notificaciones igual o superior al doble del tiempo de espera, vuelve a 300 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. Una consulta TXT dirigida a clamav.net no se cuenta, porque freshclam la usa para comprobar la actualidad de las firmas. La regla NFQUEUE subyacente lleva el indicador bypass, de modo que, aunque el proceso de detección esté detenido o no responda, el tráfico DNS sigue pasando sin verse afectado: la resolución de nombres nunca se bloquea por un fallo de esta función (el mismo diseño fail-open que el propio Link Guard pasivo).
Limitación conocida: se comprobó de forma empírica que los nombres de host largos separados por guiones (por ejemplo, prod-us-east-1-web-server-node-0042) y un único contador global que no distinguía dominios de destino en las ráfagas TXT/NULL provocaban aquí falsos positivos. Ambos se corrigieron el 23/09/2026. La corrección consiste en una lista blanca de entropía configurable y en separar el contador de 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 LinkGuardCfg.dns_tunnel_entropy_allowlist (los ajustes del guardián de enlaces de roamswitch-app tienen un campo específico, separado por comas, para ello). 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 se aplica siempre, con independencia de la lista blanca, lo que impide que un atacante disfrace un túnel bajo un dominio permitido (la única excepción es TXT bajo clamav.net, que freshclam usa para comprobar la actualidad, mediante una pequeña lista de exclusión aparte). El contador de ráfagas ya está separado por dominio apex, lo que elimina los falsos positivos causados por un único contador global que no distinguía el destino (la lógica subyacente de ventana deslizante de 60 segundos no ha cambiado; el tiempo de espera de renotificación empieza en 300 segundos y se amplía hasta un máximo de 1800 segundos mientras las notificaciones continúan). El tráfico malicioso real de herramientas como iodine o dnscat2 solo 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 de riesgos de Docker y protección contra la evasión del firewall
Los puertos publicados por Docker (-p) tienen un problema conocido de evasión del firewall estándar del host, por lo que se insertan permanentemente reglas de inspección en la cadena DOCKER-USER, bloqueando cualquier tráfico externo no permitido explícitamente (no requiere configuración, activo de forma predeterminada). Además, se vigila docker events en tiempo real para detectar al instante cuándo se inicia un contenedor --privileged o se monta /var/run/docker.sock, un riesgo de escape del contenedor, y notificarle de inmediato. Como esto indica una configuración de riesgo y no un compromiso confirmado, solo se notifica, sin bloqueo automático.
Cobertura — esta inspección de la cadena DOCKER-USER cubre el backend iptables (IPv4), IPv6 y el backend nftables nativo de Docker, y se comprueba y se repara cada 60 segundos (también en la edición de escritorio, desde la 1.10.4; las versiones anteriores solo cubrían el backend iptables (IPv4)).
Escáner de fugas de secretos y claves API
Detecta claves API (OpenAI, Anthropic, AWS, GitHub, Slack, Stripe y más) y claves privadas SSH mediante entropía de Shannon y coincidencia de patrones. roamswitch audit-secrets <text|archivo|directorio> analiza texto y archivos, además de un directorio completo de forma recursiva (omitiendo .git, node_modules, etc.; se omiten los archivos de más de 2 MB o que parezcan binarios). La misma detección también está disponible mediante la herramienta MCP audit_secrets (con text o path) y mediante el cliente Rust roamswitch-linux-kit del SDK público (audit_secrets() para texto, audit_secrets_path() para un archivo o directorio). El contenido nunca se transmite a ningún lugar.
Detección y bloqueo de fuerza bruta SSH (solo mientras sshd esté en marcha)
El demonio de la Client Edition ejecuta el mismo motor que la Server Edition (ssh_guard_enabled, true por defecto). Hay tres diferencias. Primero, trabaja solo mientras sshd esté en marcha: cada minuto comprueba si algo escucha en los puertos SSH y se detiene tras tres ausencias seguidas. En un equipo sin sshd no sigue el registro ni toca ninguna tabla de nftables. Segundo, los ajustes son las mismas 10 claves (ssh_guard_*) con los mismos valores por defecto, guardadas en config.json y aplicadas en aproximadamente un minuto. Tercero, las notificaciones son notificaciones de escritorio y una entrada en el registro de incidentes del cliente; nunca inicia el Air-Gap. Un origen cuyos fallos alcanzan el umbral (por defecto 6 en 600 segundos) pierde sus conexiones nuevas, y las conexiones SSH que ya tenía abiertas se cortan al mismo tiempo (una sesión autenticada y un origen exento nunca se cortan). Cuando fail2ban (con una jail sshd activada) o CrowdSec ya protege sshd, solo detecta y notifica. Los bloqueos se listan y se levantan con sudo roamswitch bans y sudo roamswitch unban <IP>; no hay pantalla GTK para ello, solo config.json y los comandos. Con los niveles de protección por defecto del cliente el tráfico entrante está cerrado, así que las conexiones pueden no llegar a SSH.
Asistente de configuración de primera ejecución y UI de confirmación
El paquete se envía /etc/xdg/autostart/roamswitch.desktop, por lo que el asistente de configuración se inicia en el primer inicio de sesión después de la instalación (después solo reside en la bandeja). Al habilitar/deshabilitar un guardia de seguridad se muestra un cuadro de diálogo de confirmación para los mismos guardias que la compilación de macOS (anomalía de puerto, autocontención ARP, almacenamiento USB, teclado BadUSB, Bluetooth). Las decisiones en el momento del evento (un teclado USB desconocido, una unidad USB no registrada, un puerto desconocido bloqueado automáticamente, un Air-Gap de emergencia) se presentan como diálogos modales.
§5
Detección de ráfagas de entropía de Shannon · Escaneo YARA
Los eventos de escritura se rastrean por PID. Si un solo proceso crea 20 o más archivos distintos con entropía ≥ 7,92 en 5 segundos, se trata como cifrado masivo de ransomware y el proceso se congela con SIGSTOP. A continuación se genera una notificación de escritorio, se registra en la línea de tiempo del incidente y se recoge un paquete de evidencias. Esta detección también activa el Air-Gap (el corte de toda la red), como desencadenante de alta confianza igual que la manipulación de un canario o los eventos eBPF de gravedad alta.
Medido en una máquina real (Ubuntu 24.04, kernel 7.0) con un programa de reproducción que reescribe 40 archivos (medición del 2026-09-26, contra la versión del demonio incluida en la 1.10.7, que añade la detección del cifrado parcial). Un cifrado completo se detectó tras el archivo 20 y el proceso se congeló en el 31. También se detecta cuando el proceso se renombra a python3 o git: la lista de permitidos ahora comprueba que el ejecutable y el nombre del script concuerden con el nombre, no solo el nombre (antes, los 40 archivos pasaban desapercibidos). cp -r, rsync -a, tar -x y los scripts de Python se identifican en el momento en que se ve la escritura, por lo que no dan una detección falsa aunque terminen rápido (antes ya habían salido al evaluar y se notificaban como «proceso desconocido»). El cifrado parcial (16 bytes de cada 32, la forma de escribir del ransomware de tipo LockBit) no alcanza el promedio de 7,92 del archivo completo, por lo que antes no se detectaba; desde la 1.10.7 sí se detecta. La regla busca bloques de 16 bytes que alternan entre aspecto aleatorio y aspecto de texto; detectó el 94 % de los archivos simulados y dio 2 falsos positivos (0,01 %) entre 19 997 archivos normales. Un programa de reproducción que escribe a toda velocidad, sin pausa entre archivos, termina los 40 archivos antes de que se evalúe el umbral de 20, así que el proceso ya había terminado cuando se detuvo; con 0,2 segundos por archivo se detuvo en el 20. El cifrado solo del principio, el cifrado con tramos omitidos y los datos cifrados escritos en Base64 no se cuentan como detecciones: solo se registran en el log y no se congela nada, porque podrían dar falsos positivos con archivos normales. Un ransomware escrito con un programa auténtico de la lista de permitidos (como python3) tampoco se distingue por el nombre.
- Separación del escaneo YARA y la detección por entropía: la inspección de malware YARA se aplica a los archivos escritos sin exclusión por extensión, incluidos
~/.cache/,node_modules/y los archivos comprimidos (salvo elscan_exclusionsconfigurado por el usuario, el Vault de cuarentena,/dev/shmy los archivos en memoria o ya eliminados, ubicaciones como/proc,/sysy/dev, y lo que escriben procesos del sistema como el propio demonio, ClamAV y la base del escritorio; solo se leen los primeros 256 KB). Por otro lado, el análisis de ráfaga de entropía del ransomware excluye los formatos que ya nacen comprimidos (.zip,.tgz,.tar.gz,.gz,.png,.jpg,.mp4,.wasm, etc.), los archivos cuyo inicio sigue siendo una cabecera legítima de compresión o de medios (desde la 1.10.25) y el trabajo de caché de los administradores de paquetes verificados (npm,npx,cargo,rustc,dpkg,apt,tar, etc.), para evitar congelaciones SIGSTOP por falsos positivos al desplegar herramientas legítimas. - Las sobrescrituras repetidas del mismo archivo (por ejemplo, las tres pasadas de
shred) cuentan como un evento. - Protección de procesos críticos: los procesos se comparan con una lista de procesos protegidos codificada de forma fija (
PROTECTED_PROCESS_NAMES, consystemd/dockerd/NetworkManager/sshd/gnome-shell, etc.); un proceso cuyo nombre coincide y cuyo ejecutable está en una ubicación del sistema (como/usru/opt) nunca se congela (evita un bloqueo de toda la máquina). Un programa que solo imita el nombre y está en/tmpo/homeno está protegido.
Reglas de la capa de aplicación web (web shells y código inyectado)
Reglas específicas detectan web shells PHP, JavaScript, JSP y ASP, código ofuscado inyectado, PHP oculto en imágenes y el secuestro de la ejecución mediante .htaccess. Son independientes de la detección integrada de ELF y shell y se escriben en un lenguaje pequeño de literales, secuencias de literales con separación acotada y dos estadísticas (una línea extremadamente larga, un bloque Base64 enorme). Sin expresiones regulares, la evaluación no puede dispararse ni bloquearse; incluso un archivo de reglas hostil solo puede causar falsos positivos. Ninguna firma procede de una muestra real ni está ligada a una familia de malware con nombre: las 18 reglas describen solo técnicas documentadas públicamente (ejecutar la salida de un decodificador, valores de la petición pasados directamente a ejecución, PHP ejecutable en directorios de subida, PHP dentro de una imagen, asignaciones de manejadores en .htaccess, scripts inyectados construidos con unescape y bibliotecas de minería en el navegador).
Las reglas tienen tres capas: integradas, instaladas por el actualizador diario (11.3) tras verificar la firma, y sus propios archivos en /etc/roamswitch/rules.d/*.json (solo se leen archivos propiedad de root que ni el grupo ni otros puedan escribir). Las gravedades son critical, high, medium y low; critical y high pasan por aprobación o cuarentena, y medium e inferiores solo se informan. La cuarentena automática se limita a critical por defecto (quarantine en /etc/roamswitch/web_rules.conf); en la edición Cliente, una detección al escribir o ejecutar la decide el usuario en el cuadro de aprobación. Todo lo que está bajo tests/, fixtures/ y .git/ se omite por defecto, porque el código de pruebas cita las mismas construcciones.
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.
Canarias (archivos señuelo)
Los archivos señuelo se colocan en las carpetas Documentos, Escritorio, Descargas e Imágenes del usuario y en un directorio dedicado, y se comparan con una línea base SHA-256 en un ciclo de 3 segundos. Cualquier cambio de nombre, eliminación, truncamiento o cambio de contenido activa Air-Gap inmediatamente. Debido a que el demonio se ejecuta como root, enumera /home/* e implementa valores canarios en los directorios de cada usuario real.
Paquetes de captura forense de evidencias
Solo cuando se activa la detección de ráfagas de entropía por ransomware, o cuando un evento de gravedad Critical del sensor de amenazas en tiempo de ejecución eBPF culmina en un aislamiento específico del proceso (SIGSTOP + corte de red), 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, cuando se le pasa un PID, una instantánea /proc/<pid> del proceso afectado (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; se registra y continúa el resto de la captura. En los eventos eBPF se pasa el PID del proceso afectado. En la detección de ráfagas de entropía se pasa el PID cuando se pudo identificar el proceso que escribía; si ese proceso ya había terminado cuando se evaluó la ráfaga, la herramienta se inicia sin PID y solo captura los paquetes. La captura de evidencias no se ejecuta cuando el aislamiento específico del proceso no es posible o falla y se recurre al aislamiento Air-Gap de todo el host, ni en eventos de nivel Emergency/Alert que activan directamente un aislamiento de todo el host. 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 se ejecuta explícitamente roamswitch-incident-capture prune (retención predeterminada: 30 días).
Las pruebas capturadas no se verifican en el momento de la captura. Para comprobar posteriormente si han sido alteradas, ejecute 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 apoya en la anterior, y solo se puede añadir cuando el sistema de archivos lo permite) 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.
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: ~/.aws/credentials, ~/.ssh/id_rsa y ~/.docker/config.json, ~/.pypirc, ~/.env.backup (bajo el directorio personal del usuario invocador), cada uno con un marcador ROAMSWITCH_HONEYTOKEN. 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 el despliegue en cascada de señuelos adicionales relacionados. 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. 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 ~/.docker/config.json. La lectura por cualquier otro programa se sigue detectando como antes.
Un vigilante en segundo plano usa fanotify de clase notificación (FAN_OPEN/FAN_ACCESS marcados archivo por archivo en cada señuelo) para supervisar la lectura y apertura de cada señuelo; en los entornos donde fanotify no está disponible (por ejemplo, si no se ejecuta como root) recurre a inotify. Como ningún proceso legítimo tiene motivo alguno para leer estos archivos, cualquier acceso se trata como una señal fuerte de reconocimiento posterior a un compromiso por parte de un intruso (MITRE ATT&CK T1552); RoamSwitch intenta identificar el proceso que accede y registra el evento en la línea de tiempo del incidente con gravedad Critical (no se muestra ninguna ventana emergente de escritorio; si el reenvío de eventos está activado, se reenvía). Los accesos de los propios ejecutables de RoamSwitch y, en los señuelos de SSH, los de la familia de clientes OpenSSH (ssh, scp, sftp, ssh-add, ssh-agent) no se registran, para evitar falsas alarmas; un acceso cuyo ejecutable no se puede identificar sí se registra. Es solo de registro: no congela ni bloquea por sí mismo al proceso que accede.
Limitación conocida: el contenido del señuelo ~/.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. Además, el proceso que accede se identifica normalmente a partir del PID que lleva el evento de fanotify; solo cuando fanotify no está disponible y se recurre a inotify se llama a fuser, por lo que puede no obtenerse 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 trata como desconocido, lo cual no afecta a la detección del acceso en sí (el evento se dispara igualmente y se registra).
monitoreo fanotify · Bóveda de Cuarentena · ClamAV
- El demonio monitorea
/home,/tmp,/var/tmpy/dev/shmcon marcas a nivel de sistema de archivos (desde la 1.10.18; la marca a nivel de montaje es solo la alternativa cuando se rechaza la del sistema de archivos) y vigila la finalización de las escrituras en todo/también a nivel de sistema de archivos, medianteFAN_CLASS_CONTENT(modo de permisos; si no es posible,FAN_CLASS_NOTIF). Desde la 1.10.4, los eventos de permiso cubren solo la ejecución (FAN_OPEN_EXEC_PERM) yopense trata como una notificación (fanotify_open_permrestaura el comportamiento anterior). Los ejecutables ya analizados como limpios se almacenan en caché 60 segundos y los procesos hijos del propio demonio se excluyen de la supervisión. - Notificar primero, sin cuarentena previa a la confirmación (guardia fanotify). La guardia fanotify no pone en cuarentena sin confirmación ni aplica
FAN_DENYante un simple acierto de YARA/ClamAV. Una detección de firma real añade una solicitud modal (Cuarentena/Permitir/Más tarde) en/run/roamswitch/approvals.json; nada ingresa a la bóveda hasta que el usuario lo confirme. Solo unexecvereal recibeFAN_DENY, y solo cuandopre_exec_blocking_enabledestá activado. El tiempo de aprobación se agota en modo fail-open. La vigilancia de descargas (por defecto~/Downloadsy~/Desktop; se añaden más conwatched_folders) es una ruta distinta: un archivo que YARA o ClamAV detecta (salvo EICAR) se mueve de inmediato a la bóveda de cuarentena, sin pedir confirmación, y se notifica. - Las firmas de prueba como EICAR solo se registran en el historial: la cadena de prueba estándar de la industria no genera ninguna notificación, solo una entrada en el historial de notificaciones, y nunca se pone en cuarentena ni se bloquea, tanto en la protección fanotify como en la de descarga.
- Los escáneres YARA y ClamAV respetan una lista de rutas
scan_exclusionsconfigurada por el usuario (rutas absolutas, aplicadas recursivamente; incluye elclamav_exclusionsheredado). "Permitir" en el mensaje y "Restaurar" en la pestaña Cuarentena se agregan a él, por lo que el mismo archivo no se vuelve a marcar. - El malware real detectado (en la guardia fanotify, solo lo que el usuario ha confirmado) se reubica en
/var/lib/roamswitch/quarantine/, accesible solo para root (antes era~/.local/share/roamswitch/quarantine/de cada usuario; la ubicación antigua se migra automáticamente al arrancar). El directorio de la bóveda es0700y pertenece a root; cada muestra es0400y pertenece a root (solo lectura, no ejecutable). Los usuarios solo pueden operar sobre la bóveda a través del IPC del demonio, y únicamente pueden ver, restaurar y eliminar los elementos que ellos mismos pusieron en cuarentena. Una restauración la escribe, como archivo0644, un proceso hijo que ha reducido sus privilegios a los del usuario que la solicita (un archivo existente nunca se sobrescribe). - El bucle de eventos de permiso no lee el archivo de configuración: la configuración la lee un subproceso en segundo plano dedicado en una instantánea
Mutex<GuardConfig>; en la ruta crítica, el acceso a la configuración se limita a bloquear y copiar (la lectura del inicio del archivo que se va a ejecutar, para evaluarlo, es aparte). Esta es una invariante de diseño: bloquearFAN_OPEN_EXEC_PERMcongelaría la máquina. - Cuando ClamAV está presente (
clamav_enabled, activado por defecto), los archivos nuevos que aparecen en las carpetas vigiladas por la vigilancia de descargas (por defecto~/Downloadsy~/Desktop) se escanean en la base de datos local, a continuación del YARA integrado. Los ejecutables de tipo Windows, como.exe, generan una notificación; los formatos de distribución de Linux (.sh,.deb,.elf,…) solo se registran en el historial; y los modelos de IA peligrosos (.pkl,.pt) generan una notificación con su origen. ClamAV es una dependencia opcional; las otras defensas funcionan sin él. Cuando se instala, el actualizador diario (§11) invocafreshclampara que la base de datos de firmas se mantenga actualizada.
Registrador de ejecuciones de procesos (solo local)
Un registrador estrictamente local que anota los eventos exec y los cambios de uid mediante el conector de procesos de netlink (proc connector). Registra la ruta del ejecutable, los argumentos de la línea de comandos (limitados a 64 argumentos, 512 caracteres cada uno y 4096 en total, con los caracteres de control escapados), la ascendencia de procesos padre, el uid, el directorio de trabajo, el ID del contenedor, el SHA-256 del ejecutable, etc. Está activado por defecto, pero desactivado por defecto en máquinas con menos de 1 GiB de RAM (un ajuste explícito de exec_recorder_enabled prevalece). Su propio uso de CPU se limita al 3 % de un núcleo (exec_recorder_cpu_budget_pct) y se ejecuta con nice 10. Si no se puede abrir el conector de procesos, recurre a un escaneo poco frecuente de /proc (que pierde los procesos de corta duración).
- Almacenamiento — los registros se guardan en
/var/lib/roamswitch/exec_logcomo JSON Lines divididos en segmentos de 8 MiB (0600). La retención por defecto es de 200 MB / 14 días (exec_recorder_retention_mb/exec_recorder_retention_days). Una cadena de hashes hace que la manipulación sea detectable pero no evitable (un atacante que obtenga root puede reescribir toda la cadena). La búsqueda se hace conroamswitch events search|tree|export|verify; las herramientas MCP de solo lectura para clientes de IA sonsearch_exec_eventsyget_process_tree. - Reglas de correlación solo de notificación (EXR-001 a EXR-010) — diez reglas: una shell lanzada por un servicio web, ejecución desde
/tmp, un archivo en memoria (memfd) o un archivo eliminado, ejecución justo después de una descarga, un one-liner base64/eval, obtención de uid 0 sin ruta setuid, ejecución de un binario depositado justo después de un cambio de cron/systemd, ejecución tras un cambio en/etc/ld.so.preload, la eliminación de una instantánea local (btrfs, LVM) por algo que no sea una herramienta de gestión conocida comosnapperotimeshift(EXR-008, 1.10.11), que un mismo origen de ejecución busque 8 o más herramientas de ataque conwhicho similares en 30 segundos (EXR-009, 1.11.0) y que un intérprete de scripts como python o node inicie 3 o más herramientas de ataque en 2 minutos (EXR-010, 1.11.0; se excluye lo que una persona escribe bajo sshd, login o un terminal). Cada regla lleva un ID de técnica MITRE ATT&CK. Las reglas solo notifican y nunca aíslan por sí solas; solo si un operador activa explícitamenteexec_rules_isolation_optin(false por defecto), una regla de gravedad alta se trata como activador de Air-Gap de alta confianza (§4). - Nada sale de la máquina — el registro en bruto nunca se reenvía. Solo si el reenvío de eventos (§9.4) está activado se envían las notificaciones de reglas (gravedad, ID de regla, nombre del proceso, resumen) y los hashes de sellado de los segmentos, y únicamente al destino que el operador configuró.
Vigilancia del acceso a archivos de credenciales (activada por defecto, solo notificación)
Activada por defecto (credential_access_watch_enabled es true; con false se detiene, y también se puede cambiar con el interruptor de la pantalla de protección contra malware). Notifica cuando un programa inesperado abre una clave privada SSH (~/.ssh/id_*), ~/.aws/credentials, los archivos de credenciales bajo ~/.config/gcloud/ (4 tipos, como credentials.db) o el almacén de credenciales/cookies de un navegador habitual (solo notificación). Se limita a fijar el evento de notificación FAN_OPEN de fanotify en unos pocos archivos individuales, nunca bloquea, congela ni deniega nada, y el kernel no espera respuesta. El nombre del ejecutable del programa que abrió el archivo se compara con listas de permitidos por tipo (ssh / git / gpg-agent, navegadores, aws / terraform / kubectl, etc.), y las notificaciones repetidas para el mismo par (programa, archivo) se suprimen durante 10 minutos. Las herramientas de copia de seguridad (restic, borg, etc.) y los indexadores (tracker, baloo, etc.) están permitidos desde el principio. Añada cualquier otro programa para el que no quiera notificaciones a credential_access_watch_allow en config.json. Para las credenciales de AWS, también se permiten las herramientas que se ejecutan como python, python3 o ansible, como los scripts de boto3 o Ansible (para gcloud se permiten python y python3). Sin embargo, un one-liner como python -c sí notifica. El precio de esta permisividad es que un programa de robo de credenciales que se ejecute como archivo de script de python tampoco notifica. Otros intérpretes como node o ruby, y las herramientas generales de búsqueda y sincronización como grep o rclone, no se incluyen en la lista de permitidos a propósito, porque un programa que roba credenciales tiene exactamente el mismo aspecto; por eso pueden generar notificaciones. Una lectura muy breve, como cat o head, en la que el programa termina antes de que RoamSwitch pueda identificar su ejecutable, puede no notificarse (una lectura de un programa que sigue en ejecución durante un rato sí se notifica). RoamSwitch no usa el fanotify de clase de permisos para evitar este límite, porque podría detener el sistema. Los cambios surten efecto en unos 30 segundos, sin reiniciar.
La vía de intrusión en las notificaciones de manipulación de archivos
Las notificaciones de la vigilancia de archivos críticos (FIM) del cliente y de la vigilancia de archivos de bloqueo de dependencias también indican el proceso que hizo el cambio (cuenta, ejecutable, comando) y el inicio de sesión SSH al que pertenece (origen, hora, método de autenticación). Es fim_intrusion_context_enabled en config.json, true por defecto; no se envía nada al exterior. La notificación de un acceso a un archivo de credenciales añade una línea solo cuando el proceso pertenece a un inicio de sesión SSH. El funcionamiento, los valores en los que confía y las tres respuestas que distingue (encontrado / no se encontró ningún inicio de sesión SSH / no se pudo determinar la vía de acceso) son los mismos que en la Server Edition. Lo que no se pudo comprobar nunca se presenta como «no es un inicio de sesión». El registro se guarda en /var/lib/roamswitch/fim_intrusions.json y en el campo recentIntrusionContext de la herramienta MCP verify_fim.
Prioridad y endurecimiento de la escalada de privilegios locales (LPE) del kernel
Cuando un atacante o malware consigue un punto de apoyo para un usuario sin privilegios, la defensa en profundidad impide explotar las vulnerabilidades del kernel de Linux para obtener privilegios de root.
- Tratamiento de los espacios de nombres de usuario sin privilegios (mitigación de Frag Gap CVE-2026-53362): muchas vulnerabilidades LPE del kernel de Linux moderno dependen de usuarios sin privilegios que llaman a
unshare(CLONE_NEWUSER)para crear espacios de nombres de usuario y, dentro de ellos, acceden a estructuras no inicializadas o a subsistemas de red heredados. El diagnóstico comprueba y muestra el estado deuser.max_user_namespacesykernel.unprivileged_userns_clone, pero, como desactivarlos puede romper navegadores, Flatpak, Electron y contenedores rootless, la edición Client no recomienda desactivarlos y no lo incluye en el refuerzo con un solo clic (si está enuser.max_user_namespaces = 0, se indica que se restaure el valor estándar). Desactivarlos es una medida provisional que solo cierra las rutas de ataque que necesitan un espacio de nombres de usuario; no detiene otras vías. El comportamiento de ponerkernel.unprivileged_userns_clone = 0solo mientras se está en una red que no es de confianza se aplica únicamente si se activa explícitamenteadaptive_security.untrusted_network_actions.auto_enable_userns_restrictionenguard.yaml(desactivado por defecto; no hace nada en kernels que no tienen este ajuste). La edición Server lo aplica de forma permanente por defecto (harden_userns_enabled). La solución real es actualizar a un kernel corregido y reiniciar. - Protección de memoria Yama LSM: aplicar
kernel.yama.ptrace_scope = 1(o2) evita que procesos no autorizados se conecten a través deptraceo lean la memoria a través de/proc/$pid/mem, salvaguardando las sesiones del navegador y las claves privadas GPG/SSH. - Supresión de volcado de núcleo: la configuración
fs.suid_dumpable = 0evita que procesos sensibles que fallan escriban volcados de memoria de texto sin formato en el disco. /tmpy/dev/shmnoexecMount Defense: para evitar ataques de caída y ejecución de carga útil, RoamSwitch vuelve a montar/tmpy/dev/shmconnoexec,nosuid,nodevcuando se conecta a redes que no son de confianza.
Sensor de amenazas en tiempo de ejecución de eBPF y congelación de procesos autónomos (eBPF Guard de escritorio)
El demonio cliente RoamSwitch incluye integración nativa con modernos sensores eBPF integrados en el kernel (Falco/Tetragon/BTF).
# Detección de amenazas en tiempo de ejecución eBPF y flujo de interceptación autónoma [Proceso malicioso (LPE/malware)] │ Ejecución de llamada al sistema ilegal (intento de explotación de Frag Gap) ▼ [Kernel de Linux (sonda eBPF)] │ Búfer circular eBPF (diseño compacto de 2 MB) ▼ [Falco (RoamSwitch Optimized)] │ Socket UNIX directo en memoria (/var/run/roamswitch/events.sock) ▼ [roamswitch-daemon (root)] ├─► ① Congelación del proceso atacante en milisegundos: kill(pid, SIGSTOP) [estado: Ts] ├─► ② Corte de red: Critical solo para el usuario de ese proceso (roamswitch_isolate), Emergency/Alert con nftables input/output drop [Air-Gap activado] └─► ③ Alerta de emergencia de escritorio: modal GTK3 y notificación libnotify
- Direct UNIX Domain Socket IPC (
/var/run/roamswitch/events.sock): las alertas de Falco u otros sensores eBPF se reciben directamente a través de un socket UNIX en memoria sin escribir en los registros del disco, lo que elimina la latencia de E/S del disco y permite transferencias de respuesta en menos de un segundo. - Respuesta autónoma en milisegundos (congelación de procesos y corte de red): cuando se recibe un evento con prioridad
Critical(como LPE del kernel, escalada de privilegios o cifrado por ransomware), el demonio envíaSIGSTOPal PID infractor para detener el proceso por completo (estado:Ts), realiza un «aislamiento específico» que bloquea, en la tablaroamswitch_isolate, solo el tráfico del usuario real de ese proceso (o de su cgroup, en el caso de un contenedor) y emite una notificación de escritorio. Si no se puede identificar el PID, si se rechaza la congelación porque el proceso es un proceso protegido, si falla el aislamiento (por ejemplo, con procesos de root) o si la prioridad esEmergencyoAlert, implementapolicy drop(Air-Gap) en la tablainet roamswitchy, si se conoce el PID, también envíaSIGSTOP. Los eventos por debajo deCriticalno se bloquean y solo se registran en el historial. - Ajuste de bajo consumo para escritorio y prevención del crecimiento excesivo de registros (RoamSwitch Optimized) Para limitar el consumo de batería, la carga de CPU y el agotamiento del disco en portátiles y estaciones de trabajo, se incluyen un perfil optimizado (
99-roamswitch-optimized.yaml) y una configuración de logrotate para los entornos que instalan Falco. Los paquetes de la edición Client y el modo de escritorio deinstall.shno instalan Falco y tampoco colocan este perfil (sí lo hacen los paquetes de la edición Server yinstall.sh --server). Si usa Falco con Client, es el usuario quien instala Falco y, si lo desea, coloca el perfil por su cuenta. Los ajustes principales son los siguientes.- Filtrado de gravedad (
priority: warning): filtra el 95 % de los eventos de aviso/información de rutina. - Kernel Early Drop (
drop_failed_exit: true): descarta las salidas de llamadas al sistema fallidas directamente en el controlador del kernel, reduciendo a la mitad los cambios de contexto y la carga de la CPU. - Búfer en anillo compacto (
cpus_for_each_buffer: 2): reduce el tamaño del búfer en anillo a 2 MB, manteniendo la RAM residente entre 30 y 50 MB y el uso de la CPU <1 %. - Límite de rotación de registros obligatorio (
/etc/logrotate.d/roamswitch-falco): aplica un límite estricto de 50 MB con rotación comprimida de séptima generación.
- Filtrado de gravedad (
- Resiliencia híbrida (endurecimiento independiente frente a ampliación eBPF): aunque Falco no esté instalado, RoamSwitch se defiende de forma autónoma mediante sysctl del kernel (restricción de ptrace de Yama, supresión de volcados de núcleo y protecciones de red, que se aplican en cada cambio de perfil), perfiles nftables, fanotify y vigilancia de canarios. Con Falco instalado se suman la interceptación de día cero y la congelación de procesos mediante eventos eBPF en tiempo real, para lograr la defensa de máxima intensidad.
§6
Se monitorean los eventos USB; Se realiza un seguimiento de los dispositivos de clase de teclado (HID) y del almacenamiento masivo USB. Desactivado de forma predeterminada (envío isEnabled = false, correspondiente a macOS USBKeyboardGuard / USBStorageGuard). Regístrese desde el asistente de primera ejecución o la pestaña USB; Al habilitarlo se muestra un cuadro de diálogo de confirmación.
Teclado USB fraudulento (inyección de pulsaciones de teclas)
- Sin desautorización de puerto físico a nivel de kernel (importante). La implementación anterior (≤1.0.6) escribía
0a/sys/bus/usb/devices/*/authorizedpara un teclado no registrado, desconectándolo físicamente. En un receptor combinado de teclado y mouse (común en una Raspberry Pi o mini PC), esto también desconecta el mouse, bloqueando una máquina sin entrada incorporada. RoamSwitch ahora se niega rotundamente a escribirauthorized=0en cualquier dispositivo que exponga una interfaz HID (bInterfaceClass 03), y el demonio vuelve a autorizar cualquier dispositivoauthorized==0HID al inicio para reparar automáticamente las máquinas bloqueadas por una versión anterior. - Las pulsaciones de teclas se eliminan en el software. Al detectar un teclado no registrado, se toma un evdev exclusivo
EVIOCGRABen el/dev/input/eventNde ese dispositivo (solo nodos con un controladorkbden/proc/bus/input/devices, que coincida con VID:PID). El nodo del mouse está intacto. Este es el equivalente en Linux de macOSCGEventTap; el dispositivo permanece encendido. - Seguridad: el agarre solo se realiza si hay otro teclado para escribir (uno integrado o uno USB aprobado y conectado), por lo que una máquina con un solo teclado nuevo nunca queda bloqueada.
- Cuadro de aprobación — junto con la captura, se encola una solicitud de aprobación en
/run/roamswitch/approvals.jsony la GUI muestra un modal (Permitir / Denegar). Permitir añade el dispositivo a la lista blanca y libera la captura. Si nadie lo aprueba en 3 minutos, el teclado ya no se libera por defecto (desde la 1.10.4; las versiones anteriores fallaban en abierto tras 3 minutos, y activarusb_keyboard_timeout_releaserestaura ese comportamiento).
Almacenamiento masivo USB no autorizado
Cuando se inserta un dispositivo de almacenamiento masivo USB no registrado (sin interfaz HID) y la protección está habilitada, se mantiene retenido con authorized=0 (seguro para un dispositivo que no es HID) y se muestra un cuadro de diálogo de aprobación, sea o no de confianza la red. Una unidad que ya está montada o que se usa como swap (un disco externo en uso, un USB en vivo) no se retiene, porque equivaldría a una extracción brusca que podría dañarla; solo se anota en el registro. Permitir vuelve a autorizar y lo añade a la lista blanca; Denegar lo mantiene retenido. La compilación de macOS se alineó con este modelo de “mantener solo lectura + mensaje de aprobación” en 1.7.4.
Identificación y derechos adquiridos
HID se determina a partir de bInterfaceClass == "03" y la línea Handlers= en /proc/bus/input/devices. Un receptor combinado (un VID:PID con una sección kbd y una de mouse) se fusiona con VID:PID. Los derechos adquiridos (grandfathering: registrar los teclados existentes en la lista de permitidos) los aplica la GUI, no el demonio. Los teclados conectados en ese momento se registran en la lista de permitidos cuando se activa la protección en el asistente de configuración inicial (y en una migración única para entornos existentes en los que la protección está activada, la configuración inicial está hecha y ningún teclado conectado figura en la lista de permitidos). Si se activa más tarde en la pestaña USB, no se registra nada. Un teclado que esté conectado al arrancar el demonio pero no esté registrado se trata como no registrado, ya que pudo haber sido sustituido antes del arranque, y se toma (grab) si hay otro teclado disponible con el que escribir.
§7
El LinuxHealthChecker de roamswitch-core evalúa 27 elementos y genera una puntuación de 0 a 100, una calificación S/A/B/C (100 puntos es S, de 85 a 99 es A, de 70 a 84 es B y por debajo es C) y asesoramiento para cada elemento. Desde el panel GTK, la CLI (roamswitch status) y MCP (get_security_report), el título, los detalles y la recomendación de los 27 elementos aparecen en el idioma del usuario (10 idiomas).
| Categoría | Artículos de ejemplo |
|---|---|
| Disco/arranque | Cifrado LUKS/dm-crypt, arranque seguro UEFI |
| control de acceso | LSM (AppArmor / SELinux), auditoría de configuración SSH / sudo |
| Endurecimiento del grano | endurecimiento de sysctl, comprobación del estado de los espacios de nombres de usuario sin privilegios (mitigación de Frag Gap; la edición Client no recomienda desactivarlos), restricción de ptrace de Yama, control de volcado de núcleo, noexec en /tmp y /dev/shm |
| Actualizaciones | configuración de actualización de seguridad automática |
| Red | Monitoreo de suplantación de ARP, estado de bloqueo ARP/NDP de la puerta de enlace, auditoría de puertos expuestos, nivel de cifrado de Wi-Fi |
| malware | ClamAV (incluida una prueba real de detección con EICAR) / fanotify (también comprueba que la guardia esté realmente ejecutándose y que no se hayan perdido eventos por desbordamiento de la cola) / monitoreo de entropía / noexec / comprobación de la instalación de los programas auxiliares (honeytokens y captura de evidencias) |
| Navegador | Configuración de navegación segura de Firefox/Chrome/Chromium |
| DNS/USB | DNS de protección contra amenazas, estado de confianza cero del bus USB |
| Contenedores y vulnerabilidades | Exposición por montaje del socket de Docker, auditoría de contenedores privilegiados (--privileged), nivel de aislamiento del runtime de contenedores (runc / gVisor / Kata), exposición a CVE conocidas del kernel en ejecución |
Cada verificación se basa en la lectura de archivos locales y la salida del comando (getenforce, mokutil, resolvectl, ss,…). No existe ninguna consulta externa de ningún tipo.
Calificación de reconocimiento de confianza. El refuerzo que RoamSwitch aplica deliberadamente no se aplica en una red (doméstica) confiable: fijar la MAC de la puerta de enlace como PERMANENTE (lo que bloquearía la LAN al reiniciar el enrutador), volver a montar /tmp y /dev/shm noexec (lo que interrumpe las compilaciones de paquetes y algunos instaladores) y el DNS de protección contra amenazas bajo el alcance predeterminado se muestra en verde. con una explicación en lugar de un "no reforzado" rojo, y señala que se aplica automáticamente en una red que no es de confianza. La verdad en tiempo de ejecución proviene de /run/roamswitch/state.json (active_level / network_trusted / fanotify_ready), escrita en cada ciclo por el demonio. El elemento fanotify verifica que el guardia esté realmente marcado y ejecutándose (/run/roamswitch/fanotify.ready), no simplemente que el kernel lo admita. El elemento de puertos expuestos lee el perfil activo del demonio (el código físico de MCP desapareció).
Endurecimiento por elemento. Los elementos fallidos que tienen una solución automatizada (kernel sysctl / Yama / core dumps, /tmp noexec, USB zero-trust, fijación de ARP de puerta de enlace, DNS de protección contra amenazas, reinicio del demonio cuando la guardia de fanotify está baja) llevan un botón "🔧 Endurecer" en el panel de GTK. El demonio se ejecuta como root, por lo que no se necesita pkexec excepto para reiniciar el demonio.
Correspondencia con NIST CSF 2.0 / CIS Controls v8
Cada uno de los 27 elementos se etiqueta con un identificador de función/categoría de NIST CSF 2.0 (por ejemplo, PR.DS-01, DE.CM-09) y, solo en los elementos con una correspondencia fiable, también con un número de control de CIS Controls v8 (por ejemplo, 3.11, 4.1). Los elementos ambiguos no se fuerzan a una correspondencia, sino que quedan sin asignar. Esta correspondencia (nistCsf/cisControl) se incluye en la salida de MCP (get_security_report) y en la salida JSON de la CLI (roamswitch status --json). No aparece en la GUI ni en la salida normal de la CLI de la edición Client (sí aparece en la salida de roamswitch status --server de la edición Server). Esta correspondencia permite a los operadores y auditores que necesitan cobertura de marcos de cumplimiento asignar mecánicamente las comprobaciones de RoamSwitch a los marcos existentes.
§8
roamswitch-mcp habla JSON-RPC a través de stdio y lo inician clientes de IA como Claude Desktop, Cursor y Antigravity. Cada herramienta es de solo lectura y casi todas se completan en local. La única herramienta que puede conectarse a un host externo es run_npm_audit_signatures, desactivada por defecto. La evaluación de get_security_report y verify_security_findings no envía nada a la LAN, y la puerta de enlace solo se lee de la caché ARP y de otra información local. Un ping solo puede salir en otros procesos que vuelven a resolver la MAC de la puerta de enlace (supervisión ARP del demonio, restauración de emergencia, emparejamiento de Sensor, get_guard_status y similares).
| Herramienta | Propósito |
|---|---|
| obtener_informe_de_seguridad | Evaluación de 27 ítems, puntuación y recomendaciones. |
| verify_security_findings | Reevalúa elementos (checkId) de get_security_report según el estado actual y devuelve un veredicto por elemento, stillPresent (sigue aplicándose), resolved (resuelto, o no aplicable por diseño) o inconclusive (no se pudo determinar), junto con un código de motivo independiente del idioma. Sirve para comprobar, después de una corrección, que de verdad ha surtido efecto. Un elemento que no se pudo medir, por ejemplo por falta de privilegios, porque el helper con privilegios no está conectado o porque Docker no es accesible, nunca se notifica como seguro y se devuelve como inconclusive. Si se omite checkIds, se comprueban todos los elementos (hasta 50 identificadores en Mac y 64 en Linux; en Linux también se acepta isServer). En Mac, host_firewall, network_stealth_mode, gateway_arp_lock, malware_scanning, dns_threat_guard y usb_zero_trust se deciden según los ajustes de la app RoamSwitch, por lo que reflejan esos ajustes y no son una nueva medición del estado real del sistema operativo. Es de solo lectura, no se conecta a hosts externos y no repara nada ni cambia ningún ajuste. En Linux tampoco envía nada a la LAN y solo lee la puerta de enlace de las cachés locales. En Mac, para averiguar la dirección MAC de la puerta de enlace, puede enviar un ping ICMP, normalmente uno (unos pocos si no se resuelve la MAC), a la puerta de enlace de la LAN. |
| audit_mcp_configs | Lee los archivos de configuración MCP de los clientes de IA (Claude Desktop, Claude Code, Cursor, Windsurf, VS Code, Cline, Gemini CLI, además de .mcp.json y archivos similares en las carpetas de proyecto registradas) y señala ajustes de riesgo. Detecta herramientas aprobadas automáticamente sin confirmación en cada llamada (auto_approve), servidores http:// en texto plano en un host que no es loopback, ya figure en el campo url o se pase como argumento de inicio, como --server http://… (remote_plain_http), comandos de inicio que son líneas únicas de shell o intérprete (shell_wrapper), lanzadores como npx / uvx / docker run que descargan un paquete o imagen sin versión fijada (unpinned_package_launcher), docker run con --privileged o con el socket de Docker o el sistema de archivos raíz montados (container_host_access), las variables de entorno que cargan código adicional al iniciar, como LD_PRELOAD o --require en NODE_OPTIONS (nombres sin distinguir mayúsculas, valores nunca devueltos; code_loading_env), entradas que apuntan a HexStrike AI, un backend de herramientas de ataque sin autenticación (hexstrike), archivos de configuración con escritura permitida para el grupo u otros (world_writable_config), el ajuste enableAllProjectMcpServers de Claude Code (auto_trust_project_servers) y un archivo de configuración que reúne un servidor que lee textos escritos por otras personas (páginas web, correos, chats, issues) y un servidor que puede ejecutar comandos o modificar archivos, la combinación que necesita la inyección de prompt indirecta (se reconoce por nombres de paquete conocidos y no ve el contenido de las herramientas del servidor; injection_exposure). No tiene argumentos, es de solo lectura y no envía nada a la red. Nunca devuelve valores de entorno o de cabeceras, argumentos ni secretos, solo nombres de servidores, el nombre base de un comando, un host de URL y, si es un nombre de registro simple, el nombre del paquete o de la imagen. Los archivos que no se pudieron analizar aparecen en unreadableFiles y no se auditaron. |
| audit_agent_setup | Lee los archivos de instrucciones y los skills dados a agentes de IA (CLAUDE.md, commands, agents y skills bajo ~/.claude, además de ~/.codex, ~/.cursor/rules, ~/.gemini, ~/.agents, ~/.openclaw, y CLAUDE.md, AGENTS.md, .cursorrules y similares en las carpetas de proyecto registradas) y el openclaw.json de OpenClaw, y señala contenido y ajustes de riesgo. En los archivos de instrucciones y skills informa de frases ocultas en caracteres de etiqueta Unicode invisibles (hidden_tag_characters), caracteres de ancho cero y de control de dirección (invisible_characters), redacción que anula instrucciones anteriores u oculta algo al usuario (override_instruction), descargar y ejecutar como curl … | sh (remote_script_exec), decodificar y ejecutar (encoded_payload_exec), referencias a almacenes de claves y credenciales (credential_access) junto a un envío o un servicio de recepción (credential_exfil_shape, exfil_endpoint), configuración de mecanismos persistentes (persistence_instruction), código compilado dentro de un skill (skill_precompiled_code) y archivos que otros pueden modificar (agent_file_writable). Lo que esté dentro de un comentario HTML se valora más alto, porque una página renderizada lo oculta. Para OpenClaw informa de autenticación desactivada (openclaw_auth_disabled), una escucha alcanzable desde la red (openclaw_wide_bind), un token de menos de 24 caracteres (openclaw_weak_token), un origen comodín (openclaw_origin_wildcard), interruptores dangerously… activados (openclaw_dangerous_flag), una lista de proxies que confía en todos (openclaw_broad_trusted_proxy, openclaw_proxy_trust_loosened), una política de mensajes directos o de grupo abierta a cualquiera (openclaw_open_dm), mDNS en full (openclaw_mdns_full), hallazgos de autodiagnóstico ocultos (openclaw_audit_suppressed) y permisos laxos (openclaw_loose_permissions). Cada regla es determinista: una coincidencia significa «una persona debería leer esta línea», no «malicioso». No toma argumentos, es de solo lectura y no envía nada a la red. Nunca devuelve el contenido de los archivos ni secretos (un token solo se informa por su longitud), solo un nombre de regla, un número de línea, un host de URL y una ruta de clave de configuración. Los archivos que no se pudieron leer figuran en unreadableFiles y no se auditaron. |
| get_exposed_ports | puertos expuestos y su estado de bloqueo del firewall |
| get_guard_status | nivel de protección actual y estado del módulo de defensa |
| auditoría_url_seguridad | Análisis heurístico de seguridad de URL (no busca el objetivo) |
| secretos_auditoría | detección de claves API/secretos en texto (no transmite el texto) |
| auditoría_seguridad_logs | agregación de registros de autenticación/diario locales |
| get_quarantine_status / get_canary_status | cuarentena y estado canario |
| get_app_help | búsqueda de la base de conocimientos incorporada |
| run_active_vuln_scan | Verificación activa de vulnerabilidades, no destructiva y limitada a 127.0.0.1. No contacta con ningún otro host, pero envía peticiones de red, por lo que está desactivada por defecto y requiere activarla expresamente en los ajustes. |
| run_package_cve_scan | Compara los paquetes instalados (Mac: Homebrew / Linux: dpkg, dnf, zypper, pacman) con un mapa de CVE local. No se realiza ninguna comunicación de red. En Linux, si no se pudo leer la lista de paquetes o se agotó el tiempo, no se informa «sin coincidencias»; complete pasa a false e incompleteReason indica el motivo (un resultado incompleto no es un resultado limpio). En Ubuntu (20.04, 22.04, 24.04, 26.04) usa los datos propios de Ubuntu, compara por nombre y versión del paquete fuente y solo comprueba las CVE con CVSS 7,0 o superior para las que Ubuntu ya ha publicado una corrección, por lo que «sin coincidencias» no significa que no haya CVE conocidas. El resultado añade ecosystem y release, cada hallazgo incluye binary (el nombre del paquete binario instalado) y las correcciones que solo ofrece Ubuntu Pro llevan proOnly. Las derivadas de Ubuntu (Linux Mint, Pop!_OS y otras) y las versiones no compatibles devuelven complete = false con el motivo en incompleteReason, sin pasar a los datos de Debian. |
| run_package_cve_scan_languages | Compara los archivos de bloqueo de dependencias de npm, PyPI, crates.io, RubyGems, Packagist, Go y Maven con el mismo mapa de CVE local. No se realiza ninguna comunicación de red. |
| get_vulnerability_scan_history | Devuelve, para cada sonda registrada por scan-vulns --confirm, el número de días transcurridos desde que se ejecutó y confirmó realmente por última vez (pass_age_days). |
| verify_fim | Vuelve a calcular el hash de 23 archivos críticos del sistema fijos, de todos los archivos directamente dentro de 12 directorios (autenticación, cron, systemd, configuración SSH, etc.), de los binarios propios de RoamSwitch y del authorized_keys de cada usuario, y los compara con la línea base para verificar manipulaciones y archivos nuevos. Si se ejecuta como root, el resultado incluye además un campo añadido, recentIntrusionContext: por cada cambio reciente, el proceso que lo hizo (cuenta, ejecutable, comando) y el inicio de sesión SSH al que pertenece (origen, hora de inicio de sesión, método de autenticación), o el motivo por el que no se pudo averiguar. |
| get_file_scan_guard_status | Devuelve la configuración del File Scan Guard (ClamAV) y el estado del baúl de cuarentena que utiliza (herramienta de la Server Edition; no informa de la configuración de la protección contra malware de la edición Client). |
| get_port_anomaly_incidents | Devuelve el estado de la línea base de la protección de puertos anómalos, los puertos bloqueados automáticamente y hasta los 50 incidentes más recientes. La respuesta indica explícitamente que los puertos actualmente bloqueados son una instantánea del estado actual, sin marca de tiempo, distinta del historial de incidentes, que sí lleva marca de tiempo. |
| get_ebpf_incidents | Devuelve el estado de aislamiento actual del guardián de ejecución eBPF y el historial de incidentes que lo motivó. En la Server Edition cada registro lleva también el tiempo desde la detección hasta la respuesta en milisegundos (detect_to_receive_ms, receive_to_action_ms, detect_to_action_ms), y el resultado incluye el estado del heartbeat de Falco (heartbeat). |
| get_ssh_guard_incidents | Devuelve el estado y el historial de la guardia contra fuerza bruta SSH: si bloquea o solo detecta (por ejemplo, porque otra herramienta ya protege sshd), los orígenes bloqueados en este momento con el tiempo restante y hasta los 200 registros más recientes (un origen fue bloqueado, fue detectado sin bloqueo, o un inicio de sesión tuvo éxito tras una serie de fallos del mismo origen). Los nombres de usuario probados no se guardan en Linux (solo su cantidad) y en Mac se devuelven saneados, hasta 5. Mac devuelve además los inicios de sesión SSH recientes (origen, usuario, método, huella de la clave). Solo lectura: ninguna herramienta MCP levanta un bloqueo. En Linux la lista de bloqueos solo la puede leer root, así que quien llama sin root recibe solo el número. Linux: la Server Edition y la Client Edition mientras sshd esté en marcha. Mac: solo Pro. |
| get_resource_guard_incidents | Devuelve el historial del guardián de agotamiento de recursos y anomalías de procesos (fugas de memoria, bucles de fallo), con un nivel de confianza (este guardián pertenece a la Server Edition; la edición Client no lo ejecuta, por lo que el resultado allí siempre está vacío). |
| get_notification_history | Devuelve el historial de notificaciones enviadas por RoamSwitch (anomalías de auditoría de registros de seguridad, detecciones de ClickFix, etc.) de los últimos 7 días, las más recientes primero. |
| get_incident_timeline | Unifica las detecciones de todos los guardianes en una única línea temporal, con la ascendencia de procesos y etiquetas MITRE ATT&CK (experimental). |
La verificación activa de vulnerabilidades también registra, para cada sonda confirmada, cuántos días han transcurrido desde que se ejecutó y confirmó realmente por última vez (pass_age), distinguiendo así «esta comprobación existe» de «esta comprobación se verificó realmente hace poco». Todavía no existe un ejecutor programado, por lo que este valor solo refleja la frecuencia con la que realmente se ha ejecutado el análisis. Ese mismo número de días transcurridos está disponible desde la CLI (roamswitch vuln-status), la GUI y MCP (get_vulnerability_scan_history), de modo que un operador o auditor puede ver cuándo se comprobó realmente por última vez, en lugar de suponer una comprobación reciente solo porque la función existe.
El servidor MCP no abre ninguna conexión de red por sí mismo. Solo get_sensor_audit_results se conecta directamente al socket Unix del demonio (/run/roamswitch/roamswitch.sock); las demás herramientas llaman directamente a la lógica de roamswitch-core o solo leen los archivos de estado que escribió el demonio (roamswitchkit funciona en sentido contrario: es el SDK con el que la CLI y otros lanzan roamswitch-mcp como proceso hijo). Las excepciones son dos herramientas desactivadas por defecto: run_active_vuln_scan envía sondas no destructivas a 127.0.0.1, es decir, al propio host, y run_npm_audit_signatures lanza el comando npm, que consulta el registro de npm (el binario MCP en sí no contiene ninguna pila HTTP / TLS). La evaluación del diagnóstico (get_security_report y verify_security_findings) no envía nada a la LAN, y la comprobación de la puerta de enlace solo consulta información local, como la caché ARP. Un ping solo puede salir en otros procesos que vuelven a resolver la MAC de la puerta de enlace (supervisión ARP del demonio, restauración de emergencia, emparejamiento de Sensor, get_guard_status y similares).
Una atribución incorrecta descubierta — y corregida — durante un simulacro con un LLM local
En septiembre de 2026, durante un simulacro (fire drill) en el que un LLM local (Qwen 3.8 27B) investigaba un incidente a través de este servidor MCP, descubrimos un caso en el que el modelo interpretó mal los datos devueltos por get_port_anomaly_incidents e incorporó, por error, un bloqueo de puerto antiguo y no relacionado a la narrativa de un incidente en curso. La causa: una instantánea sin marca de tiempo de los puertos actualmente bloqueados se devolvía en la misma respuesta que el historial real de incidentes, que sí tiene marca de tiempo, sin nada que distinguiera ambos. La corrección añade campos de nota explícitos que indican cuál de los dos campos es una línea temporal y cuál es solo una instantánea. Al volver a ejecutar el mismo escenario con el mismo modelo tras la corrección, este citó el texto de la nota y rechazó explícitamente atribuir el puerto antiguo al incidente actual. La eficacia de la corrección se verificó tanto con una prueba de regresión automatizada como con una nueva ejecución del LLM local real.
§9
Por defecto, RoamSwitch para Linux realiza un único tipo de solicitud saliente: el GET de un manifiesto firmado por el actualizador diario (§11). Todo lo demás —cada evaluación, detección y auditoría— se completa en local. Las comunicaciones que solo se producen cuando un operador o usuario las activa o desencadena de forma explícita se enumeran en el 9.4. RoamSwitch para Linux es software gratuito propietario y su código fuente no se publica, por lo que las inspecciones siguientes las realizó lafine; los resultados están en el 9.2 y los pasos del apéndice C se pueden reproducir solo con el paquete distribuido.
9.1 Método de auditoría
- Aislamiento de crates — solo se permite una pila cliente HTTP/TLS en un único crate,
roamswitch-updater, que compila el binario independiente del actualizador temporizado.scripts/audit_no_network.sh(ejecutado en CI, bloqueante) hace fallar la compilación siroamswitch-core,roamswitch-daemon,roamswitch-app,roamswitch-mcp,roamswitchkit(o los crates de CLI y Server) incorporan una pila cliente HTTP/TLS comoreqwest/ureq/rustls/native-tls/openssl-sys, o si algún archivo.rsfuera deroamswitch-updatery de los archivos citados en el 9.4 usaTcpStream/TcpListener/UdpSocket/to_socket_addrs/lookup_host. La única autorización adicional estonic/hyper, permitidos solo enroamswitch-corepara gRPC sobre socket de dominio UNIX hacia el sensor eBPF (Tetragon); el mismo script prohíbe combinarlos con TLS. - Inspección del árbol de dependencia: se buscó en el
Cargo.lockcompleto SDK de telemetría/análisis/informes de fallos (sentry,opentelemetry,…). roamswitch-updaterreview: su objetivo de solicitud único, la ausencia de cadena de consulta y de cookies, y un User-Agent fijo como única cabecera que nombra el software y su verificación Ed25519 de todo lo que descarga.- Enumeración de subprocesos: se revisó cada
Command::new(...)sitio de llamada.
9.2 Resultados
| Elemento de auditoría | Resultado |
|---|---|
| Pila HTTP/TLS | Presente en roamswitch-updater únicamente (ureq + rustls). No en core / daemon / app / mcp / kit |
| SDK de telemetría/análisis/informes de fallos | No presente en ninguna parte |
TcpStream / TcpListener / UdpSocket | Solo en roamswitch-updater y en los cuatro archivos de las funciones opcionales del 9.4 |
| API de socket en uso | AF_UNIX (IPC local); AF_NETLINK (canal de veredicto NFQUEUE, control de nftables e ip neigh, sin emitir paquetes); AF_INET solo en roamswitch-updater y en las funciones opcionales del 9.4 (TLS/HTTP se delegan en los subprocesos del sistema openssl/curl); AF_PACKET solo cuando la reverificación ARP (§4) envía un único ARP who-has a la puerta de enlace (dentro de la LAN). El túnel VPN (§4), si se activa, es wg-quick (backend WireGuard) o tailscaled (backend Tailscale) —un subproceso, no el demonio— que envía tráfico al propio punto de conexión / tailnet del usuario |
| actualizador diario | Dos solicitudes cada ~24 h: GET https://lafine.net/updates/v1/manifest y su firma manifest.sig (los archivos de datos se obtienen además solo cuando su versión es más reciente). Sin cadena de consulta, sin cookies, User-Agent: RoamSwitch-Updater fijo (la única cabecera que nombra el software), sin identificador por dispositivo. Todas las cargas útiles verificadas con Ed25519. updates.enabled = false lo desactiva por completo |
| Túnel VPN (§4) | Desactivado de forma predeterminada. Cuando está habilitado, wg-quick construye un túnel WireGuard para el punto final en el .conf del usuario, sin relación con lafine. Resolución del nombre de host del punto final a través de getent ahosts (glibc NSS), no una API de socket Rust |
| Activación de licencia | Verificación de firma Ed25519 sin conexión. No hay comunicación con un servidor de activación. |
Por defecto, RoamSwitch para Linux inicia un único tipo de solicitud saliente: la comprobación diaria, solo de recepción, de un manifiesto firmado, con descarga de datos de detección firmados cuando existe una versión más reciente; nada de ello contiene información sobre el usuario ni la máquina. Salvo que un operador o usuario lo active o desencadene de forma explícita, nada más en el producto contacta con un servidor externo (9.4). El ping que se envía a la LAN para fijar la tabla de vecinos y para la supervisión ARP (#2 en 9.3) va solo a direcciones de loopback, privadas, link-local y ULA, y nunca se envía a una dirección pública.
9.3 Lista completa de operaciones con cara al exterior
Todas las operaciones del funcionamiento por defecto que pueden hacer que un paquete salga de la máquina local, directa o indirectamente (la tabla siguiente; las operaciones que solo se producen cuando un operador o usuario las activa o desencadena figuran en el 9.4):
| # | Operación | Destino | gatillo | que se envía |
|---|---|---|---|---|
| 1 | Actualizador diario — GET /v1/manifest y su firma; los archivos de datos solo cuando su versión es más reciente (los mapas CVE del kernel, de paquetes del SO y de lenguajes se comprueban aproximadamente cada 30 días; un equipo Ubuntu sin el mapa CVE de Ubuntu lo obtiene en la siguiente ejecución diaria) | lafine.net/updates/v1 (CDN) | Temporizador systemd: la primera ejecución, 15 minutos después del arranque y, a partir de ahí, aproximadamente cada 24 h con un retraso aleatorio de hasta 3 h. No se produce cuando updates.enabled = false o roamswitch-update.timer está deshabilitado | un GET HTTPS simple: sin cadena de consulta, sin cookies, User-Agent: RoamSwitch-Updater fijo, nada sobre el usuario o la máquina. Recibe un manifiesto firmado + feed |
| 1a | freshclam (actualizador propio de ClamAV), invocado por el actualizador diario cuando se instala ClamAV | CDN del proyecto ClamAV | como parte del #1; también se inicia cuando el usuario pulsa el botón «Actualizar firmas de virus (freshclam)» de la GUI | tráfico de la propia ClamAV (un proyecto independiente); RoamSwitch solo lo inicia |
| 2 | ping -c 1 -w 1 -W 1 <IP de la puerta de enlace> (el mismo tipo de ping, una sola vez, al router IPv6 y a los servidores DNS de la LAN) | la puerta de enlace predeterminada de la LAN; en la comprobación de la fijación ARP/NDP, también el router IPv6 predeterminado y cada servidor DNS que figure en resolv.conf (con systemd-resolved, /run/systemd/resolve/resolv.conf). Solo se hace ping a los que tienen una dirección de loopback, privada, link-local o ULA. No se envía ningún paquete a un resolvedor público (por ejemplo Quad9, del DNS de protección contra amenazas) | para la puerta de enlace, solo cuando se necesita su MAC (supervisión ARP y similares) y no está en la caché ARP, ni en la tabla de vecinos, ni en la copia en memoria de los últimos 15 segundos (como máximo una vez cada 8 s por destino). Para el router IPv6 y los servidores DNS, cuando el bloqueo ARP de la puerta de enlace (gateway_arp_lock_enabled, activado por defecto) vuelve a fijar la tabla de vecinos (al cambiar a una red que no es de confianza y al cambiar el ajuste) | una sola solicitud de eco ICMP (con los datos estándar de ping). Sin resolución DNS |
| 3 | iniciando el navegador del sistema (xdg-open) | https://roamswitch.com/linux (sitio del producto), https://lafine.net/ (sitio del operador, términos de uso, contacto) y https://github.com/lafine1211/roamswitch-support (notas de la versión y soporte) | solo cuando el usuario hace clic en un botón como "Abrir notas de la versión" | el proceso RoamSwitch no abre ningún socket; entrega la URL al navegador del sistema operativo |
| 4 | resolvectl dns | — (RoamSwitch no genera ninguna comunicación por sí mismo) | DNS de protección contra amenazas en una red que no es de confianza (activado por defecto, se puede desactivar). <iface> es cada NIC física que lleva una ruta predeterminada (cableada + inalámbrica a la vez; lo / tailscale* / wg* / docker* / veth* excluidos); también se aplica en una red confiable cuando dns_scope = always_on; reconciliado cada ciclo centinela | solo cambia la configuración de resolución del sistema operativo; el propio RoamSwitch no envía ninguna consulta DNS. Después, la resolución de nombres de esa NIC se dirige al DNS de protección contra amenazas elegido (Quad9 por defecto) |
| 5 | actualizaciones del administrador de paquetes | los repositorios apt / dnf / zypper / pacman de la distro | cuando el usuario ejecuta apt upgrade, etc., o hace clic en el botón GUI "Actualizar ahora" (que ejecuta apt-get / dnf / zypper / pacman a través de pkexec) | En los sistemas apt, RoamSwitch lee la salida local de apt-cache policy e inicia el administrador de paquetes. En los sistemas dnf ejecuta dnf check-update (por ejemplo, al abrir la pestaña de actualizaciones), que puede consultar los repositorios configurados (zypper: zypper list-updates, pacman: pacman -Qu); en todos los casos, sin relación con lafine |
| 6 | Túnel VPN (§4) — Backend WireGuard: el túnel wg-quick construye / Backend Tailscale: tailscale set --exit-node enruta todo el tráfico a través del nodo de salida | el punto final VPN/tailnet propio del usuario (WireGuard: el punto final en .conf; Tailscale: el nodo de salida elegido y los relés DERP) | cuando vpn_on_untrusted_enabled y la red no son de confianza. Desactivado de forma predeterminada | tráfico cifrado WireGuard / Tailscale; el destino es el propio servidor VPN/tailnet del usuario, sin relación con lafine. Resolución de punto final WireGuard a través de getent ahosts (glibc NSS); Tailscale es impulsado por tailscaled (un proceso separado) |
Todas las demás funciones de RoamSwitch —control de nftables, fijación ARP/NDP de la tabla de vecinos (ip neigh, sin emitir paquetes), guardián de enlaces (NFQUEUE, solo AF_NETLINK), escaneo de puertos (análisis de la salida de ss), monitorización ARP, evaluación de salud, monitorización de entropía, canarios, fanotify, monitorización USB (captura evdev), cuarentena, auditoría de registros, auditoría de URL y de secretos— se completan solo con lecturas de archivos locales y el análisis de la salida de subprocesos. El túnel VPN del §4 es la «comunicación saliente configurada explícitamente por el usuario»; su destino es el propio servidor VPN del usuario y ningún dato fluye hacia la infraestructura de lafine. El destino de cada función del 9.4 se indica en el 9.4.
9.4 Operaciones que solo llegan al exterior si se activan o las desencadena el usuario
La tabla del 9.3 recoge lo que ocurre por defecto. A continuación se enumeran los puntos del código que abren un socket o delegan la comunicación en un programa externo (curl / openssl / npm) y que solo se ejecutan si un operador o usuario los activa o desencadena de forma explícita. scripts/audit_no_network.sh solo permite las API de sockets en cinco archivos —event_forward.rs, webhook_guard.rs, active_vuln_scan.rs, web_vuln_scan.rs y sensor_pairing.rs (webhook_guard.rs solo resuelve con el resolvedor del sistema el nombre del destino del webhook, y únicamente cuando se activa el ajuste, desactivado por defecto, forward_webhook_block_private_addresses, para comprobar que no sea una dirección interna; no se conecta nunca. Además, tests/fleet_roundtrip.rs, solo de pruebas, únicamente se conecta a un servidor simulado en 127.0.0.1)— y hace fallar la compilación si cualquier otro código usa TcpStream y similares. Estas funciones tampoco añaden ningún crate HTTP/TLS: TLS se deja al openssl del sistema y HTTP al curl del sistema, como subprocesos.
- Reenvío de eventos (
event_forward.rs) — desactivado por defecto (forward_enabled = false), y los únicos destinos son los que el operador configura a mano; no hay ningún destino integrado. Los destinos admitidos son syslog (RFC 5424 o CEF, por UDP / TCP / TLS), un archivo JSON Lines y un webhook firmado con HMAC-SHA256. El syslog TLS pasa por elopenssl s_clientdel sistema y el webhook por elcurldel sistema (los secretos, como la URL y la firma, 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. Solo se envían estos campos: nombre de host, ID de evento, marca de tiempo, origen, gravedad, regla, acción, PID, nombre del proceso y resumen (ni contenido de archivos ni el registro bruto de ejecuciones). Los eventos no entregados por TCP / TLS / webhook se guardan en una cola en disco0600(límite de 20 MB / 72 horas por defecto) y se reintentan. - Fleet (nivel Business, solo lado del dispositivo) (
roamswitch-core/src/fleet/) — el código del lado del dispositivo se incluye en el binario, pero un dispositivo no inscrito no envía nada ni siquiera resuelve el nombre de host de Fleet (el worker solo lee un archivo de estado local). La inscripción solo se produce cuando un operador ejecutaroamswitch fleet enroll. El servicio de gestión no está desplegado ni publicado, así que por ahora no hay nada a lo que conectarse. Las garantías de diseño presentes en el código son: (a) el dispositivo solo acepta datos de política firmados por la organización, verificados con la clave pública de la organización fijada en la inscripción; una política que no supera la verificación no se aplica. El diseño prevé que la clave de firma de la organización la conserve el cliente, no lafine. (b) No existe ningún canal de comandos remotos: en el lado del dispositivo no hay ningún punto de acceso, campo ni ruta de código con el que se pudiera ordenar aislar, levantar, recopilar o ejecutar. (c) Una política no puede debilitar el mínimo local: desactivar una protección u obstaculizar el levantamiento / la restauración de emergencia ni siquiera se puede expresar en el esquema, y prevalece el valor más estricto. Además, una política solo puede cambiar el destino del reenvío de eventos a hosts que el root local haya permitido (fleet_forwarding_allowed_hosts, vacío por defecto). (d) Un dispositivo solo envía un resumen mínimo de estado —versión de la aplicación, familia de SO, estado de aislamiento (normal / aislado), versión de política aplicada, puntuación de salud (0–100) y época de clave— más el ID de dispositivo y el ID de organización en las cabeceras de firma. Usa elcurldel sistema (TLS) y solo para un dispositivo inscrito. - Emparejamiento con Sensor (
sensor_pairing.rs) — desactivado por defecto (sensor_pairing_enabled). Solo se conecta directamente a la dirección del Sensor que introduce el operador; no hay destino integrado. Admite TLS 1.3 (mediante elopenssldel sistema, con fijación de la huella del certificado), pero un emparejamiento heredado sin fijación recurre a texto plano con una advertencia. El Sensor es un producto aparte y no está cubierto por las afirmaciones de Zero Telemetry de este documento. - Verificación activa de vulnerabilidades (
active_vuln_scan.rs/web_vuln_scan.rs) — desactivada por defecto y requiere activarla en los ajustes. En esta edición para equipos de usuario, la ruta se limita a sondeos a127.0.0.1de los puertos en escucha ya observados en esta máquina (fijado por las pruebas propias de los módulos). - El botón «Buscar actualizaciones» de la GUI — solo cuando el usuario pulsa el botón, y únicamente en las distribuciones basadas en apt, la GUI realiza un único
GETdehttps://lafine.net/apt/dists/stable/main/<arch>/Packagescon elcurldel sistema para averiguar la última versión (tiempo de espera de 4 segundos; URL pública sin secretos, sin identificador ni cadena de consulta añadidos). En los sistemas dnf, zypper y pacman no se usa curl, sino que se consulta a cada administrador de paquetes (#5 en 9.3). - Verificación de firmas de npm —
roamswitch audit-npm-signatures <DIR> --confirmsolo se ejecuta si el ajustenpm_audit_signatures_enabled(false por defecto) está activado y el usuario lo lanza con--confirm; entonces es el propionpmquien contacta con registry.npmjs.org.
§10
- Sin cuenta: sin registro de usuario, sin inicio de sesión, sin solicitud de dirección de correo electrónico.
- La configuración es solo local: se almacena en texto sin formato en
~/.config/roamswitch/config.jsony nunca sale de la máquina. No hay sincronización en la nube. - Los resultados de la evaluación son solo locales: los informes de seguridad y los resultados de las auditorías de registros solo se muestran en la pantalla y, si el usuario así lo desea, se exportan a un archivo local.
- Las claves de licencia se verifican sin conexión: los tokens de licencia Pro/Business contienen una firma Ed25519 y se verifican con una clave pública. No se realiza ninguna consulta a un servidor lafine.
- No hay informes de fallas: en caso de pánico, no se envía ningún seguimiento de la pila a ninguna parte.
- El actualizador diario no envía nada sobre usted: ni cadena de consulta, ni cookies, ni identificador por dispositivo o instalación, y un
User-Agent: RoamSwitch-Updaterfijo como única cabecera que nombra el software. Es una descarga, no un informe. Los veredictos de protección de enlaces, los hosts bloqueados y los resultados del análisis se escriben en la vista de actividad local/se registran en el diario únicamente, nunca se transmiten. - Mientras se usa el DNS de protección contra amenazas, las consultas DNS llegan a un resolvedor externo. El DNS de protección contra amenazas (§4.5; activado por defecto, solo para redes no confiables) dirige el resolvedor DNS del SO a Quad9 (
9.9.9.9; también se puede elegir1.1.1.2de Cloudflare). Durante ese tiempo, las consultas las envía el SO, no RoamSwitch, a ese resolvedor, y RoamSwitch en sí no envía nada. Para detenerlo, pongadns_enabledenfalse. - El historial de notificaciones se purga a los 7 días y tiene la longitud limitada — el historial de notificaciones enviadas se guarda en local y en cada escritura se eliminan las entradas de más de 7 días. Un título se recorta a 200 caracteres y un cuerpo a 2000 (una ventana emergente de notificación de escritorio muestra como máximo 1000 caracteres de cuerpo). Este historial no sale de la máquina salvo que se active el reenvío de eventos (desactivado por defecto).
- El registro de ejecuciones también es solo local — el registro del registrador de ejecuciones de procesos (§5) se guarda, por defecto, solo en la máquina (
/var/lib/roamswitch/exec_log,0600). Como contiene argumentos de línea de comandos, se elimina automáticamente según el período de retención (14 días por defecto) y el tamaño (200 MB por defecto).
§11
| Edición | Para | Precio | Licencia |
|---|---|---|---|
| Edición comunitaria | Uso personal y uso profesional interno en Linux (sin límite de equipos) | Gratis | Software gratuito propietario (CLUF incluido) |
| Negocio (planificado) | Gestión de flotas, distribución de políticas, apto interno firmado, SLA para organizaciones. | Pagado | Licencia comercial. Mismo binario + puerta de nivel |
Cuando se empiece a ofrecer el nivel Business, la política prevista es conceder gratis las funciones Business en sus propias máquinas Linux a quienes compren macOS RoamSwitch Pro Lifetime. Hoy no se ofrece el nivel Business y no existe ningún servidor de gestión.
Cadena de distribución y suministro
| canal | Detalle |
|---|---|
| APTO | https://lafine.net/apt (stable main). Release firmado con una clave RSA-4096 |
| RPM | https://lafine.net/rpm (Fedora / openSUSE separado). repomd.xml firmado por GPG |
| AUR | roamswitch-bin (en preparación. Como AUR no admite nuevos registros de cuenta, por ahora se instala con makepkg desde https://lafine.net/linux/dl/PKGBUILD; el tarball de lafine.net/linux/dl se verifica con sha256 y una firma separada (validpgpkeys)) |
| bola de tar | .tar.gz + .sha256 + .asc (firma separada con la clave de firma del repositorio) para x86_64 / aarch64 en https://lafine.net/linux/dl/. El repositorio de código fuente es privado, así que el lugar de descarga público es este y no GitHub Releases |
- Piso de glibc fijado: los binarios de lanzamiento se crean dentro de un contenedor Debian 12 (bookworm, glibc 2.36). Esto evita discrepancias en las versiones de símbolos en Debian 12/Ubuntu 22.04 y posteriores. Un paso de CI de "verificar el nivel mínimo de glibc" falla en la compilación si se introduce una dependencia que requiere más de 2,36.
- Gestión de claves de firma: la clave privada para la firma del repositorio apt/rpm existe solo en un secreto cifrado de GitHub Actions; sólo la clave pública se envía al repositorio.
11.3 El actualizador diario
roamswitch-updater es un binario independiente ejecutado por un temporizador de systemd (roamswitch-update.timer: 15 minutos después del arranque y luego cada 24 horas, con un retraso aleatorio de hasta 3 horas). Por defecto es el único componente que accede a una URL de distribución externa (aparte de las funciones opcionales del 9.4). En cada ejecución:
GET https://lafine.net/updates/v1/manifesty su firma separada.- Verifique la firma Ed25519 del manifiesto con una clave de firma de fuente de amenazas dedicada, separada de la clave de actualización de la aplicación Sparkle. El CI del lado del servidor vuelve a firmar el feed diariamente, de modo que la clave privada reside en un secreto de CI; limitarlo al feed significa que una filtración solo permite que un atacante proporcione una lista de bloqueo incorrecta (sumidero/advertencia: sin ruta de código, sin ruta de actualización de aplicaciones). La clave de firma de actualización de la aplicación nunca sale del llavero de macOS. Un manifiesto que no se verifica se descarta: el actualizador nunca recurre a datos sin firmar.
- Si la versión de la fuente de amenazas cambió, descargue la fuente, verifique su firma SHA-256 y Ed25519 e instálela de forma atómica para Link Guard (§4). Los demás datos solo se obtienen cuando el manifiesto muestra una versión más reciente (el mapa CVE del análisis activo y la lista de paquetes npm populares). Los mapas CVE del kernel, de los paquetes del sistema y de los ecosistemas de lenguajes solo se obtienen cuando han pasado unos 30 días desde la última actualización. El mapa CVE de Ubuntu solo se obtiene en una versión compatible de Ubuntu; un equipo que aún no lo tiene lo obtiene en la siguiente ejecución diaria, sin esperar los 30 días. Todo se instala solo tras verificar la firma.
- Cuando la versión del manifiesto es más reciente, también se obtienen las reglas de detección de la capa de aplicación web (§5). Se verifican el SHA-256 y la firma Ed25519, y se comprueba que el archivo se puede leer como reglas antes de instalarlo de forma atómica. Un archivo ilegible nunca se instala y las reglas en vigor no se sustituyen. No se añade ningún destino nuevo: va incluida en la misma obtención del manifiesto. En la edición Servidor sigue a
cve_kernel_map_updates_enabled: si está desactivado, no hay ningún contacto de red. - Si ClamAV está instalado, invoque
freshclam. - Registre la última versión de la aplicación para recibir una notificación (el actualizador never instala la aplicación; en Linux, el usuario ejecuta
apt upgrade).
El fallo de cualquier paso (fuera de línea, firma incorrecta, ausencia de freshclam) se registra en lastError de updates.json y el proceso siempre termina con exit 0, por lo que la ejecución del temporizador nunca genera una notificación de error de systemd. Todo el mecanismo se puede desactivar con "enabled": false en updates.json o con systemctl disable --now roamswitch-update.timer; el producto funciona entonces solo con los datos incluidos en el paquete. La fuente de amenazas también se envía dentro del paquete, por lo que una instalación nueva tiene una fuente funcional desde el primer momento, y apt upgrade la actualiza incluso con el actualizador deshabilitado.
§12
- SO: Ubuntu 22.04 / 24.04 y posteriores, Debian 12 y posteriores, Linux Mint / Pop!_OS / elementary / Zorin, Fedora / familia RHEL, openSUSE, familia Arch (AUR en preparación; se instala desde el PKGBUILD), Raspberry Pi OS de 64 bits (Bookworm), Ubuntu para Raspberry Pi
- Arquitectura: x86_64 / aarch64
- Requerido:
systemd,nftables,iproute2(ip neigh, el bloqueo ARP/NDP),conntrackylibnotify(notificaciones de escritorio) (todo salvosystemdse incorpora automáticamente por las dependencias.deb/.rpm) - Opcional:
clamav(escaneo de malware),nmcli(detección de cifrado Wi-Fi),iw(información inalámbrica alternativa),bluez(guardia Bluetooth),systemd-resolved(configuración del DNS de protección contra amenazas),lsof(detección alternativa de puertos en escucha),nmap(apoyo a la verificación activa de vulnerabilidades),wireguard-tools(el backend WireGuard del túnel VPN + interruptor de apagado, §4),tailscale(su backend Tailscale, §4),polkit/pkexec(ejecutar una actualización, una desinstalación o un reinicio del demonio desde la GUI) - No compatible: distribuciones que no son systemd (Alpine / Void / Devuan)
- Display: 1280×720 o mayor (la ventana y la fuente se escalan automáticamente al tamaño de la pantalla, admitiendo pantallas pequeñas de Raspberry Pi)
Apéndice A
| caja | Propósito | Red |
|---|---|---|
| serde/serde_json | serialización de configuración e IPC | ninguno |
| tokio | runtime asíncrono (features = ["full"]; por defecto solo se usan sockets Unix) | ninguno (sin TCP por defecto; el emparejamiento opcional con Sensor es la excepción) |
| rastreo / rastreo-suscriptor | registro (local / diario) | ninguno |
| este error / de todos modos | manejo de errores | ninguno |
| expresión regular | análisis de cadenas (diagnóstico, escalado CSS) | ninguno |
| URL | Análisis de URL (heurísticas audit_url_safety) | ninguno (no recupera) |
| crono | manejo de fecha/hora | ninguno |
| ed25519-dalek | verificación fuera de línea de las firmas de licencia y de los datos firmados, como el manifiesto del actualizador y la fuente de amenazas (también se usa en el emparejamiento de Sensor opcional) | ninguno |
| base64/sha2 | tokens de licencia, hashes canarios | ninguno |
| libc | chown / kill / llamadas al sistema | ninguno |
| idna | decodificación de hosts IDN/punycode para detección de homógrafos | ninguno |
| nfq (solo demonio) | Fijaciones NFQUEUE para el protector de enlace: óxido puro, AF_NETLINK | ninguno (canal de veredicto, sin salida) |
| abierto | llama a xdg-open para abrir enlaces como "Abrir notas de la versión", el EULA y las páginas de soporte | el proceso en sí no se comunica |
| GTK3 / ksni (solo aplicación) | GUI y bandeja | ninguno |
| serde_yaml | Lectura de /etc/roamswitch/guard.yaml (core) | Ninguno |
| toml | Análisis de Cargo.lock / poetry.lock (análisis de CVE de paquetes de lenguajes, core) | Ninguno |
| md-5 | Hash de las huellas JA3 de Link Guard (no se usa para autenticación ni integridad, core) | Ninguno |
| bs58 | Comprobación Base58 de claves privadas de criptoactivos (detección de secretos, core) | Ninguno |
| rand | Generación de la clave del dispositivo para el emparejamiento opcional de Sensor (core) | Ninguno |
| tonic / tonic-prost / prost / tower / hyper-util | gRPC con el sensor Tetragon. Solo socket de dominio Unix, sin TLS (core) | Ninguno (solo socket Unix) |
| flate2 | Descompresión gzip del mapa CVE de paquetes de la familia RHEL (solo updater; Rust puro) | Ninguno (solo descompresión) |
| inotify | Supervisión del acceso a honeytokens (señuelos de credenciales) (roamswitch-honeytokens) | Ninguno |
| aya | Cargador del XDP boot gate experimental (roamswitch-xdp-gate; lo referencia el daemon) | Ninguno (solo bpf() / netlink) |
| urlencoding / gdk / gdk-pixbuf / glib / pango | Decodificación de URL de la GUI y crates periféricos de GTK3 (solo app) | Ninguno |
| ureq + Rustls (roamswitch-updater solamente) | los HTTPS GET del actualizador diario (manifiesto, firma y cualquier dato que haya cambiado) | la única pila HTTP/TLS en el árbol; confinado al binario del actualizador |
No aparece ningún cliente HTTP que salga a Internet, ni pila TLS, ni SDK de telemetría en roamswitch-core, roamswitch-daemon, roamswitch-app, roamswitch-mcp o roamswitchkit. El par tonic / hyper de roamswitch-core sirve para gRPC con Tetragon sobre un socket de dominio Unix, no tiene TLS y nunca sale a la red. ureq + rustls están presentes solo en roamswitch-updater (scripts/audit_no_network.sh lo aplica en CI). No hay ningún SDK de telemetría en ninguna parte.
Apéndice B
table inet roamswitch { # Cortafuegos de perfil (open / balanced / lockdown)
chain input {
type filter hook input priority 0; policy <accept|drop>;
ct state established,related accept
iif "lo" accept
ip protocol icmp accept # open es policy accept sin reglas; balanced / lockdown son policy drop (ICMP e ICMPv6 se permiten solo en balanced)
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table inet roamswitch { # Durante el Air-Gap (aislamiento) se vuelve a crear la misma tabla: policy drop en input y output, solo se permite lo (tampoco pasan las conexiones establecidas)
chain input { type filter hook input priority -100; policy drop; iif "lo" accept }
chain output { type filter hook output priority -100; policy drop; oif "lo" accept }
}
table inet roamswitch_linkguard { # §4.5 — independiente de la tabla de perfiles
chain output {
type filter hook output priority 0; policy accept;
udp dport 443 reject with icmpx type port-unreachable # Solo cuando block_quic está activado (desactivado por defecto)
meta l4proto udp udp dport 53 queue num 92 bypass
meta l4proto tcp tcp dport 53 queue num 92 bypass
meta l4proto tcp tcp dport { 80, 443 } ct state new,established ct original packets 1-14 queue num 92 bypass # Solo se envían a inspección los 14 primeros paquetes de cada conexión
}
}
table inet roamswitch_dev_guard { # §4 Guardia de anomalías de puerto / aislamiento manual de puertos, tabla separada
chain input {
type filter hook input priority -10; policy accept;
iif != "lo" tcp dport { … } drop
}
}
table inet roamswitch_portscan { # Detección y bloqueo de escaneos de puertos (activado por defecto)
chain block { type filter hook input priority -200; policy accept; ip saddr @blocked_ips ct state new log … drop }
chain detect { type filter hook input priority -100; policy accept; iifname != "lo" ct state new tcp flags syn log … }
}
table inet roamswitch_egress_block { # Lista de bloqueo del tráfico saliente hacia IP maliciosas (activada por defecto)
chain output { type filter hook output priority -150; policy accept; ip daddr { … } log … drop }
chain forward { type filter hook forward priority -150; policy accept; ip daddr { … } log … drop }
}
table inet roamswitch_docker_guard { # Contramedida contra la elusión del firewall por los puertos publicados de Docker (solo si existe el backend nativo nftables de Docker)
chain forward { type filter hook forward priority -10; policy accept; … iifname { … } drop }
}
table inet roamswitch_isolate { # Aislamiento por proceso al detectar eBPF (por cgroup / UID)
chain process_block { type filter hook output priority -100; policy accept; … drop }
}
table inet roamswitch_vpn { # §4 Interruptor de corte del backend WireGuard
chain output {
type filter hook output priority -150; policy drop;
oifname "lo" accept
oifname "roamswitch" accept
meta l4proto { icmp, ipv6-icmp } accept
udp sport 68 udp dport 67 accept # DHCP
ip daddr { <endpoint v4> } udp dport <port> accept
ip6 daddr { <endpoint v6> } udp dport <port> accept
ct state established,related accept
}
chain input { type filter hook input priority -150; policy drop; … } # simétrico
}
table inet roamswitch_ts { # §4 Interruptor de corte del backend Tailscale (más permisivo)
chain output {
type filter hook output priority -150; policy drop;
oifname "lo" accept
oifname "tailscale0" accept
meta l4proto { icmp, ipv6-icmp } accept
udp sport 68 udp dport 67 accept # DHCP (el lado v6 también es simétrico)
ip daddr 100.64.0.0/10 accept # tailnet CGNAT
ip6 daddr fd7a:115c:a1e0::/48 accept
ip daddr 100.100.100.100 udp dport 53 accept # MagicDNS
ip daddr 100.100.100.100 tcp dport 53 accept
udp dport 3478 accept # STUN
udp dport 41641 accept # Conexión directa WireGuard
tcp dport 443 accept # Respaldo DERP
ct state established,related accept
}
chain input { type filter hook input priority -150; policy drop; … } # simétrico
}
La fijación ARP/NDP de la puerta de enlace/enrutador IPv6/resolvedor DNS (§4) se realiza en la tabla de vecinos del kernel (ip neigh replace … nud permanent), no en nftables. Las entradas fijadas se registran en /var/lib/roamswitch/arp_lock.json.
Apéndice C
Cada afirmación contenida en este documento se puede verificar en su propia máquina. Los siguientes comandos se ejecutan en un host con el paquete roamswitch instalado.
C.1 Verificación del tráfico saliente
El enlace binario demonio/aplicación/MCP no tiene biblioteca HTTP/TLS:
for b in roamswitch-daemon roamswitch-app roamswitch-mcp; do
ldd "$(command -v $b)" | grep -iE 'ssl|crypto|nghttp|curl' && echo " ^ in $b"
done
# → Sin salida. La pila HTTP/TLS solo está enlazada estáticamente en roamswitch-updater.
El demonio en ejecución no tiene conexión a Internet:
sudo lsof -p "$(pgrep -x roamswitch-daem)" -a -i
# → Sin salida = cero conexiones a Internet TCP/UDP
sudo lsof -p "$(pgrep -x roamswitch-daem)" -a -U | grep roamswitch
# → Solo /run/roamswitch/roamswitch.sock y events.sock (para recibir los eventos de detección eBPF) (la guardia de enlace añade un socket AF_NETLINK,
# pero es un canal del kernel, no un socket de internet)
La única salida atribuible a RoamSwitch es el actualizador diario:
watch -n 5 'sudo ss -tupn | grep -E "roamswitch-(daem|app|mcp)" || echo "(none)"'
# → Siempre "(none)". Ninguno de estos tres abre jamás un socket de internet.
systemctl list-timers roamswitch-update.timer
sudo systemctl start roamswitch-update.service
journalctl -u roamswitch-update.service -n 20 --no-pager
# → Solo realiza HTTPS GET a lafine.net/updates/v1 (manifiesto, firma y datos que han cambiado) y finaliza. Si ClamAV está instalado, freshclam también se conecta a los espejos de ClamAV.
Desactívalo y confirma silencio total:
sudo systemctl disable --now roamswitch-update.timer
# o edite /var/lib/roamswitch/updates.json para establecer "enabled": false
sudo strace -f -e trace=network -p "$(pgrep -x roamswitch-daem)" 2>&1 | grep -i 'connect('
# → Solo se conecta a AF_UNIX / AF_NETLINK. AF_INET / AF_INET6 nunca aparecen.
C.2 Verificación de defensa reproducible (Docker Test Suite)
Los distintos mecanismos de defensa de RoamSwitch (corte Air-Gap, autocuración tras un borrado forzoso del firewall, detección de ransomware, /tmp noexec, Yama LSM, detección de homógrafos, etc.) se pueden reproducir en su propia máquina con el conjunto de pruebas de Docker publicado en el repositorio oficial de soporte (roamswitch-support). El conjunto solo manipula un espacio de nombres de red independiente y los archivos dentro del contenedor, por lo que no afecta a la red ni a los archivos del host. Sin embargo, se ejecuta con --privileged, así que los ajustes compartidos por todo el kernel, como ptrace_scope y suid_dumpable de Yama, también se reflejan en el host y no se revierten al eliminar el contenedor. Ejecútelo en un entorno desechable.
# Obtener y reproducir la suite de verificación (verificación automatizada dentro de un contenedor aislado, usando el paquete de distribución oficial)
git clone https://github.com/lafine1211/roamswitch-support.git
cd roamswitch-support/test/docker
docker build -t roamswitch-test .
docker run --rm --privileged roamswitch-test
El contenedor de prueba recupera automáticamente el paquete de distribución oficial del repositorio APT (lafine.net/apt) y ejecuta y evalúa las comprobaciones dentro de un espacio de nombres de red y de montaje aislado. El conjunto público consta de pentest.sh (los 7 escenarios PENT-1 a 7) y selftest.sh (autopruebas destructivas del demonio: Air-Gap, autocuración, canarios, detección de ráfagas, Vault de cuarentena, etc.). Los ID de las tablas siguientes son números correlativos del registro de verificación y no se corresponden uno a uno con las comprobaciones de los scripts. BadUSB (SP-6), los modos del Link Guard (familia SP-7), la fijación ARP y el interruptor de apagado de la VPN (SP-8 a 10), las comprobaciones individuales del endurecimiento del kernel (familia SP-11) y PENT-8 y 9 no forman parte del conjunto público; son elementos que se confirmaron por otros procedimientos (en un equipo real, en espacios de nombres de red independientes, con inyección manual de eventos).
(1) Defensa de red autónoma y autorreparación de espacios de aire
| identificación | Escenario | Método de verificación | Criterio de aprobación |
|---|---|---|---|
| SP-3 | Habilitación de Air-Gap → caída de salida | Enviar enable_air_gapIPC | policy drop aplicado al gancho de salida; todo el tráfico saliente bloqueado |
| SP-3b | Purga forzada y autorreparación del firewall | Purgar reglas a través de nft delete table inet roamswitch | La autocuración Anti-Clobber restaura las reglas de caída en 5 segundos |
| SP-3c | Fallo cerrado durante el reinicio del demonio | Detener el demonio durante el Air-Gap (selftest.sh usa kill, es decir, SIGTERM) y reiniciarlo | La política de caída de salida persiste sin problemas durante el reinicio |
| SP-3d | Recuperación autorizada de Air-Gap | Enviar autorizado disable_air_gap IPC | Se levantó la política de eliminación, se eliminaron todos los archivos de marcador, se restableció la salida |
| SP-8 | Fijación preventiva de ARP/NDP | Habilitar en un entorno de puerta de enlace de red aislada → enviar ARP gratuito falsificado | La entrada cercana a la puerta de enlace sigue siendo PERMANENT; ARP falsificado ignorado |
| SP-9 | Desconexión automática de VPN (WireGuard) | Servidor Live WireGuard en redes separadas, importar .conf → vpn_up | El protocolo de enlace se completa hasta inet roamswitch_vpn; Cero fugas en la muerte del túnel. |
| SP-10 | Interruptor de interrupción de VPN (escala trasera) | Stub tailscale CLI + redes separadas, tailscale0 IF | TCP/80 de texto plano fuera del túnel y DNS local bloqueados; DERP y MagicDNS permitidos |
(2) Endurecimiento del núcleo y la montura
| identificación | Escenario | Método de verificación | Criterio de aprobación |
|---|---|---|---|
| SP-11a | Endurecimiento del sistema de red | Ejecutar apply_kernel_sysctl_hardening IPC | rp_filter=1, tcp_syncookies=1, accept_redirects=0 alcanzan los valores objetivo |
| SP-11b | Protección de memoria y Yama LSM | Verifique ptrace_scope y suid_dumpable | kernel.yama.ptrace_scope=1 (bloquea el espionaje), fs.suid_dumpable=0 (sin volcado de núcleo) |
| SP-11c | /tmp y /dev/shm defensa noexec | Ejecutar apply_mount_hardening IPC | /tmp y /dev/shm volvieron a montar noexec,nosuid,nodev; ejecución denegada |
| SP-11d | Se conserva la ejecución legítima del usuario | Ejecutar scripts en el directorio de inicio estándar (~/) | Se ejecuta sin obstáculos sin interrumpir los flujos de trabajo legítimos de desarrollador/administrador. |
(3) Defensa y Canarias contra malware y ransomware multicapa
| identificación | Escenario | Método de verificación | Criterio de aprobación |
|---|---|---|---|
| SP-1 | Detección de manipulación canaria | Truncar o cambiar el nombre del archivo canario señuelo (.xlsx) en ~/Documents/ | Se detecta manipulación en cuestión de segundos, lo que activa inmediatamente Air-Gap |
| SP-2 | Detección de ráfagas de cifrado masivo de ransomware | Un solo proceso genera rápidamente múltiples archivos de alta entropía | Proceso de trabajador infractor congelado con SIGSTOP; Air-Gap activado |
| SP-2b | Sin falso positivo en shred | Ejecute shred -n 3 -u en archivos de prueba | La coincidencia de la lista de permitidos evita la detección o congelación de falsos positivos |
| SP-2c | Los procesos críticos nunca se congelan | Validar la lógica del objetivo de congelación durante la detección de ráfagas | Los demonios críticos (dockerd, systemd, containerd) nunca se congelan |
| SP-4 | La cadena de prueba EICAR es solo de notificación | Escriba la cadena de prueba EICAR en ~/Downloads/ | Sin notificación, solo una entrada en el historial de notificaciones; el archivo nunca se pone en cuarentena automáticamente |
| SP-4b | Primero, confirme la firma de malware real | Escribir firma real fuera de las carpetas vigiladas | Genera un diálogo de aprobación modal; puesto en cuarentena sólo tras la confirmación del usuario |
| SP-4c | Refuerzo de permisos de la bóveda de cuarentena | Inspeccionar los permisos del directorio de almacén y los archivos de muestra | Directorio de almacén establecido en 0700, muestras en cuarentena establecidas en 0400 |
| SP-4d | Restauración de archivos originales segura con IPC privilegiada | Enviar restore_quarantine_fileIPC | Archivo restaurado a la ruta original con permisos de usuario (0644) y agregado a exclusiones |
(4) Protección pasiva de enlaces y defensa contra phishing
| identificación | Escenario | Método de verificación | Criterio de aprobación |
|---|---|---|---|
| SP-7 | mode = block aplicación estricta | Conéctese a hosts listados en feeds o blocklistExtra | Caído en la capa TLS (analizado SNI); Los hosts seguros no listados alcanzan HTTP 200 |
| SP-7b | mode = warn retención de cierre fallido | Conéctese al host de advertencia sin respuesta del usuario (tiempo de espera de 8 segundos) | Descarta paquetes cuando se agota el tiempo de espera (fail-closed); permitido sólo con aprobación explícita |
| SP-7c | mode = off desmontaje completo | Desactivar protección de enlace | La tabla inet roamswitch_linkguard está completamente desinstalada |
(5) Mitigación de BadUSB y control de dispositivos
| identificación | Escenario | Método de verificación | Criterio de aprobación |
|---|---|---|---|
| SP-6 | Antigüedad del teclado conectado | Verifique los teclados conectados al inicio del demonio | Dispositivos existentes protegidos; sólo se tomaron las conexiones en caliente posteriores |
(6) Autoverificación con elementos que simulan ataques (PENT-1 a 7 en la suite pública de Docker, PENT-8 y 9 en comprobaciones manuales aparte)
| identificación | Vector de ataque | Detalle de simulación | Criterios de defensa/detección |
|---|---|---|---|
| PENT-1 | Exfiltración C2 | Se intentó la salida HTTP/TCP a C2 bajo Air-Gap | Todos los paquetes cayeron en netfilter; cero fugas |
| PENT-1b | Eliminación forzada de FW | Purgar reglas con nft delete table inet roamswitch | La autocuración Anti-Clobber restaura las reglas en segundos |
| PENT-1c | Restauración segura del espacio de aire | Emisión autenticada disable_air_gap IPC | Se eliminaron las reglas de caída; marcadores eliminados; conectividad restaurada |
| PENT-2 | /tmp Puerta trasera | Ejecutar script de puerta trasera eliminado en /tmp | Denegado por noexec protección de montaje (Permission denied) |
| PENT-2b | /dev/shm Memoria compartida | Ejecutar carga binaria colocada en /dev/shm | Bloqueado por la defensa de la montura noexec |
| PENT-2c | Preservación del espacio del usuario | Ejecutar script en directorio de usuario legítimo (/home/tester/) | Ejecución sin obstáculos; cero interrupción del flujo de trabajo |
| PENT-3 | ptrace espionaje de memoria | PTRACE_ATTACH al objetivo para extraer secretos/tokens | Negado por Yama LSM kernel.yama.ptrace_scope=1 |
| PENT-3b | Raspado secreto del volcado de núcleos | Activar el bloqueo del proceso para obtener el volcado del núcleo de la memoria | Prevenido por fs.suid_dumpable=0 |
| PENT-4 | Manipulación canaria | Archivo de señuelo canario de puesta a cero en ~/Documents/ | Detección en tiempo real de anomalías de tamaño y cambios de hash |
| PENT-4b | Limpieza a granel canaria | Eliminación masiva de archivos señuelo canario | Desaparición de archivo detectada inmediatamente, lo que genera incidente |
| PENT-5 | Falsificación de direcciones IP | Transmitir paquetes con IP de origen falsificada | Eliminado por rp_filter=1 (filtro de ruta inversa) |
| PENT-5b | SYN Inundación DoS | Inundar conexiones TCP medio abiertas | Tabla de estados protegida por tcp_syncookies=1 |
| PENT-5c | Secuestro de ruta ICMP | Inyectar paquetes de redireccionamiento ICMP falsos | Ignorado debido a accept_redirects=0 |
| PENT-6 | Phishing de homógrafos cirílicos | Vaya a gооgle.com (homógrafo cirílico) | Identificado por LinkGuard, obtuvo una puntuación de 40 (PELIGROSO/bloqueo) |
| PENT-6b | Tráfico IP sin formato y texto sin formato | Conexión saliente directa a IP sin formato | Marcado con precisión como factor de riesgo para la seguridad |
| PENT-7 | Fuga de clave de API de OpenAI | Exponer el token sk-proj-... en código o registros | Detectado mediante análisis de entropía de Shannon y enmascarado |
| PENT-7b | Fuga de tokens de GitHub | Exponer el token personal ghp_... en el código | Coincidencia por patrón de token y verificación de entropía |
| PENT-8 | Preferencia de Kernel LPE (Frag Gap) | unshare -U -r sin privilegios (intento CVE-2026-53362) | En la medición de la v1.1.0, rechazado de inmediato con Operation not permitted por user.max_user_namespaces = 0. La edición Client actual no aplica este rechazo por defecto (auto_enable_userns_restriction de guard.yaml está desactivado por defecto; la edición Server lo aplica siempre) |
| PENT-9 | Sensor de amenazas eBPF y congelación de procesos | Alerta crítica/de emergencia inyectada a través del conector Falco eBPF | Proceso congelado vía SIGSTOP en milisegundos (estado: Ts); Air-Gap desplegado |
Tipo de comprobaciones: de los puntos de la tabla anterior, PENT-2/2b (noexec), PENT-3/3b (Yama, volcados de memoria) y PENT-5/5b/5c (rp_filter, SYN cookies, redirecciones ICMP) confirman que una función estándar del kernel o de mount que RoamSwitch configura está en vigor. PENT-8 (prohibir espacios de nombres de usuario) es del mismo tipo, pero es un registro de la versión v1.1.0 y la edición Client actual no lo aplica por defecto. Los puntos que atacan la lógica propia de detección y contención de RoamSwitch son PENT-1/1b/1c (Air-Gap y autorreparación), PENT-4/4b (canarios), PENT-6/6b (Link Guard), PENT-7/7b (detección de secretos) y PENT-9 (congelación y contención tras una detección eBPF). PENT-2c confirma que el trabajo normal no se ve afectado.
(7) Resumen de la Autoverificación (sin Falco [independiente] y con Falco [integración eBPF])
Resumen de los resultados de la Autoverificación integral que simula ataques, ejecutada en un entorno de pruebas Docker aislado (Objetivo: Ubuntu 24.04 con RoamSwitch Client Edition; Atacante: Nmap / curl / hping3, etc.). La tabla siguiente recoge la medición de Client v1.1.0 del 2026-09-06; el 2026-09-25, una versión corregida compilada a partir del código fuente de la serie 1.10 superó 11 de los 11 elementos de la suite de la edición Client (no es una versión publicada; consulte audit/RESULTS-PENTEST-2026-09-25.ja.md en el repositorio público).
| Categoría de verificación | Sin Falco (independiente endurecido) | Con Falco (eBPF mejorado) | Veredicto de defensa |
|---|---|---|---|
| Resistencia de escaneo de puerto externo (Nmap SYN) | PASS (100% filtrado / DROP en Lockdown) | PASS (100% filtrado / DROP en Lockdown) | Sigilo externo total |
| Corte de acceso directo externo (TCP/80, 3306) | PASS (Tiempo de espera caído/respuesta cero) | PASS (Tiempo de espera caído/respuesta cero) | Exposición entrante cero |
| Mitigación de LPE (brecha de fragmentación) del kernel | PASS (medición de la v1.1.0; unshare bloqueado en un entorno con user.max_user_namespaces = 0; en la edición Client actual está desactivado por defecto) | PASS (alerta eBPF + SIGSTOP congelación + Air-Gap) | Defensa en profundidad verificada |
| Detección de puerto no autorizado (anomalía) | PASS (0.0.0.0 enlace marcado como ALTO riesgo) | PASS (0.0.0.0 enlace marcado como ALTO riesgo) | 100% detectado y guiado |
| Prevención de sobrecarga de registros y sobrecarga de CPU | Huella de registro cero (desinstalada) | PASS (95 % de reducción de ruido/búfer de 2 MB/límite de 50 MB) | Optimizado para escritorio |
C.3 Limitaciones conocidas
| Limitación | Impacto | Mitigación |
|---|---|---|
En algunos núcleos, FAN_MARK_MOUNT en tmpfs (/tmp) no entrega eventos | las escrituras de archivos bajo /tmp no son monitoreadas por fanotify | /tmp es efímera y no es una carpeta de usuario monitoreada; ransomware apunta a ~/. Los errores de marca se registran explícitamente |
| No hay entrega de fanotify en contenedores overlayfs/tmpfs | La detección de explosión de entropía en el contenedor no se puede validar | validado en hardware real (ext4); El arnés se salta automáticamente a través de una sonda. |
Las operaciones nftables de RoamSwitch comparten el espacio netfilter con iptables-nft de Docker | Las operaciones de reglas de RoamSwitch pueden interferir con las reglas NAT de Docker | aislado en tablas dedicadas (inet roamswitch y roamswitch_*), pero se deben evitar operaciones sobre el conjunto de reglas completo como nft flush ruleset |
| El protector de enlace ve el DoH de un navegador solo a través de TLS SNI; Hola de cliente cifrado (ECH) también lo oculta | Una vez que ECH se implementa ampliamente, una conexión que utiliza ECH a un sitio compatible con ECH no es identificable por el nombre de host. | la lista de bloqueo local y la heurística todavía actúan en DNS/HTTP y TLS no ECH; La protección contra amenazas DNS (§4.5, activada por defecto) cubre la ruta de resolución |
Cuando enabled en updates.json es false, la frescura del feed depende de la cadencia de lanzamiento | con las actualizaciones desactivadas, la fuente está tan actualizada como el último apt upgrade | las detecciones de mayor gravedad (homógrafo de marca) no necesitan alimentación; Vuelva a inscribirse o habilite DNS de protección contra amenazas para obtener una cobertura siempre actualizada. |