Diseño de Sistemas Lafine · Documento técnico

Informe técnico sobre arquitectura y seguridad de RoamSwitch

Este documento explica con qué privilegios se ejecuta RoamSwitch y qué hace en ese límite. No contiene lenguaje de marketing; Todo lo indicado aquí se puede verificar con la aplicación de envío. binario y su comportamiento real.

Versión v1.6 Alcance RoamSwitch 1.11.14 (build 161) Requisitos macOS 13.0+ / Apple Silicon Publicado 2026-09-23 Team ID GV76B6G4YU

§1

RoamSwitch es una aplicación de barra de menús para Mac. Dependiendo de cuánto confíes en la red, eres conectado actualmente, cambia automáticamente el firewall de macOS, los servicios compartidos, AirDrop, y DNS. También vigila la suplantación de ARP, los puertos abiertos al exterior, el almacenamiento USB y actividad de cifrado tipo ransomware, y cuando detecta algo peligroso irá tan lejos como cortar el tráfico con el filtro de paquetes (pf): un espacio de aire de emergencia.

En otras palabras, RoamSwitch instala un demonio con privilegios de root y, si quisiera, podría detener todos Tráfico de red en Mac. Lo construye una sola persona y "por favor, confía en mí" no es suficiente para justificar ese privilegio. En cambio, este documento explica el diseño en una forma que usted puede verificar.

¿Para quién es esto?

  • Ingenieros decidiendo si instalarlo
  • Investigadores de seguridad y periodistas que quieran comprender los aspectos internos antes de una reseña o un artículo.
  • Personal de seguridad de empresas asociadas que evalúan una implementación interna o un paquete OEM

Lo que este documento no cubre

No aborda el ajuste del umbral de detección, las estadísticas de falsos positivos ni los tutoriales de la interfaz de usuario. Lo que explica son cuatro cosas: privilegios, límites del proceso, flujo de datos y criptografía. Las especificaciones de las funciones en sí se encuentran en el recurso MCP incluido. roamswitch://docs/features y en la ayuda de la aplicación.

Qué se divulga y hasta qué punto (política de divulgación)

Este documento está escrito bajo el supuesto de que un atacante ya tiene el binario distribuido. Cada URL de punto final, identificador, ruta de archivo, protocolo XPC y clave pública integrada que aparece aquí se puede sacar del envío RoamSwitch.app en unos minutos con strings, codesign -d o un proxy de tráfico. Escribiéndolos aquí por lo tanto no le da nada nuevo al atacante. Lo único que avanza es la comprensión del crítico.

Por otro lado, la implementación del lado del servidor que no se puede ver desde el binario: límite de velocidad umbrales, claves, puntos finales de administración, el esquema de base de datos, el diseño del proyecto de Firebase, no están incluidos. El diseño de integración de OEM y socios también está fuera del alcance aquí y se trata en un artículo separado. documento interno. "Lo que se puede aprender observando al cliente" es la línea de divulgación de este documento.

Nota adicional

Este documento corresponde a la fuente de la versión indicada en la parte superior. Donde el comportamiento cambia en una versión posterior, se revisa el documento y se actualizan el número de versión y la compilación de destino. Si encuentra una discrepancia entre el texto y el código, háganoslo saber en lafine.net/contact.html.

§2

RoamSwitch.app se compone de tres ejecutables. Sólo uno de ellos es privilegiado; el otros dos se ejecutan con derechos de usuario de inicio de sesión. Los tres se envían con Hardened Runtime habilitado, ID de desarrollador firmado y notariado.

Diagrama de los componentes de RoamSwitch y los límites de confianza Cliente MCP Claude Desktop / Code MCPServer Privilegio de usuario Solo lectura RoamSwitch.app Privilegio de usuario Barra de menús / Monitorización / UI Helper root / LaunchDaemon SMAppService.daemon Binarios del sistema pfctl / socketfilterfw … stdio (JSON-RPC) Solo lectura * XPC mach service Autorizado mediante firma de código Ejecución de proceso
* MCPServer no se conecta a la aplicación en sí; lee directamente el dominio de preferencias compartidas y los módulos de seguimiento (§8).
Privilegios y rol de cada componente.
ejecutableprivilegiopuedo hacerno puedo hacer
RoamSwitch
.aplicación
Usuario de inicio de sesión Supervise el estado de la red, ejecute diagnósticos, dibuje la interfaz de usuario, llame al asistente a través de XPC, cambie AirDrop a través de defaults, inicie ClamAV (opcional) Opere directamente el firewall, pf o demonios del sistema (todo esto pasa a través del asistente)
RoamSwitch
Ayudante
raíz Sólo las operaciones enumeradas en HelperProtocol (tabla §3): firewall/sigilo, carga/descarga de demonios compartidos, aplicación del conjunto de reglas pf, cambio de DNS, envío de señales a procesos Todo lo demás. No existe una interfaz para ejecutar comandos arbitrarios. No se declara ningún entitlement de envío por red, pero como no está en el sandbox, eso por sí solo no impide la comunicación (la medición del tráfico saliente está en el §7)
RoamSwitch
MCPServidor
Usuario de inicio de sesión Leer y formatear valores de diagnóstico, buscar en la base de conocimiento local; los resultados se devuelven al cliente a través de stdio Cambiar ajustes, alternar el bloqueo, aislar un puerto, expulsar un dispositivo. Por defecto no abre ningún socket ni envía nada por la red (las únicas excepciones son la verificación de firmas npm, desactivada por defecto, y el diagnóstico de vulnerabilidades dirigido a este propio Mac, 127.0.0.1; véase §8). No se declara ningún entitlement de envío por red, pero como no está en el sandbox, eso por sí solo no impide la comunicación

Derechos y firma

  • Los tres objetivos tienen ENABLE_HARDENED_RUNTIME = true.
  • La zona de pruebas de la aplicación está deshabilitada (com.apple.security.app-sandbox = false). Los derechos del ayudante y del MCPServer son diccionarios vacíos.
  • Las compilaciones de distribución están firmadas por la solicitud de identificación del desarrollador, certificadas por notario de Apple y grapadas (§10).
Compensación de diseño

La aplicación Sandbox no se utiliza. RoamSwitch necesita leer el UUID del hardware de IOKit, usar CoreWLAN y DiskArbitration, enumera los sockets de escucha de otros procesos (lsof), abre un XPC conexión a un LaunchDaemon y generar binarios del sistema. Nada de esto es posible dentro del sandbox, por lo que se deja deshabilitado.

Cuatro cosas lo compensan. Primero, tiempo de ejecución reforzado. En segundo lugar, firma de ID de desarrollador y notarización. En tercer lugar, sólo un ejecutable se ejecuta como root (el asistente) y lo que ese asistente puede hacer. es fijo y enumerado (la tabla del §3). Cuarto, las conexiones con el asistente están restringidas por código. firma (§3).

§3

como se registra

El ayudante está registrado como LaunchDaemon usando SMAppService.daemon(plistName:). el plist incrustado en la aplicación (Contents/Library/LaunchDaemons/com.tetsuharu.RoamSwitch.Helper.plist) declara sólo Label, BundleProgram, una sola entrada MachServices y AssociatedBundleIdentifiers. Por cómo funciona SMAppService, El registro ni siquiera es posible a menos que la aplicación esté en /Applications. en el primero registro, el asistente no se activa hasta que el usuario lo apruebe manualmente en Sistema Configuración.

Cuyas conexiones acepta (ClientValidator)

El ayudante inspecciona el proceso de conexión en NSXPCListener shouldAcceptNewConnection y reparte HelperProtocol sólo a aquellos que pase. La inspección utiliza el audit_token en lugar del PID, para Evite la reutilización de PID y TOCTOU.

# Requisitos de firma de código que la compilación Release exige a los clientes que se conectan
identifier "com.tetsuharu.RoamSwitch"
  and anchor apple generic
  and certificate leaf[subject.OU] = "GV76B6G4YU"

Este requisito se verifica con SecCodeCopyGuestWithAttributes y SecStaticCodeCheckValidity, y la conexión se corta si no pasa. Sólo Las compilaciones DEBUG eliminan el pin de ID del equipo para facilitar el desarrollo. Lo que realmente se envía es siempre un Versión de lanzamiento.

Puntos clave del límite

La seguridad del ayudante depende de este requisito único de firma de código. Cualquier cosa que lo satisfaga (un correctamente firmado RoamSwitch.app) puede llamar a todas las operaciones en la siguiente tabla. hay No hay canal para alimentarlo con comandos arbitrarios, pero las operaciones en esa tabla no son débiles en ellos mismos. Si se toma el control del propio RoamSwitch.app, estas operaciones pasan al atacante.

Qué puede hacer el ayudante (la lista completa)

Las operaciones privilegiadas definidas en Shared/HelperProtocol.swift son todas ellas. No hay ninguna API privilegiada que no aparezca aquí.

HelperProtocol: todo lo que se ejecuta como root
Métodoque haceBinario/API invocado
establecerBloquearTodo(_:) Activa o desactiva el firewall de la aplicación y el modo oculto /usr/libexec/ApplicationFirewall/socketfilterfw
--setblockall / --setstealthmode
obtenerBloquearTodoEstado(...) Lee los valores actuales de lo anterior. socketfilterfw --getblockall
setSharingServicesEnabled(_:) descarga / carga de los demonios SSH / SMB / Screen Sharing. Al parar registra solo "los que estaban corriendo" y restaura solo aquellos /bin/launchctl list / unload -w / load -w
(fijado a los tres: ssh.plist / com.apple.smbd.plist / com.apple.screensharing.plist)
enableNetworkAirGap(...)
disableNetworkAirGap(...)
Aplica y levanta el bloqueo completo de emergencia (block drop all). Pasa por PFRulesetCoordinator (§4) /sbin/pfctl -f / -e / -sr
setGuardedDevServerPorts(_:) Utiliza pf para bloquear solo conexiones external a los puertos del servidor de desarrollo dados (el host local pasa). Pasar una matriz vacía los levanta a todos /sbin/pfctl (también a través del Coordinador)
setSecureDNSServers(_:)
restoreOriginalDNSServers(...)
getCurrentDNSServers(...)
Cambia el DNS de los servicios de red activos a DNS que bloquea malware (Quad9 9.9.9.9 / Cloudflare 1.1.1.2), realiza una copia de seguridad de la configuración original y la restaura. /usr/sbin/networksetup
-listallnetworkservices / -getdnsservers / -setdnsservers
setLinkGuardSinkhole(_:) Guardia de enlace (§5). Escribe los dominios de phishing/estafa proporcionados en una sección administrada delimitada de /etc/hosts como 0.0.0.0 y luego vacía la caché de DNS. Una matriz vacía elimina la sección. Los dominios están normalizados y deduplicados; Se eliminan las IP y la basura; con un límite de 60.000; escrito a través de un archivo temporal + reemplazo atómico reescribe /etc/hosts (FileManager.replaceItemAt)
/usr/bin/dscacheutil -flushcache
/usr/bin/killall -HUP mDNSResponder
lockGatewayARP(_:)
unlockGatewayARP(...)
getGatewayARPLockStatus(...)
Bloqueo preventivo ARP/NDP de la puerta de enlace (§5). Fija las asignaciones IP → MAC dadas (la puerta de enlace IPv4, el enrutador predeterminado IPv6, los solucionadores de DNS en el enlace) como entradas de caché vecinas permanent. Los formatos IP y MAC están validados; un modelo de conciliación (se eliminan los pines que no están en la solicitud). El conjunto de pines persiste en gateway_arp_lock.json /usr/sbin/arp -s / -d
/usr/sbin/ndp -s / -d
wireGuardImport(_:)
wireGuardForget(...)
Túnel VPN (§5). El ayudante guarda/borra el texto WireGuard .conf en 0600 solo escritura de archivos
wireGuardUp(punto finalIPv4:punto finalIPv6:puerto:)
wireGuardDown(...)
wireGuardStatus(...)
Sube/baja el túnel/lee el estado. El nombre de host del punto final es resuelto por la aplicación y la IP se pasa al asistente (el DNS del asistente se puede cortar mediante el interruptor de apagado) Homebrew wg-quick up/down, wg show (wireguard-tools; deshabilitado si no está instalado)
terminarProceso(pid:forceKill:) Suspende (SIGSTOP) o fuerza el cierre (SIGKILL) de un proceso. Se utiliza para contener procesos similares a ransomware. pid > 1 solamente llamada al sistema kill(2) (no es un subproceso)
getHelperVersion(...) Devuelve la cadena de versión del asistente (utilizada para comprobar la compatibilidad de aplicaciones) —
Compensación de diseño

terminateProcess puede enviar SIGKILL a cualquier proceso, siempre que pid > 1. setSecureDNSServers acepta cualquier cadena de servidor DNS. Eso es el ancho que necesita la característica, pero no es estrecho. Juzguenlo entendiendo que el El control de firma de código frente a él (ClientValidator) es la única puerta.

Estado dentro del ayudante

  • HelperTool.shared es una instancia única compartida entre conexiones. Solía ​​ser una instancia separada por conexión, por lo que una contención de emergencia que abría una nueva conexión podría afectar a una carrera que perdía la pista de "qué servicios traer de vuelta".
  • El servicio compartido y las copias de seguridad de DNS solo se modifican en una cola en serie (stateQueue).

Separación de privilegios dentro del asistente y la excepción para leer el registro SSH

Justo después de arrancar, el asistente se impone a sí mismo un techo aplicado por el kernel (un sandbox en tiempo de ejecución, distinto del entitlement de App Sandbox mencionado arriba), que heredan los procesos hijos que inicia. Ese techo impide sustituir programas del sistema, registrar un LaunchDaemon o LaunchAgent, reescribir la configuración de sudoers, PAM o sshd, escribir directamente en el almacén de directorios local y ejecutar programas ubicados en lugares donde pueden escribir los usuarios, los volúmenes externos o las carpetas temporales. No impide los cambios de cuenta hechos mediante Open Directory (dscl) y no restringe el tráfico de red (no es para eso).

Bajo este techo, /usr/bin/log no se ejecuta. En macOS 26, log se niega a ejecutarse dentro de un sandbox (log: Cannot run while sandboxed) y también como nobody (Must be admin). Por tanto, ni la app del usuario con sesión iniciada ni el asistente root dentro de su sandbox pueden leer el registro de sshd (el registro unificado). La detección de fuerza bruta SSH necesita leer ese registro, así que la separación de privilegios tiene una excepción.

La excepción es un pequeño proceso root: antes de entrar en su sandbox, el asistente inicia el mismo binario con --ssh-log-reader. Ese proceso solo acepta un comando de un byte (iniciar o detener) y no hace más que lanzar /usr/bin/log con argumentos fijos (log stream --style ndjson --level info y un filtro fijo que deja pasar solo líneas de sshd). No pasa variables de entorno y quien llama no tiene forma de elegir argumentos ni un comando. No lee ni interpreta el registro; la salida va a una tubería que le entregó el asistente.

Las líneas del registro contienen cadenas que un atacante puede escribir, como el nombre de usuario, así que el asistente root no las interpreta. Entrega cada línea a un proceso de análisis que se ejecuta como nobody dentro de un sandbox que prohíbe el acceso a la red, la escritura de archivos y el inicio o la bifurcación de procesos, y la dirección de origen se toma del final de la línea (IP port N), sobre el que el nombre de usuario no influye. En un Mac con la sesión remota desactivada, este proceso no inicia log y no lee nada. Las operaciones SSH que se pueden llamar mediante el XPC del asistente son cinco: aplicar los ajustes, obtener el estado, los registros y los inicios de sesión, y levantar un bloqueo. Ninguna acepta un comando.

§4

Cuatro características tocan pf: el espacio de aire de emergencia, la protección del puerto del servidor de desarrollo, el túnel VPN Interruptor de interrupción WireGuard (§5, 1.7.6+) y su interruptor de interrupción Tailscale (§5, 1.8.0+: solo los backends seleccionados). Todos ellos siempre van a través de un único punto de entrada, PFRulesetCoordinator, y nunca ejecutar pfctl -f ellos mismos.

Por qué hay un único punto de entrada

Anteriormente, las dos funciones cargaban reglas con pfctl -f de forma independiente, compitiendo sobre el conjunto de reglas principal único de pf. Si la regla estricta del guardia del puerto block ... port {…} fuera cargado después del block drop all del espacio de aire, podría terminar en un estado donde la pantalla decía "aislado" pero aún se podía acceder a la Mac. Este error se encontró atacando el máquina de otro host, y se corrigió en 1.4.3 (la historia está escrita en docs/marketing/zenn/03_lan_side_attack_test.md).

¿Cómo funciona ahora?

  • Reconstruido en su totalidad cada vez. Todo el conjunto de reglas requerido se reconstruye a partir del estado actual y se aplica de una sola vez. Nunca se aplica como diferencia.
  • Una cola serial. Cada cambio de pf se ejecuta en el mismo DispatchQueue, por lo que ya sea que provenga de una conexión XPC, el inicio del asistente o el temporizador de seguridad, los cambios se procesan en orden.
  • El orden de prioridad es el siguiente (los elementos más arriba tienen prioridad).
    1. Entrehierro de emergencia → set skip on lo0 y block drop all (no se considera nada más)
    2. Interruptor de interrupción de VPN → block drop all más pass quick solo para: lo, la interfaz del túnel (utunN), el protocolo de enlace UDP a las IP del punto final anclado, DHCP e ICMP
    3. Guardia del servidor de desarrollo → block drop in quick proto tcp ... port { … }
    4. Ninguno → recargar /etc/pf.conf y devolver pf a su estado original
  • Vuelve a leer después de aplicar. pfctl -sr vuelve a leer las reglas para confirmar que block drop all, o la regla de cada puerto, está realmente cargada. Un caso en el que pfctl -f se ignoró silenciosamente no se considera un éxito.
  • El archivo temporal se escribe en una ruta que contiene UUID y se elimina una vez aplicado (v1.4.5 eliminó la ruta fija en favor de una difícil de adivinar). El directorio estatal es /Library/Application Support/RoamSwitch.
Comportamiento de la API

Las respuestas XPC de enableNetworkAirGap y setGuardedDevServerPorts, (Bool, String?), informar si la operación pasó por completo relectura. La persona que llama (como ARPSpoofContainmentManager) vuelve a intentarlo en caso de error, y si todavía falla, aparece el mensaje directamente en la pantalla: "El tráfico aún no se ha detenido. Apague Wi-Fi ahora."

§5

Los tipos de bloque son cada uno diferente.

tipos de bloque
amableAlcancegatillobucle invertido
Espacio de aire de emergencia Detiene todo el tráfico, entrante y saliente. Cuando se detecta actividad de cifrado similar a ransomware. Al detectar suplantación ARP, se activa de inmediato solo en modo Bloqueo; en redes equilibradas/de confianza, en su lugar notifica (usted lo activa manualmente). Ante un patrón de comando conocido de tipo «ClickFix» en el historial del shell, como decodificar base64 directamente hacia un shell o AppleScript (1.8.7+, Pro, desactivado por defecto). No se usa para la protección cotidiana fuera de casa Pasó con set skip on lo0
Interruptor de interrupción de VPN Todo excepto el túnel, su protocolo de enlace, DHCP e ICMP. Cuando el túnel VPN (§5, Pro, desactivado de forma predeterminada) está activado y te unes a una red que no es de confianza. Se mantiene hasta que se establece el túnel (y mientras está abajo) Pasó con set skip on lo0
Guardia de puerto del servidor de desarrollo Sólo external conexiones TCP a los puertos indicados Un aislamiento manual con un solo clic o bloqueo automático cuando se detecta un puerto de escucha desconocido (Pro) Desde localhost, sin cambios
Protección diaria de redes no confiables Cortafuegos y sigilo, se detuvo el uso compartido (§6). pf no se utiliza Cuando te conectas a una red que no te has registrado —
Guardia de enlace Solo resolución de nombres de dominios de phishing/fraude (0.0.0.0 a través de /etc/hosts). pf no se utiliza Un destino en el feed de amenazas o un homógrafo de marca. Activado de forma predeterminada (Pro) No afectado
Bloqueo preventivo ARP/NDP Solo la MAC de la puerta de enlace, el enrutador IPv6 y el DNS (caché vecino) en el enlace. pf no se utiliza Al unirse a una red que no es de confianza (Pro, desactivada de forma predeterminada). Se vuelve a fijar en cada cambio de red No afectado

Cómo el Air-Gap ni se eterniza ni se puede esperar a que acabe

  • Cómo termina depende de lo que lo activó (desde 1.9.52). Un señuelo de ransomware alterado, una detección confirmada de XProtect y un aislamiento manual elegido desde un aviso ARP son de «confianza alta»; ClickFix, las anomalías de puertos y similares son de «confianza baja». Un aislamiento de confianza baja se levanta solo, como antes, como máximo a los 10 minutos. Uno de confianza alta no se levanta a los 10 minutos: levantarlo del todo solo por el tiempo permitiría a un atacante limitarse a esperar.
  • Un aislamiento de confianza alta se mantiene totalmente cerrado hasta un límite (1 hora por defecto, de 5 minutos a 24 horas en los ajustes) y entonces comprueba la causa. En el caso del ransomware, solo da la causa por desaparecida si el proceso sospechoso se identificó y ha terminado, y los señuelos no han cambiado desde la detección. Si no se puede confirmar, pasa al «modo degradado»: las conexiones nuevas siguen bloqueadas (salvo el bucle local y DHCP) y solo se restablece la radio Wi-Fi, para que pueda ver qué ocurre. Nunca se abre solo por el paso del tiempo. Solo se abre si usted lo levanta o si se confirma que la causa ha desaparecido.
  • Al empezar un aislamiento, también se cortan las conexiones salientes que ya estaban abiertas. Se vacía la tabla de estados de pf (pfctl -F states), lo que detiene el tráfico anterior al aislamiento. Lo mismo ocurre al pasar al modo degradado.
  • Un aislamiento causado por suplantación ARP solo se levanta automáticamente cuando se confirma, sin ningún tráfico, que la causa ha desaparecido. La comprobación solo lee la caché ARP (no envía paquetes) y da la causa por desaparecida cuando la MAC de confianza de la puerta de enlace se ve en 3 comprobaciones consecutivas que abarcan al menos 60 segundos. Durante el aislamiento, sin embargo, la entrada ARP de la puerta de enlace puede desaparecer de la tabla de vecinos (confirmado en un Mac real) y entonces no se puede confirmar la causa. En ese caso permanece en modo degradado hasta que usted lo levante.
  • Aunque el helper se caiga, la comprobación de levantamiento no se detiene. Un LaunchDaemon dedicado sin ningún KeepAlive (AirGapFailsafe) se inicia por sí solo cada 3 minutos y toma la misma decisión que el helper (confianza baja: levantar a los 10 minutos; confianza alta: comprobar la causa en el límite y pasar a degradado). Si el propio helper se bloquea y pierde su temporizador de levantamiento, esta red de seguridad no se ve afectada (desde 1.8.6; la decisión según la confianza desde 1.9.52).
  • Este daemon dedicado nunca toca la propia configuración de inicio del asistente interactivo. Un intento anterior (1.8.5) usaba el propio KeepAlive.PathState del asistente para esta recuperación, pero en ocasiones interfería con un cierre normal de la aplicación y provocaba un reinicio no deseado, por lo que se revirtió en favor del daemon actual, totalmente independiente.
  • La comprobación de la marca de tiempo utiliza una combinación híbrida de tiempo de actividad monótono (ProcessInfo.systemUptime) y hora de reloj. Normalmente prioriza el tiempo de actividad monótono, de modo que las correcciones NTP y los cambios manuales de hora no le afectan. Solo recurre a la hora de reloj cuando el tiempo de actividad no es fiable —justo después de un reinicio, detectado cuando el valor actual es menor que el almacenado.
  • Se vuelve a aplicar tras un reinicio o al recrearse el demonio. Al arrancar, el helper lee el estado del disco con reapplyFromDisk() y restaura por sí mismo el Air-Gap, el guardián de puertos y los valores predeterminados del sistema, en ese orden. También conserva el estado del aislamiento (confianza, completo o degradado) y el tiempo transcurrido; la cuenta continúa desde la primera activación (confirmado en un Mac real reiniciando el helper).
  • Un levantamiento fallido se trata como una falla. Si block drop all en realidad no se pudo eliminar, la marca de tiempo se vuelve a escribir para que el temporizador de seguridad y el reintento tengan algo en lo que converger. La pantalla nunca muestra falsamente "levantado".
  • Puede recuperar el control en cualquier momento: con «Levantar aislamiento Air-Gap» en la barra de menús, con el botón de levantar del modal, o simplemente apagando el Wi-Fi. Levantarlo no requiere Pro (funciona incluso si Pro caduca en mitad de un aislamiento).

Cómo se recupera un falso positivo de guardia de puerto

El bloqueo automático de puerto desconocido (Pro, activado de forma predeterminada una vez que se activa Pro) puede detener un acceso legítimo Receptor LAN: LocalSend, Syncthing, cualquier cosa comenzó después de que se habilitó la protección. cuando eso sucede, permítalo desde el botón "Permitir" en el banner de notificación o en la fila correspondiente en la pantalla "Puertos expuestos". Un ejecutable permitido una vez se registra como conocido y no se bloquea nuevamente (PortAnomalyGuard.allowPort(_:)). Tenga en cuenta que los intérpretes de guiones genéricos (Python, Node.js, Netcat, etc.) tienen un alcance estricto de path:port en lugar de binario por sí solos para evitar ataques de personas que viven de la tierra. Demonios del sistema Apple que satisfacen anchor apple (rapportd, que respalda Handoff y similares) no son visto en primer lugar.

Cebo canario de ransomware autorreparable y supresión de contención

Si los archivos señuelo canary son manipulados o renombrados y activan el espacio de aire de emergencia, liberarlos la contención después de verificar la seguridad regenera automáticamente los archivos canary dañados o faltantes de nuevo a su hash de referencia auténtico generado a partir de plantillas integradas, restaurando inmediatamente la vigilancia kqueue ininterrumpida (evitando la contaminación de referencia del adversario). Además, durante la contención activa (mientras se muestra el modo de emergencia), las alertas de notificación redundantes y los eventos de reactivación del sondeo periódico de integridad en segundo plano se suprimen automáticamente para evitar distraer al usuario durante la respuesta al incidente.

Detección genérica de ransomware basada en entropía — 1.10.0, Pro, activada por defecto

Vigila las carpetas Documentos, Escritorio, Descargas y Fotos mediante FSEvents, y se activa cuando llegan escrituras con entropía de Shannon superior al umbral (7,92) en una ráfaga de 20 o más archivos en 5 segundos. A diferencia del enfoque canary (archivo señuelo), no depende de la manipulación de un archivo señuelo, por lo que también detecta el cifrado en lugares sin archivo canary. Solo cuando se alcanza el umbral utiliza lsof para identificar el proceso y comprobarlo contra una lista de permitidos (cp, mv, ditto, tar, ffmpeg, Xcode, Time Machine, navegadores principales, etc.). Al detectarlo, activa el mismo bloqueo de red automático, a través de RansomwareContainmentManager, que usa la protección canary.

La edición Linux puede rastrear ráfagas por proceso porque fanotify asocia un PID a cada evento de escritura. macOS no tiene fanotify ni (por diseño) EndpointSecurity, por lo que esta implementación solo lee la ruta mediante FSEvents y cuenta la ráfaga de forma global, independiente del proceso. Limitación: cuando varios procesos legítimos escriben a la vez grandes cantidades de archivos de alta entropía (por ejemplo, trabajos de compresión o cifrado simultáneos), es más probable un falso positivo aquí que en la edición Linux.

Instantáneas previas al daño para recuperar tras un ransomware (Mac)

Una instantánea tomada en el momento de la detección ya contiene los archivos cifrados antes de ella, así que no es un camino de vuelta. Por eso RoamSwitch toma instantáneas locales APFS de forma periódica, con independencia de la detección (cada 6 horas por defecto; desactivado, 1, 3, 6, 12 o 24 horas). Cuando salta una detección, las nuevas instantáneas previas al daño y la limpieza se pausan hasta 7 días (modo de retención), para que el estado cifrado no desplace la última generación anterior al cifrado. La recuperación es siempre manual: solo los archivos elegidos se copian a ~/RoamSwitch-Recovered/. Nunca se sobrescriben los archivos actuales y no se ofrece restaurar el volumen completo. Copiar requiere montar la instantánea, por lo que el helper con privilegios necesita acceso total al disco (las instantáneas se siguen creando sin él). macOS puede eliminar por sí mismo las instantáneas locales, por ejemplo con poco espacio libre, y no se garantiza que duren más de unas 24 horas. La ventana de recuperación es una función Pro (la creación de las instantáneas funciona también sin Pro).

Paquete de evidencia forense — 1.10.0

Cuando se inicia un Air-Gap de emergencia — activado por la protección canary, la protección de ransomware por entropía u otra similar — una cola en segundo plano que no bloquea la propia contención guarda una instantánea de la lista de procesos (ps), las conexiones de red (lsof -i), los archivos modificados en los últimos cinco minutos y los archivos abiertos por el proceso sospechoso, en ~/Library/Application Support/RoamSwitch/incident-evidence/<marca de tiempo>/manifest.json. El manifiesto registra el SHA-256 de cada artefacto para poder detectar manipulaciones posteriores. La lista de paquetes está disponible desde la herramienta MCP get_forensic_evidence_bundles. Limitación: al construirse sin el derecho EndpointSecurity, no captura trazas de paquetes ni instantáneas de memoria/descriptores de archivo del proceso.

Registro de ejecución de procesos (Mac, Pro, desactivado por defecto)

El helper privilegiado lanza el /usr/bin/eslogger propio de macOS (exec, fork, exit) como proceso hijo y registra los inicios de procesos solo en este Mac. No solicita el entitlement de EndpointSecurity (nunca llama a es_new_client) y RoamSwitch debe estar permitido en Acceso total al disco (Ajustes del Sistema; si no aparece, añada /Applications/RoamSwitch.app con «+»). Mientras falte el permiso, muestra el estado «no disponible», espera y vuelve a comprobar cada 60 segundos (no cambia a un sondeo costoso). Solo notifica. Nunca bloquea la ejecución ni la comunicación, ni aísla.

Notifica estos diez comportamientos: un navegador, una aplicación de Office o de correo que lanza un shell directamente; la ejecución de un binario sin firmar desde una carpeta temporal o una ubicación en cuarentena; un comando de una línea que descarga y lo canaliza a un shell (combinado con una URL de IP directa, base64, eval o verificación TLS desactivada); osascript usando base64 o eval; la ejecución de un archivo justo después de eliminar su atributo de cuarentena; el lanzamiento de un binario sin firmar desde un registro de inicio automático recién escrito (LaunchAgent o LaunchDaemon); un proceso padre no firmado por Apple que lee un secreto del llavero (como security find-generic-password -w); una línea de python3, perl, ruby, php o pwsh con -c/-e/-Command en la que una decodificación base64/eval y una llamada a curl, wget, exec o system aparecen juntas en la misma línea (desde 1.10.0; usar solo base64 o solo eval no activa esto); y el lanzamiento de un proceso con la variable de entorno DYLD_INSERT_LIBRARIES definida (desde 1.10.0; para evitar filtrar secretos, solo se lee el valor de esta única variable; los procesos firmados por Apple quedan excluidos); y una línea de comandos que nombra directamente un archivo de credenciales señuelo colocado por RoamSwitch (como ~/.aws/credentials o ~/.env.backup, reconocido por la marca en su contenido), por ejemplo cat, tar o curl -F; los archivos reales nunca coinciden y las lecturas mediante una biblioteca quedan para la vigilancia de honeytokens. Cada detección lleva un ID de técnica MITRE ATT&CK.

Solo las detecciones de gravedad alta se convierten en banners, y las del mismo tipo se agrupan en una cada 10 minutos (cada evento queda en el registro). Las herramientas de desarrollo como Claude Code y la CLI de GitHub que leen sus propias credenciales no se notifican por defecto. Si otro programa las lee, la acción «Permitir este programa» de la notificación permite solo ese elemento (por Team ID si está firmado; si no, por nombre del ejecutable, junto con el nombre del elemento).

El registro se guarda legible solo por root (directorio 0700, archivos 0600) y conserva 200 MB y 14 días por defecto. No se guardan las variables de entorno, pero los argumentos de línea de comandos pueden contener secretos. Cada segmento se sella en una cadena de hashes enlazada al hash del segmento anterior, y «Verificar cadena» detecta ediciones, segmentos ausentes y sellos eliminados (cada uno confirmado en un Mac real). Esto detecta la manipulación, no la impide. Borrar o revertir el segmento más reciente no se puede detectar. La lectura pasa por el XPC del helper, y MCP pasa por la app (con nueva comprobación de Pro).

Límites: las ejecuciones anteriores al inicio del helper o a la activación del registro no se registran. eslogger observa a posteriori y no puede bloquear. Con cargas como miles de inicios de proceso por segundo se pierden eventos (el recuento se muestra en pantalla y el funcionamiento continúa). Este registro no sustituye a un EDR (detección y respuesta en endpoints).

Honeytokens de credenciales — 1.10.0, Pro, activados por defecto

Coloca un archivo de credenciales señuelo, marcado con un valor centinela, en cada una de estas tres ubicaciones: ~/.aws/credentials, ~/.ssh/id_rsa_backup y ~/.docker/config.json. Nunca sobrescribe un archivo real ya presente en esa ruta. La detección de acceso combina el mismo mecanismo kqueue (DispatchSource.makeFileSystemObjectSource) que usa la protección canary con un sondeo de la fecha de acceso cada 30 segundos, ya que sin EndpointSecurity no se puede capturar de forma fiable un evento open(). La detección solo notifica; nunca bloquea nada ni activa el aislamiento. El señuelo de ssh usa un nombre que ssh no busca por defecto (no id_rsa), de modo que una ejecución normal de ssh ni se rompe ni genera falsas alarmas. El señuelo de AWS usa un nombre de perfil distinto en lugar de [default], el de Docker apunta a un registro interno inexistente y todos se crean con modo 0600 (desde la 1.10.3). Además de esos tres, se colocan señuelos en ~/.pypirc y ~/.env.backup (para búsquedas de tokens de PyPI, archivos .env y claves de API de IA). No se usa ~/.npmrc porque npm lo lee en cada ejecución, ni las credenciales de GCP porque un archivo falso rompería la autenticación real. Cuando se accede realmente a un señuelo, se coloca además exactamente un señuelo relacionado (~/.aws/config, ~/.ssh/config o ~/.kube/config) y también se vigila; la cadena termina tras un paso y no se sobrescribe ningún archivo existente.

Supervisión de acceso a credenciales del navegador — 1.10.0, opcional incluso en Pro, desactivada por defecto

Vigila, mediante FSEvents, las escrituras en las bases de datos de contraseñas/cookies guardadas de Chrome, Brave, Edge, Vivaldi, Chromium y Firefox. Cuando ocurre una ráfaga, lsof identifica el proceso; cualquier cosa que no sea el propio navegador, un indexador como Spotlight o un demonio de copia de seguridad cuenta como detección. A diferencia de las demás protecciones nuevas, activar Pro no la activa automáticamente: una extensión del navegador o la función de importación de un gestor de contraseñas pueden dispararla, por lo que el riesgo de falso positivo es lo bastante inherente como para que se entregue como opcional.

Comunicación con el Sensor (TLS y firmas)

El emparejamiento, las solicitudes de auditoría y la obtención de resultados con un RoamSwitch Sensor (un producto aparte) de la misma LAN usan TLS 1.3 con la huella del certificado (SHA-256) fijada (Sensor 0.3.9 y posteriores). La huella no se obtiene por la red; el usuario escribe el valor que muestra la pantalla del Sensor. No se validan la autoridad certificadora, el nombre de host ni la caducidad, y solo se confía en el valor fijado. Si TLS falla, no se recurre al texto plano (recurrir a él permitiría a un intermediario degradar la conexión). Las firmas de las solicitudes incluyen marca de tiempo y nonce (v2) para impedir la repetición. Solo un emparejamiento antiguo sin huella sigue conectando en texto plano como antes, con una advertencia.

Si el certificado cambia, se rechaza la conexión. La actualización de la huella fijada nunca es automática: solo se guarda cuando el usuario escribe y confirma el nuevo valor y una sonda firmada tiene éxito realmente por TLS. Límite: esta sonda no comprueba con una clave que el Sensor sea el mismo que se emparejó antes. Depende de que el usuario escriba el valor que confirmó en la pantalla del Sensor.

Detección multicapa de descargas (firmas estáticas + ClamAV) — capa de firmas estáticas añadida en 1.8.7

Los archivos nuevos ubicados en las carpetas Descargas, Escritorio o Documentos pasan primero por una comprobación ligera de firmas estáticas mediante StaticSignatureScanner (solo los primeros 4 MB, con una simple coincidencia de patrones de bytes AND/OR; sin expresiones regulares ni cálculos de entropía). No necesita el entitlement de EndpointSecurity y sigue funcionando incluso donde ClamAV no está instalado. La detección se limita deliberadamente a lo que puede afirmarse como un hecho sin un corpus real de malware: la firma de prueba EICAR, estándar del sector, y cinco reverse shells clásicas de una sola línea documentadas en referencias públicas de seguridad ofensiva (por ejemplo, PayloadsAllTheThings) —/dev/tcp/ de bash/sh, -e de netcat, pty.spawn de Python, Socket de Perl y fsockopen de PHP. No escribimos «firmas» a nivel de bytes para familias de malware con nombre propio sin contar con una muestra real de la que extraerlas; eso solo daría una falsa sensación de seguridad. Los propios patrones se almacenan ofuscados con XOR 0x5A, para que el binario de RoamSwitch nunca incluya literalmente una cadena EICAR o una reverse shell por la que el propio ClamAV pudiera marcarlo.

Solo los archivos que la capa de firmas estáticas considera «limpios» pasan al análisis de ClamAV. Los archivos nuevos en las carpetas Descargas, Escritorio o Documentos son analizados de inmediato por ClamAV, tengan o no una extensión .tmp o un atributo com.apple.quarantine (por ejemplo, una copia hecha desde Terminal). EICAR y otras firmas de prueba estándar del sector se tratan como inofensivas: no se muestra ninguna notificación, solo se registra una entrada en el historial de notificaciones, y nunca se ponen en cuarentena ni se bloquean (solo las muestras de malware genuinas se ponen en cuarentena). Y si ya existía previamente un archivo en cuarentena con el mismo nombre, la nueva amenaza igualmente se traslada a la carpeta de cuarentena con un nombre único con marca de tiempo, de modo que una amenaza nunca puede permanecer en su ubicación original por una colisión de nombres.

Detección de web shells en las carpetas web de desarrollo (Web Rules)

Los archivos del desarrollo web local (~/Sites, MAMP, Herd, Local, /Library/WebServer/Documents y las carpetas de proyecto registradas para el cotejo de CVE de paquetes) se comprueban con las mismas 18 reglas de detección que la edición Linux: PHP, JavaScript, JSP y ASP. Las reglas describen solo técnicas documentadas públicamente y no contienen ninguna firma extraída de una muestra real. Hay tres capas: integradas en la app, una actualización firmada de solo recepción una vez al día (incluida en la obtención única existente) y sus propias reglas en ~/Library/Application Support/RoamSwitch/rules.d (solo se leen archivos propiedad de root o suya que ni el grupo ni otros puedan escribir). Las carpetas que la app ya vigila, como Descargas, se comprueban justo después de una escritura; las carpetas web de desarrollo, una vez al día y bajo demanda (la comprobación diaria no entra en node_modules ni .git; la de una carpeta elegida sí entra en node_modules). Como no se usa EndpointSecurity, nada puede detenerse antes de ejecutarse: toda detección es posterior.

Por defecto solo se pone en cuarentena automáticamente critical. Un resultado high no toca el archivo y aparece como notificación y en una lista de Ajustes, donde puede elegir «no detectar de nuevo» (el nivel de cuarentena se cambia en Ajustes). El hook posterior a la detección (~/Library/Application Support/RoamSwitch/hooks/post-detection) solo se ejecuta si pertenece a root o a usted y nadie más puede escribirlo, y se detiene a los 30 segundos. La respuesta sobre cuentas (desactivada por defecto; activarla requiere la contraseña de administrador) revoca el shell de inicio de sesión del propietario de un archivo en cuarentena, pero no toca root, UID inferiores a 501 ni usuarios con sesión iniciada (incluido usted, que usa este Mac). Ejecutado como root bajo el propio sandbox del ayudante en una máquina virtual con macOS 26, se verificaron el cambio real de shell con dscl y su restauración (el sandbox bloquea la escritura directa en dslocal, pero no los cambios hechos mediante dscl). La confirmación con la contraseña de administrador y la ruta de llamada de la app al ayudante todavía no se han verificado en un equipo real. Hubo 0 resultados en 114.501 archivos de una carpeta de desarrollo.

Supervisión de nueva persistencia de inicio (LaunchAgent/LaunchDaemon) — 1.8.7

Los nuevos archivos .plist colocados en ~/Library/LaunchAgents, /Library/LaunchAgents o /Library/LaunchDaemons se detectan en tiempo real mediante FSEvents (PersistenceMonitorGuard). Sin el ES_EVENT_TYPE_AUTH_CREATE de EndpointSecurity, no se puede bloquear la propia escritura. Lo que sí se puede hacer es notarlo en cuestión de segundos y evaluar su contenido.

La evaluación se basa en qué se está lanzando directamente, no en la firma del propio ejecutable. Técnicas reales observadas en 2026 —un LaunchAgent disfrazado de Google Update que oculta un script bash decodificado en base64, o un LaunchDaemon con privilegios de root que reejecuta en cada arranque una carga útil de AppleScript decodificada en base64— ocultan ambas el código malicioso en el propio script, mientras llaman sin más a un /bin/bash o /usr/bin/osascript firmado válidamente. Una comprobación que solo mirara la firma del ejecutable dejaría pasar ambos casos. Por eso, cualquier LaunchAgent/LaunchDaemon recién registrado que invoque directamente un intérprete de scripts sin firmar (bash, sh, zsh, osascript, python3, perl, ruby, php y similares) se marca de forma incondicional, sin importar que la firma del propio intérprete sea válida. Los argumentos que se le pasan también se someten a StaticSignatureScanner, y cualquier patrón conocido como malicioso se incluye en la notificación. Las entradas registradas mediante BundleProgram (que apuntan a un binario compilado y firmado dentro de un paquete de aplicación, incluido el propio Helper de RoamSwitch) simplemente se verifican de la forma habitual, con codesign --verify --strict.

Detección de ClickFix y bloqueo de emergencia — 1.8.7, desactivado por defecto

«ClickFix» es una técnica de ingeniería social en la que una falsa pantalla de CAPTCHA o error le indica que abra Terminal y pegue un comando «para verificar». Las detecciones aumentaron más de un 500 % entre 2024 y 2025, y hacia 2026 se considera uno de los vectores de ataque más frecuentes en macOS. Como es el propio shell legítimo del usuario el que ejecuta exactamente lo que se escribió, elude por completo la verificación de firma de Gatekeeper.

Barra de menú → Protección contra malware → Defensa ClickFix (Pro, desactivada por defecto). Vigila mediante FSEvents las líneas nuevas añadidas a ~/.zsh_history/~/.bash_history (solo las líneas añadidas después de que se inicie la protección; nunca examina el historial ya existente), y solo coincide con las reverse shells clásicas que comparte con StaticSignatureScanner, además de la combinación específica de decodificar base64 directamente hacia un shell o hacia osascript. Un simple curl | bash —extremadamente común en instaladores legítimos— queda deliberadamente excluido, ya que es indistinguible de las instrucciones oficiales de instalación de Homebrew, rustup, nvm y similares. En cuanto se encuentra una coincidencia, se activa el mismo air-gap de emergencia descrito antes. Como el comando ya se ha ejecutado, esto es necesariamente a posteriori, pero aun así puede interrumpir una descarga de segunda etapa o una exfiltración de credenciales en curso. Como cualquier otro disparador de air-gap, se levanta por sí solo a los 10 minutos como máximo (véase el inicio de esta sección).

Una limitación conocida. Dado que la detección depende de una escritura en el historial del shell, no puede detectar la variante sin archivo que evita Terminal por completo invocando directamente el Editor de Scripts mediante el esquema de URL applescript://, sin escribir nada en disco. Apple ha introducido cierta mitigación para esto (un diálogo de confirmación para scripts no identificados), pero se han reportado variantes que lo eluden. Probamos esta vía en hardware real usando el registro unificado de macOS (log show/log stream) y no encontramos ninguna señal de registro distintiva en la que basar la detección. Preferimos exponer con claridad esta limitación antes que afirmar una cobertura que no tenemos.

Detección de riesgos de Docker — 1.8.9, Pro, desactivada de forma predeterminada

Barra de menús → «Protección antimalware» → «Detectar contenedores Docker privilegiados y montajes docker.sock» (DockerEventGuard). Dado que esta base de código no tiene precedente de una conexión de streaming persistente como docker events, utiliza el mismo enfoque de sondeo por temporizador que PortAnomalyGuard. Cada 20 segundos obtiene el conjunto de ID de contenedores con un ligero docker ps -q, y solo ejecuta docker inspect --format sobre la diferencia (contenedores recién iniciados) para una inspección detallada. La cadena de formato usada para la detección es deliberadamente idéntica a la de la edición Linux (roamswitch_core::health::LinuxHealthChecker::DOCKER_INSPECT_RISK_FORMAT), de modo que ambas plataformas señalan exactamente las mismas condiciones (inicio en modo --privileged, o un montaje enlazado de /var/run/docker.sock).

No se toma ninguna acción automática al detectarlo. Un contenedor privilegiado o un montaje de docker.sock es una «configuración» de riesgo que podría permitir una fuga de contenedor, pero no es un compromiso confirmado; también hay usos legítimos, como ejecutar deliberadamente un agente de supervisión en modo privilegiado. Dado que la mayoría de los usuarios no usan Docker en absoluto, esta función permanece desactivada de forma predeterminada incluso con una licencia Pro. No utiliza el entitlement EndpointSecurity; la CLI docker se localiza comprobando primero la ruta de instalación predeterminada de Docker Desktop, luego Homebrew y finalmente which. Verificado en una instalación real de Docker Desktop (29.7.2) en tres escenarios: un contenedor privilegiado, un contenedor con montaje docker.sock y un contenedor normal, sin falsos positivos ni detecciones omitidas.

Auditoría de fugas de secretos y claves API — añadida en 1.8.4, análisis de carpetas añadido en 1.8.9

Barra de menús → «Protección antimalware» → «Auditoría de fugas de secretos/claves API». Pega texto y se comprueba al instante en busca de claves API y tokens filtrados, mostrando el número de línea, una cadena enmascarada y una recomendación por cada hallazgo (SecretLeakAuditor, totalmente en el dispositivo). La versión 1.8.9 añadió la opción «Elegir carpeta para analizar», que permite al mismo motor de detección auditar de forma recursiva un directorio, como un repositorio de código fuente, mediante auditDirectory(at:). Se excluyen automáticamente .git, node_modules, target, vendor, dist, build, __pycache__ y venv, así como los archivos de más de 2 MB o detectados como binarios. El procesamiento se ejecuta fuera del hilo principal para no bloquear la interfaz, y nunca se envía nada al exterior.

Link Guard (bloqueo de conexiones de phishing): 1.7.2 y posteriores, reforzado en 1.8.0

Barra de menú → "Protección contra malware" → "Link Guard" (Pro). Bloquea las conexiones a sitios de phishing/estafa en el dispositivo, en todos los navegadores y aplicaciones. Hay dos puntos de cumplimiento, utilizados juntos en orden de prioridad.

  • ① Extensión del sistema de filtrado de contenido (RoamSwitchLinkFilter, 1.8.0+, preferida una vez aprobada). Una extensión del sistema NEFilterDataProvider: sin revisión de Apple (los proveedores de filtrado de contenido son de autoservicio, no tienen cola de aprobación). Inspecciona la resolución de nombre del flujo TCP saliente real después de. El nombre de destino proviene del nombre de host resuelto por el sistema operativo o, en su defecto, el TLS SNI analizado a partir de los primeros bytes del flujo, por lo que un navegador que realiza su propio DoH/DoT y se conecta a una IP básica aún está bloqueado. Nunca reescribe /etc/hosts y atribuye cada flujo a un proceso. QUIC (UDP/443) no tiene SNI legible, por lo que en el modo block se elimina, lo que obliga al navegador a recurrir a HTTP/2 sobre TCP. Necesita una aprobación única en Configuración del sistema.
  • ② /etc/hosts sumidero (alternativa, mientras la extensión no está aprobada/rechazada). El ayudante privilegiado escribe los dominios de destino en una sección administrada delimitada de /etc/hosts como 0.0.0.0 y vacía el caché. Con un límite de 60.000. Una vez que la extensión está activa, esta sección se elimina.
  • Tres modos. "Off" lo desactiva. "Solo advertir" pausa la conexión coincidente y pregunta al usuario (consulte Real warn a continuación). "Bloquear automáticamente sitios fraudulentos obvios (recomendado)" desaparece inmediatamente. El valor predeterminado es bloquear a partir de 1.7.2.
  • Real warn (1.8.0+, solo extensión). En un warn, la extensión del sistema retiene el flujo (tanto el nombre del sistema operativo como la ruta SNI) con .pause() y la aplicación genera una notificación "Permitir/Bloquear". En el momento en que el toque del usuario llega a allowlist.txt/extra.txt (la extensión sondea los pequeños archivos de estado cada 1,5 s), los flujos retenidos se reanudan (permiten) o se eliminan (bloquean). Si no hay respuesta dentro de los 25 s, se produce un error open (una advertencia es aconsejable). La decisión se almacena en caché por host (permita 5 min/bloquee 1 h), por lo que los demás flujos de la página y las visitas posteriores son instantáneas y se aplican. La notificación block no ha cambiado: "Bloqueado..." con un botón "Permitir una vez (5 min)".
  • Solo se bloquean los casos claros. Una lista en el feed de amenazas o un homógrafo Unicode de marca; todo lo demás (TLD de alto riesgo, suplantación de subdominio,...) es una advertencia. El motor de veredicto se comparte con la edición de Linux y no envía URL a ninguna parte.
  • Recuperando de un bloqueo incorrecto. Permitir un dominio desde la notificación o el menú (5 minutos o permanente). La lista de permitidos se resta cuando se regenera la sección.
  • Pro-gated. La aplicación (applyMode()) solo ocurre con una licencia Pro válida. Sin Pro, el modo se almacena pero /etc/hosts nunca se toca. La activación o caducidad de una licencia entra en vigor a mitad de sesión.
  • Feed y semilla empaquetada. La lista de bloqueo proviene de la fuente de amenazas firmada (§7, verificada con una clave dedicada a la fuente). Funciona en la semilla incluida de la aplicación (~60,000 entradas) incluso antes de la primera recuperación, y desactivar la "Actualización automática" significa que no hay tráfico saliente.

Bloqueo preventivo ARP/NDP de puerta de enlace: 1.7.5 y posteriores

Barra de menú → "Monitor de puerto y dispositivo" → "Fijar el ARP/NDP de la puerta de enlace en redes no confiables (preventivo)" (Pro, desactivado de forma predeterminada).

  • Cómo funciona. Al unirse a una red que no es de confianza, la MAC actual de la puerta de enlace IPv4, el enrutador predeterminado de IPv6 y los solucionadores de DNS en el enlace se recopilan de route / scutil --dns / arp -n / ndp -an, y el asistente fija cada uno como una entrada permanent con arp -s / ndp -s (confianza en el primer uso: la primera MAC observada es confiable). Las respuestas ARP/NDP falsificadas para esas IP se ignoran, por lo que no se puede configurar un ataque de intermediario.
  • Scope. Sólo se fijan esos tres tipos de entradas. Las redes confiables (abiertas) nunca se fijan. En cada cambio de red, se desbloquea una vez y se vuelve a fijar. Los solucionadores públicos fuera del enlace (8.8.8.8,…) no tienen entrada ARP en el enlace y se excluyen automáticamente.
  • arp -s es preventivo; el espacio de aire es posterior al hecho. Este pin existe por lo que una parodia no puede tener éxito; La detección de suplantación de identidad ARP (ARPSpoofContainmentManager) y el espacio de aire de emergencia existen para "cortar más rápido que un humano" una vez que se ve uno. Funcionan de forma independiente.
  • Persistence. El conjunto de pines se guarda en /Library/Application Support/RoamSwitch/gateway_arp_lock.json para que pueda desbloquearse mediante una reconexión de XPC o un reinicio auxiliar.

El túnel VPN (WireGuard / Tailscale) y su interruptor de apagado: 1.7.6 y posteriores, backend seleccionable en 1.8.0

Barra de menú → "Monitor de puerto y dispositivo" → "Túnel VPN" (Pro, desactivado de forma predeterminada). Esta es la defensa anti-MITM primary: no depende de la integridad de L2 (ARP/NDP). El backend se puede seleccionar (submenú → "Backend") entre "WireGuard (archivo de configuración)" y "Tailscale (Nodo de salida)". RoamSwitch no implementa ninguna criptografía; sólo el backend elegido está armado.

  • (A) Backend WireGuard. Impulsa el wireguard-tools de Homebrew (sin derecho a extensión de red de Apple). Importa tu propio .conf; el ayudante lo guarda en 0600. Kill-switch: pf block drop all más un pass quick solo para lo, la interfaz del túnel, el protocolo de enlace UDP a las IP del punto final anclado, DHCP e ICMP. Un AllowedIPs que no está completo genera una advertencia de túnel dividido.
  • (B) Backend de Tailscale (1.8.0+). Para usuarios que ya ejecutan Tailscale. RoamSwitch no ejecuta tailscale up / inicia sesión / lo instala; lee tailscale status --json y ejecuta tailscale set --exit-node=<node>. Se recomienda la compilación Standalone (descarga directa desde tailscale.com) con la integración de CLI activada (Ajustes → “CLI integration” → “Install Now”, que añade /usr/local/bin/tailscale).: la compilación de App Store (GUI) no se puede controlar desde fuera de la aplicación (sandbox); con él, eliges el nodo de salida en la aplicación Tailscale y RoamSwitch solo muestra el estado. Se requiere un nodo de salida (enruta todo el tráfico a través del túnel); se desarma automáticamente si se desconecta o es inaccesible.
  • ⚠️ No se recomienda la compilación de Homebrew (brew install tailscale). El tailscaled que distribuye Homebrew tiene un error conocido en macOS que puede impedir que reescriba correctamente la tabla de enrutamiento (tailscale/tailscale#21395, #21216): un nodo de salida puede mostrarse como «conectado» mientras el tráfico sigue filtrándose, o al desactivarlo puede perderse por completo la conexión a Internet del Mac. RoamSwitch verifica la tabla de enrutamiento real del sistema operativo y avisa cuando esto ocurre, pero usar la compilación Standalone anterior evita el problema desde el principio.
  • Reconfiguración de la red al desconectarse. El macOS independiente tailscaled no restaura limpiamente el enrutamiento + DNS cuando se borra un nodo de salida, por lo que RoamSwitch rebota los servicios de red activos (lo mismo que alternar Wi-Fi manualmente; solo servicios con una dirección IPv4, seguros para múltiples NIC e IP estática). El tráfico disminuye durante aproximadamente 5 a 10 segundos y el nivel de protección no disminuye durante ese período.
  • El interruptor de cierre de Tailscale está desactivado de forma predeterminada (optar por participar). El nodo de salida ya hace un túnel para todo; el pf block drop all (permite sólo CGNAT/MagicDNS/STUN/DERP/DHCP/ICMP/DNS) es para usuarios avanzados y es "resistente a fugas, no a prueba de fugas" (se permite DNS para que tailscaled sobreviva).
  • Automático/en caso de pérdida de licencia. Aparece en redes que no son de confianza, inactivo en las de confianza; se levanta cuando la licencia Pro caduca.

§6

Cuando se conecta a una red que no ha registrado, el interruptor de "nivel de protección" no utiliza pf. Simplemente cambia la configuración estándar del sistema operativo de una manera que se puede revertir más adelante.

Operaciones por nivel de protección
OperaciónImplementaciónprivilegiocomo se restaura
Firewall + modo oculto activado socketfilterfw --setblockall activado / --setstealthmode activado raíz (ayudante) off cuando regresas a una red segura
Detener SSH/SMB/Compartir pantalla lanzarctl descargar -w raíz (ayudante) Registra solo los que estaban ejecutando cuando se detuvieron y load -w al regresar (con SSH también acoplado a /usr/sbin/systemsetup -setremotelogin on para garantizar la restauración de macOS moderno)
Desactivar lanzamiento aéreo los valores predeterminados escriben com.apple.sharingd DiscoverableMode Usuario (la aplicación en sí) Guarda el valor anterior y lo vuelve a escribir al regresar.

Nada de esto es un nuevo mecanismo de bloqueo que agrega RoamSwitch: simplemente cambia la configuración del sistema operativo. si eliminas la aplicación, lo único que se detiene es el cambio dependiente de la red; el último sistema operativo Los ajustes que se aplicaron permanecen como están. No queda nada bloqueado, pero si quieres equivocarte Para estar seguro, vuelva a configurarlo en "Abrir" en una red confiable antes de desinstalarlo.

§7

Lo que queda en la Mac

Datos almacenados en el disco
DatosUbicaciónContenidos
token de licenciaLlavero
com.tetsuharu.RoamSwitch.license
Un token firmado por Ed25519. kSecAttrAccessibleAfterFirstUnlock
Configuración de la aplicación/protección activada y desactivadaSuite UserDefaults
com.tetsuharu.RoamSwitch
Registros de redes confiables, política de protección, listas de exclusión, etc.
estado pf/Biblioteca/Soporte de aplicaciones/RoamSwitch/La marca de tiempo del espacio de aire, JSON de los puertos protegidos, el estado del interruptor de interrupción de VPN, el archivo temporal para el conjunto de reglas que se aplica
Fuente de amenazas de Link Guard~/Biblioteca/Soporte de aplicaciones/RoamSwitch/threatfeed/feed.txtLa lista de dominios de phishing/estafa descargados (o la semilla incluida en la aplicación si aún no se ha obtenido). La versión del feed está en UserDefaults
Sección administrada Link Guard/etc/hostsUna sección delimitada delimitada por # BEGIN RoamSwitch link guard… # END, anulando los dominios bloqueados a 0.0.0.0. Eliminado cuando el modo está "Apagado" (§5)
Pasadores de bloqueo ARP/NDP/Biblioteca/Soporte de aplicaciones/RoamSwitch/gateway_arp_lock.jsonEl conjunto IP → MAC realizado permanent por el bloqueo preventivo (§5). Eliminado cuando se desbloquea
Configuración de WireGuard/Biblioteca/Soporte de aplicaciones/RoamSwitch/ (área de ayuda, 0600)El .conf que importó el usuario. El nombre de host del punto final también está guardado en UserDefaults (la aplicación lo resuelve)
UUID de reserva del dispositivoValores predeterminados de usuarioUn valor aleatorio, generado sólo cuando IOKit no devuelve un UUID (§9)
Registrosos.Logger/NSLogRegistro unificado. No se envía nada externamente

Tráfico que sale de la máquina (la lista completa)

No existe ningún código que recopile y envíe resultados de diagnóstico, información de puertos, URL o registros. No se incluye ningún SDK de análisis ni de informes de fallos. La única biblioteca externa es Sparkle (actualizaciones). Lo que sale a la red son las ocho rutas siguientes, más las funciones iniciadas por el usuario, las funciones desactivadas por defecto y la protección DNS contra amenazas descritas bajo la tabla, y nada más (la sexta solo si el usuario configura una VPN, la séptima solo si se activa el emparejamiento de RoamSwitch Sensor).

Conexiones salientes que realiza RoamSwitch
ConexiónDestinocuando sucedeque se envía
Activación/desactivación de licencia lafine.net
/api/v1/license/*
Solo cuando el usuario ingresa una clave de licencia o desactiva Pro Clave de licencia, hash del dispositivo, nombre del dispositivo (el «Nombre» de Ajustes del Sistema) y versión de la aplicación. Al liberarla, solo la clave y el hash. Stripe gestiona la información personal en la compra; la aplicación no la gestiona
Comprobación de actualización lafine.net
/updates/appcast.xml
Sparkle comprueba cada 24 horas (y también al iniciar si han pasado 24 horas o más desde la última comprobación). Siempre activo por defecto; no se envía información del sistema Una solicitud HTTP (una UA estándar y una versión). El elemento descargado se verifica mediante la firma EdDSA (§10)
Fuente de amenazas de Link Guard lafine.net
/updates/v1/{manifest, feed/.txt}
En la versión Pro, cuando la «Actualización automática» de la protección de enlaces está activada (valor por defecto): cada 24 horas (y al iniciar). Al desactivar la «Actualización automática» desaparece esta ruta Solo GET. Sin cadena de consulta, sin cookies, nada que identifique el equipo (el User-Agent estándar incluye el nombre y la versión de la aplicación y la versión de macOS). Un archivo estático firmado de solo recepción; el manifiesto y el cuerpo del feed se verifican con Ed25519. La clave de firma está dedicada al feed y es una clave distinta de la SUPublicEDKey de las actualizaciones de la aplicación (así una filtración se limita a «una lista de bloqueo incorrecta»)
Datos de actualización del cotejo de CVE de paquetes / de la verificación activa de vulnerabilidades lafine.net
/updates/v1/manifest
Al iniciar la aplicación (si han pasado 30 días o más desde la última obtención). La comprobación se realiza cada 24 horas y la obtención efectiva, una vez cada 30 días. Siempre (los datos de actualización del cotejo de CVE de paquetes no tienen interruptor de apagado, ya que son datos exclusivamente recibidos que solo se usan para un cotejo local de inventario; afecta a todos los usuarios, incluida la versión Free. El mapa de CVE de la verificación activa de vulnerabilidades solo se obtiene si se activa «Verificación activa de vulnerabilidades (verificación activa de accesibilidad)») Solo un GET. Sin cadena de consulta, sin cookies, nada que identifique el equipo (el User-Agent estándar incluye el nombre y la versión de la aplicación y la versión de macOS). Un archivo estático firmado y de solo recepción; tanto el manifiesto como el cuerpo del feed se verifican con Ed25519. Reutiliza la misma clave y el mismo manifiesto que el feed de amenazas de Link Guard, pero los feeds realmente obtenidos (mapas de CVE conocidos para Homebrew/npm/PyPI/crates.io/RubyGems/Packagist/Go/Maven, el mapa de CVE de la verificación activa de vulnerabilidades, y la lista de nombres de paquetes npm populares usada para la detección de typosquatting) son archivos independientes
Actualización de la definición del virus ClamAV Espejos oficiales ClamAV Cuando el usuario ha instalado ClamAV. En la versión Pro lanza freshclam automáticamente al iniciar, cada 24 horas y en las rondas autónomas; en la versión Free, solo cuando se pulsa «Actualizar definiciones de virus» en el menú La obtención estándar de definiciones de ClamAV. No contiene información procedente de RoamSwitch (puede incluir la información de identificación estándar del propio ClamAV)
Túnel VPN (§5, WireGuard / Tailscale) El punto final WireGuard que configuró el usuario, o el plano de control de Tailscale (Tailscale, Inc.) Solo cuando el usuario ha configurado el túnel VPN (Pro, desactivado de forma predeterminada) y se une a una red que no es de confianza. Con WireGuard, el nombre de host del punto final se resuelve a través de DNS una vez antes de que aparezca el túnel. Con Tailscale, se trata de tráfico del plano de control gestionado por el tailscaled que el propio usuario ya instaló e inició sesión — RoamSwitch solo lee tailscale status --json y ejecuta tailscale set --exit-node= Con WireGuard: el protocolo de enlace WireGuard (UDP) y el tráfico dentro del túnel. El destino es el servidor VPN del propio usuario y el contenido es el tráfico del propio usuario. Con Tailscale: tráfico entre la propia cuenta de Tailscale del usuario y el plano de control de Tailscale, Inc. (registro del dispositivo, retransmisión DERP, etc., regido por la propia política de privacidad de Tailscale). En ambos casos, RoamSwitch no agrega ningún identificador ni datos de diagnóstico
Emparejamiento y enlace de auditoría de RoamSwitch Sensor RoamSwitch Sensor en la misma LAN (hardware configurado por el propio usuario, que funciona con una IP fija) Solo cuando está activado «Emparejamiento de RoamSwitch Sensor» (Pro, desactivado de forma predeterminada) y usted introduce la dirección IP y el código de emparejamiento de Sensor para emparejarse (un intercambio puntual). Tras el emparejamiento, vuelve a conectarse a esa misma IP fija solo cuando pulsa «Solicitar auditoría al Sensor», y durante la recuperación automática del resultado (a partir de 5 minutos después de la solicitud, luego cada 5 minutos, hasta 5 intentos) El emparejamiento envía el nombre de host y la clave pública Ed25519 de este endpoint, y recibe la clave pública emitida por Sensor. Las solicitudes de auditoría y la recuperación de resultados se autentican con una firma Ed25519; solo se envían solicitudes firmadas. Los resultados de diagnóstico e información de puertos solo se reciben como respuesta de Sensor; nunca se envía contenido de archivos. El único destino es la dirección IP fija del Sensor configurado por el propio usuario; nunca llega a ningún servidor externo, incluido lafine.net. La confianza mutua solo se establece mediante el intercambio explícito del código de emparejamiento
página de pago Pago a rayas Sólo cuando el usuario presiona el botón comprar (se abre en el navegador) — (una navegación del navegador)
Alcance de «Zero Telemetry»

Aquí, «Zero Telemetry» significa que no hay telemetría que recopile y envíe datos de uso ni resultados de diagnóstico. No significa que no haya ningún tráfico de red. Las ocho rutas de la tabla anterior y las funciones descritas bajo la tabla existen realmente. Pero cada una de ellas la inicia el usuario, es una obtención de solo recepción verificada por firma o es una comunicación con un servicio externo elegido por el usuario (los espejos de distribución de ClamAV, un proveedor de DNS protector, la URL que se comprueba, etc.), y los resultados de diagnóstico, los puertos y el contenido de los archivos del Mac nunca se envían al desarrollador.

La fuente de amenazas de Link Guard (fila 3) agrega "extrae actualizaciones" además de "no envía nada" defensa. Los dos se mantienen separados; el documento técnico de Linux §1.1 también divide "Zero Telemetría" de "actualizaciones de solo recepción". Desactive la "Actualización automática" y Link Guard se ejecutará en el paquete datos (~60,000 dominios de phishing/estafa) más detección de homógrafos fuera de línea, y esta ruta no ocurre.

El túnel VPN (fila 6) solo ocurre si el usuario configura su propio servidor WireGuard, o elige el backend de Tailscale y ya inició sesión en tailscaled con su propia cuenta de Tailscale; el destino y los contenidos están bajo el control del usuario. RoamSwitch solo abre el túnel, mantiene el interruptor de apagado, o lee el estado de Tailscale para cambiar el nodo de salida — no agrega ningún identificador ni datos de uso. Sin una VPN configurada, este camino no existe.

El emparejamiento de RoamSwitch Sensor (fila 7) es una función exclusiva de Pro y desactivada de forma predeterminada; esta vía no existe a menos que la active. El propio emparejamiento solo se establece mediante una acción explícita: introducir el código de emparejamiento emitido por el operador del Sensor junto con la dirección IP fija del Sensor. Toda comunicación posterior (solicitudes de auditoría, recuperación de resultados) también se dirige a esa misma IP fija propiedad del usuario y se autentica con una firma Ed25519. Nunca llega a ningún servidor externo, incluido lafine.net. Los resultados de diagnóstico o el contenido de archivos nunca se envían, salvo como respuesta del propio Sensor.

Hay comunicaciones que no figuran en la tabla. Ninguna envía resultados de diagnóstico ni contenido de archivos al desarrollador. (1) La protección DNS contra amenazas de la versión Pro cambia el DNS del sistema a Quad9, Cloudflare, AdGuard o CleanBrowsing (ámbito por defecto: «fuera de casa / Wi-Fi no confiable»). Mientras está aplicada, las consultas de resolución de nombres se envían a ese proveedor. (2) La verificación de firmas npm de la versión Pro pide confirmación en cada ejecución y después consulta el registro de npm. (3) El diagnóstico activo de vulnerabilidades (verificación activa de accesibilidad) se limita a este propio Mac (127.0.0.1). (4) La solicitud HEAD que la hoja «Diagnóstico de seguridad de enlaces» envía directamente a la URL de destino se describe en el párrafo siguiente.

La hoja de "verificación de seguridad de enlaces" en la aplicación envía una solicitud HEAD a la URL de destino para ver dónde llega una URL acortada (las siguientes redirecciones a direcciones privadas o locales son detenidas por el v1.4.5 mitigación de la SSRF). El MCP audit_url_safety, por el contrario, es un análisis fuera de línea. que se completa en el acto y no envía la URL a ninguna parte (§8).

Medido (2026-08-29) — Veredicto: PASS

No es solo una afirmación. El 2026-08-29 se auditó una instalación 1.4.7 en ejecución con tcpdump + atribución por proceso (nettop / lsof / captura pktap filtrada) + LuLu, durante unas 2 horas, con el nivel de seguridad fijado en Bloqueo máximo y la comprobación de appcast forzada. Resultado (versión 1.4.7): ningún flujo saliente atribuido a RoamSwitch, RoamSwitchHelper o RoamSwitchMCPServer aparte de la comprobación de appcast hacia lafine.net; los únicos sockets del servidor MCP fueron hacia localhost; los volcados de entitlements están vacíos. Esta medición corresponde solo a la versión 1.4.7. Las rutas añadidas desde entonces figuran en la tabla anterior y está prevista una nueva medición de la versión actual.

Artículo completo y un script que cualquiera puede ejecutar para reproducirlo: auditoría/RESULTADOS-2026-08-29.md

Esta medición corresponde a la versión 1.4.7. Desde entonces se han añadido estas rutas: el feed de amenazas de la protección de enlaces (1.7.2, fila 3 de la tabla), el túnel VPN (1.7.6, fila 6), los datos de actualización del cotejo de CVE de paquetes (fila 4) y el emparejamiento de Sensor (fila 7). Al auditar la versión actual verá GET de solo recepción hacia lafine.net (el appcast, el feed de amenazas y los datos de actualización); si hay una VPN configurada, también verá UDP hacia el propio servidor WireGuard del usuario (destino y contenido bajo su control). El feed de amenazas desaparece con «Actualización automática: desactivada» y el tráfico VPN sin VPN configurada (el appcast y los datos de actualización del cotejo de CVE de paquetes no tienen interruptor de apagado).

§8

RoamSwitchMCPServer es una herramienta de línea de comandos independiente incluida en RoamSwitch.app/Contents/MacOS/. Un cliente MCP como Claude Desktop o Claude Code lo inicia como un subproceso y habla con él a través de stdio (JSON-RPC delimitado por nueva línea). 2.0). El SDK oficial no se basaría en el SDK de macOS de esta máquina, por lo que es implementado a mano sobre JSONSerialization de Foundation.

Lo que limita el diseño

  • Es de solo lectura. Simplemente no existe una API para cambiar el nivel de seguridad, aislar un puerto o expulsar un dispositivo. Esto no es algo que se olvide en la v1: se omite deliberadamente. Permitir que un código externo (aquí, un LLM) reescriba el estado de protección de una herramienta de seguridad rompería la confianza de todos los usuarios.
  • No abre ningún socket de escucha. No registra ningún servicio ni escucha. Lee una línea de stdin, devuelve una línea por stdout y después el cliente finaliza el proceso.
  • No envía telemetría. Solo run_npm_audit_signatures (registro de npm), que es opcional y exclusiva de Pro en Mac, se comunica con un host externo. run_active_vuln_scan (desactivada por defecto) solo sondea 127.0.0.1, y en Mac get_exposed_ports se limita a enviar un HTTP GET por cada puerto expuesto a 127.0.0.1 para verificar los encabezados. Para averiguar la dirección MAC de la puerta de enlace predeterminada, las herramientas de diagnóstico pueden enviar un ping ICMP, normalmente uno (unos pocos si no se resuelve la MAC), a la puerta de enlace de la LAN.
  • Lee la configuración de un dominio diferente. UserDefaults(suiteName: "com.tetsuharu.RoamSwitch") lee el dominio de la aplicación explícitamente (su propio dominio de ID de paquete está vacío). Sólo lee; no escribe.

Las herramientas que expone

Las herramientas principales que devuelve tools/list y los datos que devuelven (la lista completa está en Configuración de MCP)
Herramientalo que devuelveTráfico
obtener_informe_de_seguridadUna verificación de 18 puntos (FileVault / SIP / Gatekeeper / actualización automática / XProtect / cortafuegos / cifrado Wi-Fi / ARP / fijación ARP de la puerta de enlace / auditoría de configuración SSH / auditoría sudo NOPASSWD / puertos expuestos / configuración de protecciones, entre otros) con una puntuación y recomendaciones de solución por punto. Cada punto también incluye los ID de control correspondientes de NIST CSF 2.0 y CIS Controls v8 (desde 1.10.0).Sólo locales
verify_security_findingsReevalú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.Sólo locales
audit_mcp_configsLee 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.Ninguno
audit_agent_setupLee 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_portsLista de puertos TCP en escucha. Para cada puerto expuesto más allá de localhost, se compara con una base de datos de servicios peligrosos conocidos y se envía un único HTTP GET a 127.0.0.1:port (local, cerrado) para verificar CORS y los encabezados.Sólo la sonda a 127.0.0.1
get_guard_statusEl estado de encendido/apagado de las protecciones de respuesta automática Pro (anomalía de puerto/ARP/USB/Bluetooth/descarga Web+Mail/protección contra amenazas DNS), el nivel de protección actual y el estado de la red confiableSólo locales
auditoría_url_seguridadUn juicio de una URL para phishing/homógrafo (suplantación de Unicode)/suplantación de subdominio de marca/TLD de alto riesgo/HTTP de texto sin formato. Es sincrónico y completamente fuera de línea (analyzeURL; no sigue redirecciones)Ninguno
get_app_helpUna búsqueda de texto completo de la base de conocimientos incluida (especificaciones de funciones/configuraciones/solución de problemas/explicaciones de mensajes de notificación)Ninguno
audit_secretsDetecta claves de API y claves privadas expuestas en texto, un archivo o un árbol de directorios (los hallazgos se enmascaran en la salida).Ninguno
audit_security_logsAgrega los registros de seguridad recientes (Mac: fallos de autenticación sudo, intentos de fuerza bruta SSH, bloqueos de Gatekeeper, detecciones de XProtect, etc. / Linux: fallos de autenticación sudo, fuerza bruta SSH, bloqueos del cortafuegos, denegaciones de AppArmor, detecciones de ClamAV, etc.), enmascara automáticamente información sensible como claves API y tokens, y además devuelve los resultados de la detección de patrones nuevos (mediante plantillas de log) y de anomalías de frecuencia (picos estadísticos). En Linux, si no se pudo leer el journal (por ejemplo, por tiempo agotado), los ceros del recuento no significan que no haya pasado nada; journalReadComplete pasa a false y journalError indica el motivo.Sólo locales
run_active_vuln_scanVerificació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.Solo 127.0.0.1 (desactivado por defecto)
run_package_cve_scanCompara 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.Ninguno
run_package_cve_scan_languagesCompara 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.Ninguno
get_quarantine_statusDevuelve el contenido del baúl de cuarentena: ruta original, nombre de la amenaza detectada, fecha de cuarentena y tamaño.Sólo locales
get_canary_statusDevuelve el estado de los archivos señuelo antiransomware (canary) y hasta los 50 incidentes detectados más recientes.Sólo locales
get_ransomware_recovery_snapshotsDevuelve las instantáneas APFS de recuperación (previas al daño, de detección, manuales), la recomendada para recuperar, el estado del modo de retención y el intervalo de instantáneas. Una instantánea de detección puede contener ya archivos cifrados. Solo Pro.Sólo locales
search_exec_eventsBusca en el registro de ejecución de procesos (eslogger) por ruta, argumentos o ID de firma, y devuelve el estado del registrador y si el registro es legible. Solo Pro.Sólo locales
get_process_treeReconstruye los ancestros y descendientes de un proceso a partir del registro. Solo Pro.Sólo locales
get_notification_historyDevuelve 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.Sólo locales
get_port_anomaly_incidentsDevuelve 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.Sólo locales
get_ssh_guard_incidentsDevuelve 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.Sólo locales
get_runtime_threat_statusIndica si este Mac está aislado (Air-Gap) por una detección de malware de XProtect, junto con el incidente que lo activó. Es lo primero que conviene consultar durante un Air-Gap activo.Sólo locales
get_ransomware_entropy_guard_statusDevuelve si la detección genérica de ransomware por entropía está activada y sus incidentes recientes de ráfaga de entropía.Sólo locales
get_honeytoken_statusDevuelve si la protección de honeytokens de credenciales está activada y sus eventos recientes de detección de acceso.Sólo locales
get_browser_credential_watch_statusDevuelve si la supervisión de acceso a credenciales del navegador está activada y sus eventos recientes de detección de acceso.Sólo locales
get_forensic_evidence_bundlesDevuelve la lista de paquetes de evidencia forense (lista de procesos, conexiones de red, archivos modificados, etc.) guardados cuando se activó un Air-Gap de emergencia.Sólo locales
get_vulnerability_scan_historyDevuelve el último resultado registrado y los días transcurridos (pass_age) para cada sonda que run_active_vuln_scan ha registrado.Sólo locales

El campo instructions de la respuesta initialize también indica claramente: «Cannot change security level, isolate ports, or eject devices», comunicando así el límite de capacidades al LLM del lado del cliente. Los recursos MCP (roamswitch://docs/*) son también documentos Markdown de solo lectura. 1.10.0 añade cuatro documentos de triaje en formato agentskills.io (roamswitch://skills/*, solo en inglés, por diseño).

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.

Punto clave: el código fuente es público

El código fuente de este servidor y de la lógica de detección que utiliza (supervisión de ARP, escaneo de puertos, auditoría de puertos, la comprobación de salud de 18 puntos, análisis de seguridad de URL) está publicado en github.com/lafine1211/roamswitch-mcp (MIT, un espejo del código publicado, etiquetado en cada versión). Puede comprobar directamente en el código que es de solo lectura, qué le pasa al LLM y que no envía nada. No incluye el asistente con privilegios, el control de pf, las protecciones que actúan ni las licencias; eso permanece en el repositorio de la aplicación.

Las pruebas también lo incluyen: pruebas unitarias reflejadas, pruebas de entrada adversaria y confusión de mutaciones. ejecutado por swift test y verificado en CI. Fuzzing descubrió un accidente sin vigilancia (JSONSerialization desbordamiento de pila en un objeto JSON profundamente anidado); se arregla con una verificación de la profundidad de anidamiento antes del analizador y se registra en SECURITY_TESTING.md.

§9

la ficha

  • Utiliza Ed25519 (firmas Curve25519). La clave pública está integrada en la aplicación (LicenseVerifier.embeddedPublicKeyBase64). La clave privada correspondiente existe solo en el backend de la licencia (una variable de entorno de Firebase Functions) y no está en el repositorio.
  • Los datos firmados son JSON canónicos. La firma se crea y verifica sobre los bytes exactos producidos al codificar LicensePayload (clave de licencia, nivel, hash de dispositivo, emitido en, vencimiento, recuento de puestos) con .sortedKeys y .withoutEscapingSlashes de JSONEncoder.
  • Está diseñado para fallar en el cierre. Si falta la clave incrustada o está mal formada, o si no se puede producir la firma o el JSON canónico, el resultado no se "verifica": devuelve invalidSignature.

Enlace de dispositivo

device_hash = SHA-256( "RoamSwitch-LifetimeSalt-v1" : lowercase(IOPlatformUUID) )

El UUID de hardware sin formato no se envía al servidor. En el raro caso de que IOKit no devuelva un UUID, recurre a un UUID aleatorio guardado en UserDefaults. En el momento de la verificación, si el El device_hash del token no coincide con el hash del dispositivo actual, el resultado es deviceMismatch.

Funciona sin conexión

Punto clave: funciona sin servidor

Todo lo que validateSavedLicense() hace al inicio es leer el token del llavero y verifíquelo localmente con la clave pública incorporada. No se conecta a la red. si el Si el servidor de licencias se cierra, las funciones Pro siguen funcionando en una Mac que ya está activada. Se contacta al servidor sólo para una nueva activación y para una desactivación explícita. el Lo mejor es enviar un aviso de desactivación al servidor; incluso si falla, la desactivación local siempre completa.

El valor predeterminado es una compra única (de por vida); expires_at se marca sólo cuando is_lifetime es falso. El número de asientos se expresa por nivel: 2 para Pro personal, 5 para Equipo.

§10

Firma y notarización (scripts/release.sh)

  1. Ejecute xcodebuild archive (versión, firma manual, aplicación de ID de desarrollador).
  2. Exportar con -exportArchive como method: developer-id.
  3. Después de notarytool submit --wait, ejecute stapler staple en la aplicación.
  4. Verifique con spctl -a -t exec -vv.
  5. Vuelva a comprimir después de grapar para producir el artefacto de actualización de Sparkle (para que se incluya el ticket de notarización y se ejecute sin conexión sin una advertencia de Gatekeeper).
  6. Construya el DMG, regístrelo ante notario y engrápelo también, y verifique con stapler validate.

Actualizaciones (Sparkle 2.9.6)

Actualizar la configuración en Info.plist
claveValor
SUFeedURLhttps://lafine.net/updates/appcast.xml
SUPublicEDKeyCNxzwijMzMCJzliId76Yl88S/9np6t/xg/zQ9YbYzHs=
SUEnableAutomaticCheckscierto
SUSintervalodecomprobaciónprogramada86400

Antes de aplicar una actualización, se verifica la firma EdDSA que figura en el appcast contra el SUPublicEDKey incrustado en la aplicación. La clave de firma privada existe sólo en el entorno de construcción. El appcast se entrega a través de HTTPS. Las actualizaciones delta se verifican con firmas el de la misma manera.

Punto clave: el appcast está diseñado para ser público

El hecho de que la URL appcast.xml sea pública no es una debilidad en sí misma. Su contenido son solo números de versión, notas de la versión, URL de descarga, tamaños de archivos y la firma EdDSA de cada construcción, nada secreto. El ancla de la confianza no es "si el appcast es auténtico en tránsito", sino verificar la firma del artefacto con la clave pública integrada en la aplicación. Un atacante que puede reemplazar completamente el appcast (MITM, secuestro de DNS, comprometer el servidor web) aún no se puede enviar una actualización maliciosa sin la clave de firma. Gatekeeper (ID de desarrollador y certificación notarial) es un segunda puerta.

Quedan dos riesgos. Una es que las actualizaciones no llegan porque el host no funciona o el appcast no funciona. (No ocurre ninguna instalación incorrecta; simplemente no se actualiza). El otro es un ataque de congelación que retiene deliberadamente una actualización de seguridad. Sparkle 2.x rechaza degradaciones y repeticiones marcando pedido de versiones, pero una defensa completa contra la congelación necesita un servidor de actualización dedicado con caducidad. Eso está en nuestra lista para abordar.

§11

Qué debe manejar RoamSwitch

  • Sondeos y ataques de un atacante en la misma LAN o de un dispositivo IoT comprometido. Responde con sigilo, auditoría de puertos expuestos y aislamiento del exterior.
  • Exposición en una red en la que no confías. Automáticamente deja de compartir servicios y AirDrop.
  • Ataques de intermediario (suplantación de ARP/NDP). A partir de la versión 1.7.6, la defensa es multicapa: (1) el túnel VPN + kill-switch (§5, la defensa principal, que no depende de la integridad de L2), (2) la fijación preventiva ARP/NDP de la puerta de enlace en redes no confiables (§5) y (3) la detección de suplantación con un air-gap de emergencia (a posteriori). El túnel VPN y la fijación de la puerta de enlace son funciones Pro y vienen desactivados por defecto. La detección de suplantación viene activada por defecto y el air-gap de emergencia se activa por defecto al activar Pro.
  • Encontrar servidores de desarrollo y bases de datos (Redis, MongoDB, Elasticsearch, etc.) expuestos en 0.0.0.0 sin autenticación y bloquearlos desde el exterior.
  • Detectar tempranamente actividades de cifrado no autorizadas similares a ransomware y detener todo el tráfico (no depende de firmas).
  • BadUSB y protector de aprobación de teclado físico (USBKeyboardGuard, CGEventTap + IOKit) que intercepta y elimina las pulsaciones de teclas de teclados/cables USB no aprobados (Rubber Ducky, O.MG Cable, etc.) para evitar ataques automatizados de inyección de comandos.
  • Un mensaje de aprobación para almacenamiento USB desconocido (una unidad no reconocida se mantiene como de solo lectura en lugar de expulsarse inmediatamente) y un escaneo automático de ClamAV en el almacenamiento adjunto (opcional).
  • Detecta claves API sensibles (OpenAI, Anthropic, GitHub, AWS, etc.) en el portapapeles de forma totalmente local, y evita un pegado accidental en un formulario web o un chat de IA antes de que ocurra.
  • Detecta y advierte sobre archivos de modelos de IA en formato Pickle peligrosos (riesgo de ejecución de código arbitrario) descargados desde Hugging Face o la web.
  • La firma de prueba EICAR, estándar del sector, y reverse shells clásicas de una sola línea bien documentadas dentro de archivos descargados (StaticSignatureScanner, desde 1.8.7, funciona incluso sin ClamAV instalado).
  • Un LaunchAgent/LaunchDaemon recién registrado que invoca directamente un intérprete de scripts sin firmar (PersistenceMonitorGuard, desde 1.8.7).
  • Ataques de tipo «ClickFix», en los que una falsa pantalla de advertencia induce a ejecutar usted mismo un comando en Terminal, detectados mediante patrones de comandos conocidos como maliciosos en el historial del shell, lo que activa un air-gap de emergencia (ClickFixGuard, desde 1.8.7, Pro, desactivado por defecto).
  • Detecta y notifica el instante en que un contenedor se inicia con una configuración de Docker con riesgo de fuga de contenedor, como el modo --privileged o un montaje enlazado de /var/run/docker.sock (DockerEventGuard, desde 1.8.9, Pro, desactivado de forma predeterminada, solo notificación).
  • Auditoría de fugas de secretos y claves API que ahora cubre no solo el texto pegado, sino también el análisis recursivo a nivel de carpeta (SecretLeakAuditor, análisis de carpetas añadido en 1.8.9, totalmente en el dispositivo).

Lo que hemos decidido no hacer

  • No es un sustituto de un antivirus. ClamAV y XProtect se usan como auxiliares; RoamSwitch por sí solo no es un detector de malware de propósito general.
  • Hemos decidido no solicitar el entitlement de EndpointSecurity. Las tasas de aprobación para una cuenta individual de Apple Developer son bajas, y cualquier función que realmente necesite un bloqueo previo a la ejecución (por ejemplo, ES_EVENT_TYPE_AUTH_EXEC) se trata como un objetivo cerrado y descartado mientras esto siga siendo así. En lugar de detener la propia ejecución, hemos implementado, sin ese entitlement, lo que resulta práctico como detección posterior al hecho: detección por firmas estáticas, supervisión de inicio automático y defensa ClickFix.
  • No es una garantía. Es una capa en una pila de defensa en profundidad, no algo que "previene completamente el ransomware". El texto de marketing también se revisa sobre esta premisa.
  • No puede proteger una raíz o un kernel ya comprometido. Si un atacante ya tiene raíz, también puede eliminar las reglas pf del asistente.
  • No restaura la integridad L2 en sí. El pin ARP/NDP preventivo es confiable en el primer uso: si un atacante ya está instalado antes de conectarse, puede fijar un MAC falsificado. El túnel VPN (§5) es la respuesta cuando no quiere hacer esa suposición: incluso con L2 envenenada, el contenido está cifrado y el interruptor de apagado evita que se filtre el texto sin formato. No sustituye al espionaje DHCP empresarial ni a la inspección ARP dinámica.
  • No proporciona un servidor VPN. La función de túnel utiliza una configuración WireGuard que proporciona el usuario; RoamSwitch no se convierte en proveedor de VPN.

Superficie de ataque que añade la instalación de RoamSwitch

Superficie de ataque agregada y cómo se contiene
Superficie de ataquecomo esta contenido
Un LaunchDaemon que se ejecuta como root y su servicio mach (com.tetsuharu.RoamSwitch.Helper) La superficie de operación está fijada a HelperProtocol (la tabla §3). No existe un canal de comando arbitrario. Las conexiones están autorizadas mediante un requisito de firma de código, utilizando audit_token.
Si el propio RoamSwitch.app está comprometido, todas las operaciones del asistente pasan al atacante. Hardened Runtime está activado y la aplicación no recibe privilegios innecesarios. El envío por red se limita a las rutas de la tabla del §7 (y a las funciones iniciadas por el usuario o desactivadas por defecto descritas bajo esa tabla). No se declara ningún entitlement de envío por red, pero como la aplicación no está en el sandbox, eso por sí solo no impide la comunicación (mediciones al final del §7). Planeamos que un tercero revise esto.
Las rutas binarias del sistema que genera el ayudante Las rutas absolutas como /sbin/pfctl se especifican directamente, sin dependencia de PATH. Los argumentos también están codificados (aparte de los números de puerto, las cadenas DNS, los pares ARP IP/MAC y la IP del punto final de VPN, todos los cuales están validados por formato).
Link Guard reescritura /etc/hosts (setLinkGuardSinkhole) Las escrituras se limitan a una sección administrada delimitada; Las líneas exteriores se conservan palabra por palabra. Los dominios se normalizan y validan, las IP y la basura se eliminan, la lista tiene un límite de 60 000 y el archivo se escribe mediante un archivo temporal + reemplazo atómico. Solo analiza casos claros (listas de fuentes de amenazas u homógrafos de marcas), decididos por un motor de veredicto exclusivo local. La lista de bloqueo en sí proviene de la fuente de amenazas verificada por firma (clave dedicada a la fuente).
Recuperación de información de amenazas de Link Guard (un GET diario de solo recepción) Una recuperación de archivo estático sin cadena de consulta ni identificador por dispositivo (solo el User-Agent estándar). Tanto el manifiesto como el cuerpo del feed están verificados según Ed25519 y una recuperación que no supera la verificación se descarta (no hay respaldo a datos sin firmar). "Actualización automática: desactivada" elimina la ruta por completo.
Pin preventivo ARP/NDP (lockGatewayARP) Sólo se fijan las entradas de la caché vecina para "la puerta de enlace, el enrutador IPv6, el DNS en el enlace". Los pares IP/MAC pasados ​​se validan en formato y se concilian con el conjunto de solicitudes (nunca agrega entradas por sí solo). Se ejecuta sólo en redes que no son de confianza; Pro, desactivado de forma predeterminada. El límite de confianza en el primer uso se establece en el §11 "Lo que hemos decidido no hacer".
Túnel VPN (wireGuardImport/Up/…, Homebrew wireguard-tools) El ayudante almacena el .conf en 0600. La aplicación resuelve la IP del punto final y la pasa al asistente (el asistente nunca resuelve un nombre de host arbitrario). El destino y el contenido del túnel están bajo el control del usuario. La función está deshabilitada si wireguard-tools no está instalado. Pro, desactivado de forma predeterminada. El interruptor de emergencia pasa por PFRulesetCoordinator (§4).
El servidor MCP pasa el estado del sistema a un LLM (un diputado confundido) Es de sólo lectura, sin API de escritura implementada. Las comprobaciones de URL están fuera de línea. Se hace referencia al dominio de configuración como de solo lectura.
Secuestro de la ruta de actualización Verificación de firma EdDSA (SUPublicEDKey), más un ticket de notarización incluido. La transmisión de aplicaciones se realiza a través de HTTPS.

Apéndice A

Todo lo indicado en este documento se puede verificar con el artefacto distribuido con los siguientes comandos.

Firma y notarización

# Firma Developer ID e ID de equipo
codesign -dvvv /Applications/RoamSwitch.app 2>&1 | grep -E 'Authority|TeamIdentifier|flags'

# Si el ticket de notarización está grapado (staple)
stapler validate /Applications/RoamSwitch.app
spctl -a -t exec -vvv /Applications/RoamSwitch.app

# Firma del helper incluido / servidor MCP
codesign -dvvv /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchHelper
codesign -dvvv /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchMCPServer

Entitlements (comprobación de que no se declara ningún entitlement de envío por red; como la aplicación no está en el sandbox, eso por sí solo no impide la comunicación)

codesign -d --entitlements :- /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchHelper
codesign -d --entitlements :- /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchMCPServer
# → Un diccionario de entitlements vacío. Sin claves app-sandbox / network client.

Midiendo el tráfico

# Ejecute tcpdump en paralelo para confirmar que, en el uso normal, no hay tráfico fuera de las rutas de la tabla
sudo tcpdump -i any -n 'host not 127.0.0.1' and 'not port 53'
# Sin tráfico salvo las rutas de la tabla (activación de licencia, comprobaciones de actualización, datos de actualización firmados, actualizaciones de ClamAV, etc.)

Para una verificación automatizada más estricta con atribución por proceso, consulte rs-zerotel-audit.sh en roamswitch-support/audit/

Arquitectura de defensa y pruebas de penetración (5 límites de defensa)

# Verifica automáticamente en un solo lote el límite de autorización XPC, la prioridad Air-Gap de pf, la detección de exposición de puertos, el modo de solo lectura de MCP y la monitorización ARP
git clone https://github.com/lafine1211/roamswitch-support
cd roamswitch-support/audit
./rs-defense-audit.sh all

Pruebas de penetración en vivo y auditoría de defensa multicapa desde una máquina virtual macOS: RESULTS-DEFENSE-2026-08-30

Verificación de protección contra amenazas DNS (malware Quad9 y bloqueo C2)

# Consultar el dominio de prueba oficial de Quad9 para verificar el bloqueo de amenazas
nslookup test.dns9.quad9.net
# → Debe devolver ** server can't find test.dns9.quad9.net: NXDOMAIN, es decir, la resolución de nombres está bloqueada

Verificación de Web & Mail Download Guard (detección en tiempo real con ClamAV)

# Crear la cadena de prueba antivirus estándar e inofensiva (EICAR) en Downloads
echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > ~/Downloads/eicar_test.com
# → La detección de FSEvents activa un escaneo inmediato de ClamAV; no se muestra ninguna notificación y "firma de prueba EICAR detectada (inofensiva)"
#   queda registrada en el historial de notificaciones. Dado que EICAR es un archivo de prueba estándar de la industria y no una amenaza real,
#   no se pone en cuarentena ni se bloquea, y el archivo permanece en su lugar (solo una muestra real de malware se traslada al gestor de cuarentena).

Verificación de la detección por firmas estáticas (sin necesidad de EndpointSecurity)

## Una reverse shell clásica de una sola línea tomada de referencias públicas de seguridad ofensiva (inofensiva, nunca se ejecuta)
echo 'bash -i >& /dev/tcp/127.0.0.1/4444 0>&1' > ~/Downloads/rs_test.sh
## → Incluso sin ClamAV instalado, aparece una notificación «Archivo descargado peligroso puesto en cuarentena», y el archivo se
##   traslada de inmediato a la carpeta Quarantine (sin depender de la base de firmas de ClamAV)

Verificación de la nueva supervisión de inicio automático (PersistenceMonitorGuard)

# Crear un LaunchAgent inofensivo que invoca directamente un intérprete de scripts sin firmar (nunca se ejecuta realmente)
cat > ~/Library/LaunchAgents/com.example.selfcheck.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>com.example.selfcheck</string>
  <key>ProgramArguments</key><array><string>/bin/bash</string><string>-c</string><string>echo hi</string></array>
</dict></plist>
EOF
# → En pocos segundos debería aparecer una notificación «Se detectó un nuevo registro de inicio automático». Elimínelo después:
rm ~/Library/LaunchAgents/com.example.selfcheck.plist

Link Guard (bloqueo de conexiones de phishing)

# Si la sección administrada está presente (2 líneas BEGIN/END en modo Pro + predeterminado)
sudo grep -c 'RoamSwitch link guard' /etc/hosts

# El contenido de la sección y el número de sinkholes
sudo sed -n '/BEGIN RoamSwitch link guard/,/END RoamSwitch link guard/p' /etc/hosts | head -4
sudo sed -n '/BEGIN RoamSwitch link guard/,/END RoamSwitch link guard/p' /etc/hosts | grep -c '^0\.0\.0\.0'

# Si realmente se descarta (verificado con una entrada de la sección; inofensivo)
D=$(sudo sed -n '/BEGIN RoamSwitch/,/END RoamSwitch/p' /etc/hosts | awk '/^0\.0\.0\.0/{print $2; exit}')
dscacheutil -q host -a name "$D"   # → ip_address: 0.0.0.0 (resolución de nombres bloqueada)

# Confirme también que la sección anterior desaparece mediante Menú → "Protección de enlaces" → "Desactivado"

La fuente de amenazas de solo recepción (verificada por firma)

# Feed y manifiesto públicos (cualquiera puede obtenerlos y verificarlos)
curl -s https://lafine.net/updates/v1/manifest        # version/generated/threatfeed{...}
curl -sI https://lafine.net/updates/v1/manifest.sig   # → text/plain
# Solo se envía un GET sin cadena de consulta, cookies ni identificador por dispositivo (solo el User-Agent estándar). Se puede verificar ejecutando tcpdump en paralelo.

Bloqueo preventivo ARP/NDP (§5, 1.7.5+)

# Conéctese a una red no confiable con el bloqueo preventivo activado → compruebe las entradas permanent
arp -an | grep -i permanent          # La puerta de enlace y otras IP se listan como (permanent)
ndp -an | grep -i 'P '               # Lado IPv6 (P = permanent)
sudo cat "/Library/Application Support/RoamSwitch/gateway_arp_lock.json"  # El conjunto de asignaciones IP→MAC actualmente fijadas
# Libere el bloqueo desde el menú → lo anterior debería desaparecer

Túnel VPN + desconexión automática (§5, 1.7.6+)

# Se asume que wireguard-tools está instalado (vía Homebrew)
brew list wireguard-tools >/dev/null && echo "wireguard-tools: OK"

# Active la VPN en una red no confiable → antes de que el túnel se establezca, pf está en estado de interruptor de corte
sudo pfctl -sr | grep -E 'block drop all|pass .*(utun|udp)'   # block drop all + un conjunto limitado de reglas pass quick

# Después de establecer el túnel (wg-quick up ya ejecutado)
wg show                              # handshake / transfer está activo
route -n get default | grep interface  # → utunN (la ruta predeterminada es el túnel)
# Desactive la VPN → el interruptor de corte se libera y pf vuelve a su estado anterior

El ayudante privilegiado en sí

# El LaunchDaemon registrado
sudo launchctl print system/com.tetsuharu.RoamSwitch.Helper

# Las reglas pf actualmente cargadas (estado real del Air-Gap / de la guardia de puertos)
sudo pfctl -sr

# El directorio de estado del helper
ls -la "/Library/Application Support/RoamSwitch/"

La respuesta del servidor MCP (verificación fuera de línea)

BIN=/Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchMCPServer
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
              '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | "$BIN"
# Devuelve serverInfo y las definiciones de las herramientas. No se produce ninguna conexión de red.

El origen y las pruebas del servidor MCP.

git clone https://github.com/lafine1211/roamswitch-mcp
cd roamswitch-mcp
swift build -c release        # El mismo código fuente que el binario distribuido
swift test                    # Pruebas unitarias, de entrada adversaria, stdio y fuzzing por mutación
# Consulte SECURITY_TESTING.md para ver el contenido de las pruebas y los problemas encontrados

Identificador de dispositivo

# El valor bruto vinculado al token (solo se envía un SHA-256 con sal)
ioreg -d2 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}'
Libro blanco de Arquitectura / Seguridad de RoamSwitch v1.6 · Alcance 1.10.0 (build 116) · Lafine Systems Design · 2026-09-23
Informe cualquier error u omisión en lafine.net/contact.html.