§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.
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.
| ejecutable | privilegio | puedo hacer | no 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 |
Cualquier otra cosa. No existe una interfaz para ejecutar comandos arbitrarios. Tampoco tiene derecho de envío de red. |
| 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 | Cambie la configuración, active el bloqueo, aísle un puerto, expulse un dispositivo. No abre ningún enchufe. No envía nada por la red. |
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).
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.
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í.
| Método | que hace | Binario/API invocado |
|---|---|---|
| establecerBloquearTodo(_:) | Activa o desactiva el firewall de la aplicación y el modo oculto | /usr/libexec/ApplicationFirewall/ --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) | — |
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.sharedes 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).
§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).
- Entrehierro de emergencia →
set skip on lo0yblock drop all(no se considera nada más) - Interruptor de interrupción de VPN →
block drop allmáspass quicksolo para:lo, la interfaz del túnel (utunN), el protocolo de enlace UDP a las IP del punto final anclado, DHCP e ICMP - Guardia del servidor de desarrollo →
block drop in quick proto tcp ... port { … } - Ninguno → recargar
/etc/pf.confy devolver pf a su estado original
- Entrehierro de emergencia →
- Vuelve a leer después de aplicar.
pfctl -srvuelve a leer las reglas para confirmar queblock drop all, o la regla de cada puerto, está realmente cargada. Un caso en el quepfctl -fse ignoró silenciosamente no se considera un éxito. - El archivo temporal se escribe en una ruta que contiene
UUIDy 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.
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.
| amable | Alcance | gatillo | bucle 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 se evita que el espacio de aire persista
- Se levanta a los 10 minutos como máximo. El propio asistente sigue ejecutando
releaseAirGapIfExpired()justo después de iniciarse y en un temporizador de 60 segundos, pero como red de seguridad totalmente independiente, un LaunchDaemon dedicado (AirGapFailsafe) sin ningúnKeepAlivese despierta por sí solo cada 3 minutos y realiza la misma comprobación y liberación. Si el propio asistente se bloquea y arrastra consigo su propio temporizador de autoliberación, esta red de seguridad no se ve afectada (desde 1.8.6). - Este daemon dedicado nunca toca la propia configuración de inicio del asistente interactivo. Un intento anterior (1.8.5) usaba el propio
KeepAlive.PathStatedel 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 un reinicio del daemon. Al arrancar, el asistente lee el estado en disco con
reapplyFromDisk()y lo restaura él mismo, en el orden air-gap, protección de puertos, valor predeterminado del sistema (la regla de los 10 minutos también se aplica aquí). - Un levantamiento fallido se trata como una falla. Si
block drop allen 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". - Puedes recuperar el control en cualquier momento, con el botón del ascensor en el modo modal o simplemente apagando el Wi-Fi.
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 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.
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 sistemaNEFilterDataProvider: 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/hostsy atribuye cada flujo a un proceso. QUIC (UDP/443) no tiene SNI legible, por lo que en el modoblockse elimina, lo que obliga al navegador a recurrir a HTTP/2 sobre TCP. Necesita una aprobación única en Configuración del sistema. - ②
/etc/hostssumidero (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/hostscomo0.0.0.0y 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
warna 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 unwarn, 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 aallowlist.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ónblockno 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/hostsnunca 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 entradapermanentconarp -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 -ses 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.jsonpara 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-toolsde Homebrew (sin derecho a extensión de red de Apple). Importa tu propio.conf; el ayudante lo guarda en0600. Kill-switch: pfblock drop allmás unpass quicksolo paralo, la interfaz del túnel, el protocolo de enlace UDP a las IP del punto final anclado, DHCP e ICMP. UnAllowedIPsque 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; leetailscale status --jsony ejecutatailscale set --exit-node=<node>. Se recomienda la CLI independiente (brew install 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. - Reconfiguración de la red al desconectarse. El macOS independiente
tailscaledno 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 quetailscaledsobreviva). - 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.
| Operación | Implementación | privilegio | como 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 | Ubicación | Contenidos |
|---|---|---|
| token de licencia | Llavero com.tetsuharu.RoamSwitch.license | Un token firmado por Ed25519. kSecAttrAccessibleAfterFirstUnlock |
| Configuración de la aplicación/protección activada y desactivada | Suite UserDefaults com.tetsuharu.RoamSwitch | Registros de redes confiables, política de protección, listas de exclusión, etc. |
| estado pf | /Biblioteca/Soporte de aplicaciones/ | 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/ | La 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/hosts | Una 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/ | El conjunto IP → MAC realizado permanent por el bloqueo preventivo (§5). Eliminado cuando se desbloquea |
| Configuración de WireGuard | /Biblioteca/Soporte de aplicaciones/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 dispositivo | Valores predeterminados de usuario | Un valor aleatorio, generado sólo cuando IOKit no devuelve un UUID (§9) |
| Registros | os.Logger/NSLog | Registro unificado. No se envía nada externamente |
Tráfico que sale de la máquina (la lista completa)
No existe ningún código en ninguna parte 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 SDK de informe de fallos. La única biblioteca externa es Sparkle. (actualizaciones). Lo que sale a la red son estos ocho, y eso es todo (el sexto solo si el usuario configura una VPN, el séptimo solo si se activa el emparejamiento de RoamSwitch Sensor).
| Conexión | Destino | cuando sucede | que 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 de host, versión de la aplicación. Stripe maneja la información personal en el momento de la compra; la aplicación no lo maneja |
| Comprobación de actualización | lafine.net /updates/appcast.xml |
Sparkle, cada 24 horas y en el lanzamiento | 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/ |
Cuando Link Guard (§5) está activado y la "Actualización automática" está habilitada, cada 24 horas (y en el momento del lanzamiento). Desactivar la "Actualización automática" elimina esta ruta | Sólo un GET. Sin cadena de consulta, sin cookies, nada que identifique la máquina. Un archivo estático firmado solo recepción; el manifiesto y el cuerpo del feed están verificados con Ed25519. La clave de firma está dedicada al feed: una clave separada de la actualización de la aplicación SUPublicEDKey (por lo que 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 |
Cada 24 horas desde el inicio de la aplicación (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; 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. 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 | Sólo cuando el usuario ha instalado ClamAV y utiliza la función de escaneo. Lanza freshclam |
Una búsqueda de definición de ClamAV estándar. No contiene información derivada de RoamSwitch. |
| 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) |
"Telemetría cero" aquí significa que no hay telemetría que recopile y envíe datos de uso o resultados de diagnóstico. Esto no significa que no haya ningún tráfico de red. Los ocho caminos en el La tabla anterior existe. Pero cada uno de ellos es algo que inicia el usuario o una firma verificada, receive-only buscar y los resultados de diagnóstico, puertos, URL, y el contenido de los archivos en la Mac nunca lo abandone.
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.
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).
Esto no se limita a afirmar. El 29 de agosto de 2026 se auditó una instalación 1.4.7 en ejecución con
tcpdump + atribución por proceso (nettop / lsof / a filtrado
pktap captura) + LuLu, durante una ventana de ~2 horas, con el nivel de seguridad fijado en
Bloqueo máximo y verificación de appcast forzada.
Resultado: no hay flujo de salida atribuido a RoamSwitch, RoamSwitchHelper,
o RoamSwitchMCPServer que no sea el appcast marque a lafine.net; el PCM
los únicos sockets del servidor eran localhost; los volcados de derechos están vacíos.
Artículo completo y un script que cualquiera puede ejecutar para reproducirlo: auditoría/RESULTADOS-2026-08-29.md
Esta medida es de 1.4.7, antes de la fuente de amenazas de Link Guard (1.7.2, fila 3) y la VPN.
túnel (1.7.6, fila 5). Auditado en 1.7.2 o posterior, verá dos de solo recepción
GETs a lafine.net (el appcast y el feed de amenazas); si una VPN
está configurado, también verá UDP al propio servidor WireGuard del usuario (destino y contenido
bajo el control del usuario). Todo desaparece con "Actualización automática: desactivada" y sin VPN configurada.
§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. No registra servicio ni escucha. Lee una línea de la entrada estándar, devuelve una línea en la salida estándar y luego el cliente finaliza el proceso.
- No envía nada. Todos los diagnósticos completos dentro de la Mac.
- 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
| Herramienta | lo que devuelve | Tráfico |
|---|---|---|
| obtener_informe_de_seguridad | Una comprobación de 18 puntos (FileVault / SIP / Gatekeeper / actualización automática / XProtect / cortafuegos / cifrado Wi-Fi / ARP / bloqueo ARP de la puerta de enlace / auditoría de configuración SSH / auditoría de sudo NOPASSWD / puertos expuestos / configuración de protecciones, y más) con una puntuación y consejos de corrección por cada punto | Sólo locales |
| get_exposed_ports | Una lista de puertos TCP de escucha. Para cualquier exposición más allá del localhost, hace una referencia cruzada a una base de datos de servicio peligroso conocido y verifica CORS/encabezados con una sonda HTTP a 127.0.0.1:port (local, cerrado) | Sólo la sonda a 127.0.0.1 |
| get_guard_status | El 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 confiable | Sólo locales |
| auditoría_url_seguridad | Un 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_help | Una 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_secrets | Detecta 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_logs | Agrega 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). | Sólo locales |
| run_active_vuln_scan | Verificación activa de vulnerabilidades, no destructiva y limitada a 127.0.0.1. La única herramienta que usa la red: desactivada por defecto, requiere activarla en los ajustes. | Solo 127.0.0.1 (desactivado por defecto) |
| run_package_cve_scan | Compara los paquetes instalados (Mac: Homebrew / Linux: dpkg, dnf, zypper, pacman) con un mapa de CVE local. No se realiza ninguna comunicación de red. | Ninguno |
| run_package_cve_scan_languages | Compara los archivos de bloqueo de dependencias de npm, PyPI, crates.io, RubyGems, Packagist, Go y Maven con el mismo mapa de CVE local. No se realiza ninguna comunicación de red. | Ninguno |
| get_quarantine_status | Devuelve 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_status | Devuelve el estado de los archivos señuelo antiransomware (canary) y hasta los 50 incidentes detectados más recientes. | Sólo locales |
| get_notification_history | Devuelve el historial de notificaciones enviadas por RoamSwitch (anomalías de auditoría de registros de seguridad, detecciones de ClickFix, etc.) de los últimos 7 días, las más recientes primero. | Sólo locales |
| get_port_anomaly_incidents | Devuelve el estado de la línea base de la protección de puertos anómalos, los puertos bloqueados automáticamente y hasta los 50 incidentes más recientes. La respuesta indica explícitamente que los puertos actualmente bloqueados son una instantánea del estado actual, sin marca de tiempo, distinta del historial de incidentes, que sí lleva marca de tiempo. | Sólo locales |
| get_runtime_threat_status | Indica 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 |
El campo instructions en la respuesta initialize también dice claramente:
"No se puede cambiar el nivel de seguridad, aislar puertos o expulsar dispositivos", comunicando la capacidad
límite con el LLM del lado del cliente. Los recursos de MCP (roamswitch://docs/*) son de solo lectura
Documentos de rebajas también.
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.
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.sortedKeysy.withoutEscapingSlashesdeJSONEncoder. - 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
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)
- Ejecute
xcodebuild archive(versión, firma manual, aplicación de ID de desarrollador). - Exportar con
-exportArchivecomomethod: developer-id. - Después de
notarytool submit --wait, ejecutestapler stapleen la aplicación. - Verifique con
spctl -a -t exec -vv. - 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).
- Construya el DMG, regístrelo ante notario y engrápelo también, y verifique con
stapler validate.
Actualizaciones (Sparkle 2.9.6)
| clave | Valor |
|---|---|
| SUFeedURL | https://lafine.net/updates/appcast.xml |
| SUPublicEDKey | CNxzwijMzMCJzliId76Yl88S/9np6t/xg/zQ9YbYzHs= |
| SUEnableAutomaticChecks | cierto |
| SUSintervalodecomprobaciónprogramada | 86400 |
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.
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 1.7.6, esto está en capas: (1) el túnel VPN + interruptor de interrupción (§5, la defensa principal; no depende de la integridad L2), (2) un pin ARP/NDP de puerta de enlace preventiva en redes no confiables (§5) y (3) detección de suplantación de identidad con un espacio de aire de emergencia (después del hecho). All Pro, desactivado de forma predeterminada (la detección está activada de forma predeterminada).
- Encontrar servidores de desarrollo y bases de datos (Redis, MongoDB, Elasticsearch, etc.) expuestos en
0.0.0.0sin 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
--privilegedo 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 | como 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. |
El tiempo de ejecución reforzado está habilitado y la aplicación no recibe privilegios innecesarios. El tráfico saliente está limitado a las ocho rutas anteriores (la sexta solo si el usuario configura una VPN, la séptima solo si se activa el emparejamiento de RoamSwitch Sensor). 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 identificadores. 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
Derechos (sin permiso de envío de red)
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 no hay tráfico durante el uso normal
sudo tcpdump -i any -n 'host not 127.0.0.1' and 'not port 53'
# Sin tráfico salvo la activación de licencia, las comprobaciones de actualización y las actualizaciones de ClamAV
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 identificadores. 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 5 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}'