Projeto de Sistemas Lafine · Documento técnico

Artigo sobre arquitetura e segurança do RoamSwitch

Este documento explica quais privilégios o RoamSwitch executa e o que ele faz nesse limite. Não contém linguagem de marketing; tudo declarado aqui pode ser verificado no aplicativo de remessa binário e seu comportamento real.

Versão v1.6 Escopo RoamSwitch 1.11.14 (build 161) Requisitos macOS 13.0+ / Apple Silicon Publicado 2026-09-23 Team ID GV76B6G4YU

§1

RoamSwitch é um aplicativo de barra de menu para Mac. Dependendo do quanto você confia na rede que você está atualmente conectado, ele alterna automaticamente o firewall do macOS, serviços de compartilhamento, AirDrop, e DNS. Ele também observa falsificação de ARP, portas abertas para o exterior, armazenamento USB e atividade de criptografia semelhante a ransomware e, quando detecta algo perigoso, irá tão longe quanto cortando o tráfego com o filtro de pacotes (pf) — um air-gap de emergência.

Em outras palavras, o RoamSwitch instala um daemon com privilégios de root e, se quisesse, poderia parar todos tráfego de rede no Mac. É construído por uma pessoa e "por favor, confie em mim" não é suficiente para justificar esse privilégio. Em vez disso, este documento explica o design de uma forma que você pode verificar.

Para quem é isso

  • Engenheiros decidindo se devem instalá-lo
  • Pesquisadores de segurança e jornalistas que desejam entender os detalhes internos antes de uma revisão ou artigo
  • Equipe de segurança em empresas parceiras avaliando uma implementação interna ou pacote OEM

O que este documento não cobre

Ele não entra no ajuste do limite de detecção, nas estatísticas de falsos positivos ou nas orientações da interface do usuário. O que isso explica são quatro coisas: privilégios , limites do processo, fluxo de dados e criptografia. As próprias especificações do recurso estão no pacote de recursos MCP roamswitch://docs/features e na ajuda do aplicativo.

O que é divulgado e até que ponto (política de divulgação)

Este documento foi escrito partindo do pressuposto de que um invasor já possui o binário distribuído. Cada URL de endpoint, identificador, caminho de arquivo, protocolo XPC e chave pública incorporada que aparece aqui pode ser retirado da remessa RoamSwitch.app em alguns minutos com strings, codesign -d ou um proxy de tráfego. Escrevendo-os aqui, portanto não dá nada de novo ao invasor. A única coisa que avança é a compreensão do revisor.

Por outro lado, a implementação do lado do servidor que não pode ser vista no binário — limite de taxa limites, chaves, endpoints administrativos, o esquema de banco de dados, o layout do projeto Firebase – não estão incluídos. O projeto de integração de OEM e parceiros também está fora do escopo aqui e é abordado em um tópico separado documento interno. “O que você pode aprender observando o cliente” é a linha de divulgação para isso documento.

Observação

Este documento corresponde à fonte da versão indicada no topo. Onde o comportamento muda em uma versão posterior, o documento é revisado e o número da versão e a compilação de destino são atualizados. Se você encontrar uma discrepância entre o texto e o código, informe-nos em lafine.net/contact.html.

§2

RoamSwitch.app é composto por três executáveis. Apenas um deles é privilegiado; o outros dois são executados com direitos de usuário de login. Todos os três são fornecidos com Hardened Runtime habilitado, ID do desenvolvedor assinado e autenticado em cartório.

Diagrama dos componentes do RoamSwitch e dos limites de confiança Cliente MCP Claude Desktop / Code MCPServer Privilégio de usuário Somente leitura RoamSwitch.app Privilégio de usuário Barra de menu / Monitoramento / UI Helper root / LaunchDaemon SMAppService.daemon Binários do sistema pfctl / socketfilterfw … stdio (JSON-RPC) Somente leitura * XPC mach service Autorizado por assinatura de código Execução de processo
* MCPServer não se conecta ao aplicativo em si; ele lê diretamente o domínio de preferências compartilhadas e os módulos de monitoramento (§8).
Privilégios e função de cada componente
ExecutávelPrivilégioPode fazerNão posso fazer
RoamSwitch
.app
Usuário de login Monitore o estado da rede, execute diagnósticos, desenhe a UI, chame o auxiliar pelo XPC, altere o AirDrop via defaults, inicie o ClamAV (opcional) Operar diretamente o firewall, PF ou daemons do sistema (tudo isso passa pelo auxiliar)
RoamSwitch
Helper
raiz Somente as operações listadas em HelperProtocol (tabela §3): firewall/stealth, carga/descarga dos daemons de compartilhamento, aplicação do conjunto de regras PF, alteração de DNS, envio de sinais para processos Tudo o resto. Não existe interface para executar comandos arbitrários. Não é declarado nenhum entitlement de envio pela rede, mas, como não está em sandbox, isso por si só não proíbe comunicações (a medição do tráfego de saída está no §7)
RoamSwitch
MCPServer
Usuário de login Ler e formatar valores de diagnóstico, pesquisar na base de conhecimento local; os resultados são retornados ao cliente via stdio Alterar definições, alternar o bloqueio, isolar uma porta, ejetar um dispositivo. Por predefinição não abre sockets nem envia nada pela rede (as únicas exceções são a verificação de assinaturas npm, desativada por predefinição, e o diagnóstico de vulnerabilidades dirigido ao próprio Mac, 127.0.0.1; ver §8). Não é declarado nenhum entitlement de envio pela rede, mas, como não está em sandbox, isso por si só não proíbe comunicações

Direitos e assinatura

  • Todos os três alvos têm ENABLE_HARDENED_RUNTIME = true.
  • O Sandbox do aplicativo está desativado (com.apple.security.app-sandbox = false). Os direitos do auxiliar e do MCPServer são dicionários vazios.
  • As compilações de distribuição são assinadas pelo aplicativo de ID do desenvolvedor, autenticadas pela Apple e grampeadas (§10).
Compensação de design

O App Sandbox não é usado. RoamSwitch precisa ler o UUID de hardware do IOKit, usar CoreWLAN e DiskArbitration, enumere os soquetes de escuta de outros processos (lsof), abra um XPC conexão com um LaunchDaemon e gerar binários do sistema. Nada disso é possível dentro do sandbox, então ele fica desabilitado.

Quatro coisas compensam isso. Primeiro, tempo de execução reforçado. Em segundo lugar, assinatura do ID do desenvolvedor e notarização. Terceiro, apenas um executável é executado como root — o auxiliar — e o que esse auxiliar pode fazer é fixo e enumerado (a tabela §3). Quarto, as conexões com o auxiliar são restritas por código assinatura (§3).

§3

Como é registrado

O auxiliar é registrado como LaunchDaemon usando SMAppService.daemon(plistName:). O plist incorporado no aplicativo (Contents/Library/LaunchDaemons/com.tetsuharu.RoamSwitch.Helper.plist) declara apenas Label, BundleProgram, uma única entrada MachServices e AssociatedBundleIdentifiers. Por causa de como SMAppService funciona, o registro nem é possível, a menos que o aplicativo esteja em /Applications. No primeiro registro, o auxiliar não se torna ativo até que o usuário o aprove manualmente no Sistema Configurações.

Cujas conexões ele aceita (ClientValidator)

O auxiliar inspeciona o processo de conexão em NSXPCListener's shouldAcceptNewConnection e distribui HelperProtocol apenas para aqueles que passar. A inspeção usa o audit_token em vez do PID, para evite a reutilização de PID e TOCTOU.

# Requisitos de assinatura de código exigidos pela build Release dos clientes que se conectam
identifier "com.tetsuharu.RoamSwitch"
  and anchor apple generic
  and certificate leaf[subject.OU] = "GV76B6G4YU"

Este requisito é verificado com SecCodeCopyGuestWithAttributes e SecStaticCodeCheckValidity, e a conexão será interrompida se não passar. Somente As compilações DEBUG eliminam o pino de ID da equipe, para conveniência do desenvolvimento. O que realmente envia é sempre um Versão de lançamento.

Pontos-chave do limite

A segurança do ajudante depende deste único requisito de assinatura de código. Qualquer coisa que o satisfaça (um devidamente assinado RoamSwitch.app) pode chamar todas as operações na tabela abaixo. Há nenhum canal para alimentá-lo com comandos arbitrários, mas as operações nessa tabela não são fracas em eles mesmos. Se o próprio RoamSwitch.app for assumido, essas operações passam para o atacante.

O que o ajudante pode fazer (a lista completa)

As operações privilegiadas definidas em Shared/HelperProtocol.swift são todas elas. Não há API privilegiada que não esteja listada aqui.

HelperProtocol — tudo que roda como root
MétodoO que isso fazBinário/API invocado
setBloqueTodos(_:) Ativa ou desativa o firewall do aplicativo e o modo furtivo /usr/libexec/ApplicationFirewall/socketfilterfw
--setblockall / --setstealthmode
getBlockAllStatus(...) Lê os valores atuais acima socketfilterfw --getblockall
setSharingServicesEnabled(_:) descarregar/carregar os daemons SSH/SMB/Screen Sharing. Ao parar, grava apenas "os que estavam rodando" e restaura apenas aqueles /bin/launchctl list / unload -w / load -w
(fixado nos três: ssh.plist / com.apple.smbd.plist / com.apple.screensharing.plist)
enableNetworkAirGap(...)
disableNetworkAirGap(...)
Aplica e levanta o bloqueio completo de emergência (block drop all). Passa por PFRulesetCoordinator (§4) /sbin/pfctl -f / -e / -sr
setGuardedDevServerPorts(_:) Usa pf para bloquear apenas conexões external para as portas do servidor de desenvolvimento fornecidas (passagem de localhost). Passar um array vazio levanta todos eles /sbin/pfctl (também através do Coordenador)
setSecureDNSServers(_:)
restoreOriginalDNSServers(...)
getCurrentDNSServers(...)
Alterna o DNS dos serviços de rede ativos para DNS de bloqueio de malware (Quad9 9.9.9.9 / Cloudflare 1.1.1.2), fazendo backup das configurações originais e restaurando-as /usr/sbin/networksetup
-listallnetworkservices / -getdnsservers / -setdnsservers
setLinkGuardSinkhole(_:) Guarda de Link (§5). Grava os domínios de phishing/fraude fornecidos em uma seção gerenciada delimitada de /etc/hosts como 0.0.0.0 e, em seguida, libera o cache DNS. Uma matriz vazia remove a seção. Os domínios são normalizados e desduplicados; IPs e lixo são descartados; limitado a 60.000; escrito através de um arquivo temporário + substituição atômica reescreve /etc/hosts (FileManager.replaceItemAt)
/usr/bin/dscacheutil -flushcache
/usr/bin/killall -HUP mDNSResponder
lockGatewayARP(_:)
unlockGatewayARP(...)
getGatewayARPLockStatus(...)
Bloqueio preventivo de gateway ARP/NDP (§5). Fixa os mapeamentos IP → MAC fornecidos (o gateway IPv4, o roteador padrão IPv6, resolvedores DNS no link) como entradas de cache vizinho permanent. Os formatos IP e MAC são validados; um modelo de reconciliação (os pinos que não estão na solicitação são removidos). O conjunto de pinos persiste em gateway_arp_lock.json /usr/sbin/arp -s / -d
/usr/sbin/ndp -s / -d
wireGuardImport(_:)
wireGuardForget(...)
Túnel VPN (§5). O auxiliar salva/exclui o texto WireGuard .conf em 0600 arquivo somente gravação
wireGuardUp(endpointIPv4:endpointIPv6:porta:)
wireGuardDown(...)
wireGuardStatus(...)
Traga o status do túnel para cima/baixo/leitura. O nome do host do endpoint é resolvido pelo app e o IP é passado para o auxiliar (o DNS do auxiliar pode ser cortado pelo kill switch) Homebrew wg-quick up/down, wg show (wireguard-tools; desabilitado se não estiver instalado)
terminarProcess(pid:forceKill:) Suspende (SIGSTOP) ou força o encerramento (SIGKILL) de um processo. Usado para conter processos semelhantes a ransomware. pid > 1 apenas chamada de sistema kill(2) (não um subprocesso)
getHelperVersion(...) Retorna a string de versão do auxiliar (usada para verificações de compatibilidade de aplicativos) —
Compensação de design

terminateProcess pode enviar SIGKILL para qualquer processo, desde que pid > 1. setSecureDNSServers aceita qualquer string de servidor DNS. Isso é a largura que o recurso precisa, mas não é estreito. Julgue com o entendimento de que o a verificação de assinatura de código na frente dele (ClientValidator) é o único portão.

Estado dentro do ajudante

  • HelperTool.shared é uma única instância compartilhada entre conexões. Costumava ser uma instância separada por conexão, portanto, uma contenção de emergência que abrisse uma nova conexão poderia atingir uma corrida que perderia a noção de “quais serviços trazer de volta”.
  • O serviço de compartilhamento e os backups de DNS são modificados apenas em uma fila serial (stateQueue).

Separação de privilégios dentro do assistente e a exceção para ler o registo SSH

Logo após arrancar, o assistente impõe a si próprio um teto aplicado pelo kernel (uma sandbox em tempo de execução, distinta do entitlement de App Sandbox referido acima), que os processos filhos que inicia herdam. Esse teto impede substituir programas do sistema, registar um LaunchDaemon ou LaunchAgent, reescrever a configuração de sudoers, PAM ou sshd, escrever diretamente no armazenamento de diretórios local e executar programas em locais onde os utilizadores, os volumes externos ou as pastas temporárias podem escrever. Não impede as alterações de contas feitas através do Open Directory (dscl) e não restringe o tráfego de rede (não é para isso).

Sob este teto, o /usr/bin/log não é executado. No macOS 26, o log recusa-se a funcionar dentro de uma sandbox (log: Cannot run while sandboxed) e também como nobody (Must be admin). Assim, nem a app do utilizador com sessão iniciada nem o assistente root dentro da sua sandbox conseguem ler o registo do sshd (o registo unificado). A deteção de força bruta SSH tem de ler esse registo, pelo que a separação de privilégios tem uma exceção.

A exceção é um pequeno processo root: antes de entrar na sua sandbox, o assistente inicia o mesmo binário com --ssh-log-reader. Esse processo só aceita um comando de um byte (iniciar ou parar) e não faz mais do que lançar /usr/bin/log com argumentos fixos (log stream --style ndjson --level info e um filtro fixo que deixa passar apenas linhas do sshd). Não passa variáveis de ambiente e quem chama não tem forma de escolher argumentos nem um comando. Não lê nem interpreta o registo; a saída vai para um pipe entregue pelo assistente.

As linhas do registo contêm cadeias que um atacante pode escrever, como o nome de utilizador, pelo que o assistente root não as interpreta. Entrega cada linha a um processo de análise que corre como nobody dentro de uma sandbox que proíbe o acesso à rede, a escrita de ficheiros e o arranque ou a bifurcação de processos, e o endereço de origem é tirado do fim da linha (IP port N), sobre o qual o nome de utilizador não tem influência. Num Mac com o início de sessão remoto desativado, este processo não inicia o log e não lê nada. As operações SSH que podem ser chamadas pelo XPC do assistente são cinco: aplicar as definições, obter o estado, os registos e os inícios de sessão, e levantar um bloqueio. Nenhuma aceita um comando.

§4

Quatro recursos tocam no PF: o air-gap de emergência, a proteção da porta do servidor de desenvolvimento, o túnel VPN Kill-switch WireGuard (§5, 1.7.6+) e seu kill-switch Tailscale (§5, 1.8.0+ — apenas o back-end selecionado). Todos eles sempre vão através de um único ponto de entrada, PFRulesetCoordinator, e nunca execute pfctl -f eles mesmos.

Por que existe um único ponto de entrada

Anteriormente, cada um dos dois recursos carregava regras com pfctl -f independentemente, competindo sobre o único conjunto de regras principal do PF. Se a regra restrita da guarda portuária block ... port {…} fosse carregado após o block drop all do entreferro, você pode acabar em um estado em que a tela disse "isolado", mas o Mac ainda estava acessível. Este bug foi encontrado atacando o máquina de outro host, e foi corrigido em 1.4.3 (a história está escrita em docs/marketing/zenn/03_lan_side_attack_test.md).

Como funciona agora

  • Reconstruído totalmente sempre. Todo o conjunto de regras necessário é reconstruído a partir do estado atual e aplicado de uma só vez. Nunca é aplicado como um diferencial.
  • Uma fila serial. Cada alteração de PF é executada no mesmo DispatchQueue, portanto, quer tenha vindo de uma conexão XPC, da inicialização do auxiliar ou do temporizador à prova de falhas, as alterações são processadas em ordem.
  • A ordem de prioridade é a seguinte (os itens mais acima têm precedência).
    1. Entreferro de emergência → set skip on lo0 e block drop all (nada mais é considerado)
    2. Kill-switch VPN → block drop all mais pass quick apenas para: lo, a interface do túnel (utunN), o handshake UDP para o(s) IP(s) do endpoint fixado(s), DHCP e ICMP
    3. Proteção do servidor de desenvolvimento → block drop in quick proto tcp ... port { … }
    4. Nenhum → recarregar /etc/pf.conf e retornar pf ao seu estado original
  • Leia novamente após a aplicação. pfctl -sr lê as regras para confirmar se block drop all, ou a regra de cada porta, está realmente carregada. Um caso em que pfctl -f foi ignorado silenciosamente não é tratado como sucesso.
  • O arquivo temporário é gravado em um caminho contendo um UUID e excluído uma vez aplicado (v1.4.5 abandonou o caminho fixo em favor de um caminho difícil de adivinhar). O diretório de estado é /Library/Application Support/RoamSwitch.
Comportamento da API

As respostas XPC de enableNetworkAirGap e setGuardedDevServerPorts, (Bool, String?), informe se a operação passou por todo o caminho releitura. O chamador (como ARPSpoofContainmentManager) tenta novamente em caso de falha e, se ainda falha, coloca a mensagem direto na tela: "O trânsito ainda não está parado. Desligue Wi-Fi agora."

§5

Os tipos de bloco são diferentes

Tipos de bloco
GentilEscopoGatilholoopback
Entreferro de emergência Interrompe todo o tráfego, entrada e saída Quando é detectada atividade de criptografia semelhante a ransomware. Ao detectar spoofing de ARP, é acionado imediatamente apenas no modo Bloqueio; em redes balanceadas/confiáveis, ele notifica em vez disso (você o aciona manualmente). Diante de um padrão de comando conhecido do tipo «ClickFix» no histórico do shell, como decodificar base64 diretamente para um shell ou AppleScript (1.8.7+, Pro, desativado por padrão). Não é usado para proteção cotidiana fora de casa Aprovado com set skip on lo0
Interruptor de interrupção VPN Tudo, exceto o túnel, seu handshake, DHCP e ICMP Quando o túnel VPN (§5, Pro, desativado por padrão) está ativado e você ingressa em uma rede não confiável. Mantido até que o túnel seja estabelecido (e enquanto ele estiver inativo) Aprovado com set skip on lo0
Proteção de porta do servidor de desenvolvimento Somente conexões TCP external para as portas fornecidas Isolamento manual com um clique ou bloqueio automático quando uma porta de escuta desconhecida é detectada (Pro) Do localhost, inalterado
Proteção diária de redes não confiáveis Firewall e furtividade, compartilhamento interrompido (§6). PF não é usado Quando você se conecta a uma rede que não está registrada —
Guarda de ligação Somente resolução de nomes de domínios de phishing/fraude (0.0.0.0 via /etc/hosts). PF não é usado Um destino no feed de ameaças ou um homógrafo de marca. Ativado por padrão (Pro) Não afetado
Bloqueio ARP/NDP preventivo Somente o MAC do gateway, o roteador IPv6 e o DNS on-link (cache vizinho). PF não é usado Ao ingressar em uma rede não confiável (Pro, desativado por padrão). Fixado novamente em cada mudança de rede Não afetado

Como o Air-Gap nem se prolonga nem pode ser esperado

  • A forma como termina depende do que o desencadeou (desde 1.9.52). Uma isca de ransomware adulterada, uma deteção confirmada do XProtect e um isolamento manual escolhido a partir de um aviso ARP são de «confiança alta»; ClickFix, anomalias de portas e semelhantes são de «confiança baixa». Um isolamento de confiança baixa continua a levantar-se sozinho, no máximo aos 10 minutos. Um de confiança alta não se levanta aos 10 minutos: levantar por completo apenas pelo tempo permitiria a um atacante limitar-se a esperar.
  • Um isolamento de confiança alta mantém-se totalmente fechado até um limite (1 hora por predefinição, de 5 minutos a 24 horas nas definições) e aí verifica a causa. No caso do ransomware, só dá a causa por desaparecida se o processo suspeito tiver sido identificado e terminado e as iscas não tiverem mudado desde a deteção. Se não for possível confirmá-lo, passa ao «modo degradado»: as novas ligações continuam bloqueadas (exceto loopback e DHCP) e apenas o rádio Wi-Fi é reposto, para que possa ver o que se passa. Nunca abre apenas por passar o tempo. Só abre se o levantar ou se a causa for confirmada como desaparecida.
  • Quando um isolamento começa, as ligações de saída que já estavam abertas também são cortadas. A tabela de estados do pf é esvaziada (pfctl -F states), o que pára o tráfego anterior ao isolamento. O mesmo acontece ao passar ao modo degradado.
  • Um isolamento causado por falsificação ARP só é levantado automaticamente quando se confirma, sem qualquer tráfego, que a causa desapareceu. A verificação apenas lê a cache ARP (não envia pacotes) e dá a causa por desaparecida quando o MAC de confiança do gateway é visto em 3 verificações consecutivas que abrangem pelo menos 60 segundos. Durante o isolamento, porém, a entrada ARP do gateway pode desaparecer da tabela de vizinhos (confirmado num Mac real) e, nesse caso, a causa não pode ser confirmada. Nesse caso permanece em modo degradado até o levantar.
  • Mesmo que o helper falhe, a verificação de levantamento não pára. Um LaunchDaemon dedicado, sem qualquer KeepAlive (AirGapFailsafe), arranca sozinho de 3 em 3 minutos e toma a mesma decisão do helper (confiança baixa: levantar aos 10 minutos; confiança alta: verificar a causa no limite e passar a degradado). Se o próprio helper falhar e perder o seu temporizador de levantamento, esta rede de segurança não é afetada (desde 1.8.6; a decisão por confiança desde 1.9.52).
  • Esse daemon dedicado nunca toca na própria configuração de inicialização do helper interativo. Uma tentativa anterior (1.8.5) usava o próprio KeepAlive.PathState do helper para essa recuperação, mas ocasionalmente interferia em um encerramento normal do app, causando um reinício indesejado, por isso foi revertida em favor do daemon atual, totalmente independente.
  • A verificação do timestamp usa uma combinação híbrida de tempo de atividade monotônico (ProcessInfo.systemUptime) e hora do relógio. Normalmente prioriza o tempo de atividade monotônico, de modo que correções de NTP e alterações manuais de hora não o afetam. Só recorre à hora do relógio quando o tempo de atividade não é confiável — logo após uma reinicialização, detectada quando o valor atual é menor que o valor armazenado.
  • É reaplicado após um reinício ou quando o daemon é recriado. Ao arrancar, o helper lê o estado do disco com reapplyFromDisk() e repõe por si o Air-Gap, a proteção de portas e as predefinições do sistema, por essa ordem. Também mantém o estado do isolamento (confiança, completo ou degradado) e o tempo decorrido; a contagem continua desde a primeira ativação (confirmado num Mac real ao reiniciar o helper).
  • Um levantamento com falha é tratado como uma falha. Se block drop all não puder ser realmente removido, o carimbo de data/hora será gravado de volta para que o temporizador à prova de falhas e a nova tentativa tenham algo para convergir. A tela nunca mostra falsamente "levantada".
  • Pode retomar o controlo a qualquer momento: com «Levantar isolamento Air-Gap» na barra de menus, com o botão de levantar do modal, ou simplesmente desligando o Wi-Fi. Levantar não exige Pro (funciona mesmo que o Pro expire a meio de um isolamento).

Como um falso positivo da guarda portuária é recuperado

O bloqueio automático de porta desconhecida (Pro, ativado por padrão quando o Pro é ativado) pode impedir um login legítimo. Receptor LAN — LocalSend, Syncthing, qualquer coisa iniciada após a ativação da proteção. Quando isso acontecer, permita-o no botão "Permitir" no banner de notificação ou na linha correspondente em a tela "Portas expostas". Um executável permitido uma vez é registrado como conhecido e não é bloqueado novamente (PortAnomalyGuard.allowPort(_:)). Observe que os intérpretes de script genéricos (Python, Node.js, Netcat, etc.) têm escopo estritamente definido por path:port em vez de binário sozinho para evitar ataques de pessoas que vivem fora da terra. Daemons de sistema Apple que satisfazem anchor apple (rapportd, que apoia Handoff e similares) não são assisti em primeiro lugar.

Isca canário de ransomware autocurável e supressão de contenção

Se os arquivos do chamariz canário forem adulterados ou renomeados e acionarem o entreferro de emergência, liberando a contenção após a verificação da segurança regenera automaticamente arquivos canary ausentes ou corrompidos de volta ao seu hash de linha de base autêntico gerado a partir de modelos incorporados, restaurando imediatamente a vigilância ininterrupta do kqueue (evitando a contaminação da linha de base do adversário). Além disso, durante a contenção ativa (enquanto o modo de emergência é exibido), alertas de notificação redundantes e eventos de reativação de pesquisas periódicas de integridade em segundo plano são automaticamente suprimidos para evitar distrair o usuário durante a resposta a incidentes.

Deteção genérica de ransomware baseada em entropia — 1.10.0, Pro, ativada por predefinição

Monitoriza as pastas Documentos, Ambiente de Trabalho, Transferências e Fotografias via FSEvents, e é acionado quando escritas com entropia de Shannon acima do limiar (7,92) chegam numa rajada de 20 ou mais ficheiros em 5 segundos. Ao contrário da abordagem canary (ficheiro-isco), não depende da adulteração de um ficheiro-isco, pelo que também deteta encriptação em locais sem ficheiro canary. Só ao atingir o limiar é que usa lsof para identificar o processo e verificá-lo numa lista de permissões (cp, mv, ditto, tar, ffmpeg, Xcode, Time Machine, principais navegadores, etc.). Ao detetar, aciona o mesmo bloqueio automático de rede, via RansomwareContainmentManager, usado pela proteção canary.

A edição Linux consegue rastrear rajadas por processo porque o fanotify associa um PID a cada evento de escrita. O macOS não tem fanotify nem (por opção de design) EndpointSecurity, pelo que esta implementação lê apenas o caminho via FSEvents e conta a rajada globalmente, independentemente do processo. Limitação: quando vários processos legítimos escrevem simultaneamente grandes quantidades de ficheiros de alta entropia (por exemplo, tarefas de compressão ou encriptação concorrentes), um falso positivo é mais provável aqui do que na edição Linux.

Snapshots anteriores ao dano para recuperação de ransomware (Mac)

Um snapshot feito no momento da deteção já contém os ficheiros cifrados antes dela, por isso não é um caminho de volta. O RoamSwitch faz por isso snapshots locais APFS de forma periódica, independentemente da deteção (a cada 6 horas por predefinição; desativado, 1, 3, 6, 12 ou 24 horas). Quando ocorre uma deteção, os novos snapshots anteriores ao dano e a limpeza param até 7 dias (modo de retenção), para que o estado cifrado não substitua a última geração anterior à cifragem. A recuperação é sempre manual: só os ficheiros escolhidos são copiados para ~/RoamSwitch-Recovered/. Os ficheiros atuais nunca são substituídos e não é oferecida a restauração do volume inteiro. Copiar exige montar o snapshot, por isso o helper com privilégios precisa de Acesso Total ao Disco (os snapshots continuam a ser criados sem ele). O macOS pode remover sozinho os snapshots locais, por exemplo com pouco espaço livre, e não há garantia de que durem mais de cerca de 24 horas. A janela de recuperação é uma funcionalidade Pro (a criação dos snapshots em si funciona também sem Pro).

Pacote de evidências forenses — 1.10.0

Quando um Air-Gap de emergência começa — acionado pela proteção canary, pela proteção de ransomware baseada em entropia ou semelhante — uma fila em segundo plano que não bloqueia a própria contenção guarda uma captura da lista de processos (ps), das ligações de rede (lsof -i), dos ficheiros modificados nos últimos cinco minutos e dos ficheiros abertos pelo processo suspeito, em ~/Library/Application Support/RoamSwitch/incident-evidence/<carimbo temporal>/manifest.json. O manifesto regista o SHA-256 de cada artefacto para que uma adulteração posterior possa ser detetada. A lista de pacotes está disponível na ferramenta MCP get_forensic_evidence_bundles. Limitação: por ser construído sem a entitlement EndpointSecurity, não captura traços de pacotes nem capturas de memória/descritores de ficheiro do processo.

Registo de execução de processos (Mac, Pro, desativado por predefinição)

O helper privilegiado lança o /usr/bin/eslogger do próprio macOS (exec, fork, exit) como processo filho e regista os arranques de processos apenas neste Mac. Não pede o entitlement EndpointSecurity (nunca chama es_new_client) e o RoamSwitch tem de estar permitido em Acesso total ao disco (Definições do Sistema; se não constar, adicione /Applications/RoamSwitch.app com «+»). Enquanto faltar a permissão, mostra o estado «indisponível», espera e volta a verificar de 60 em 60 segundos (não passa a um polling pesado). Apenas notifica. Nunca bloqueia a execução nem a comunicação, nem isola.

Notifica estes dez comportamentos: um navegador, uma aplicação Office ou de correio eletrónico a iniciar diretamente uma shell; a execução de um binário não assinado a partir de uma pasta temporária ou de um local em quarentena; um comando de uma linha que transfere e canaliza para uma shell (combinado com um URL de IP em bruto, base64, eval ou verificação TLS desativada); osascript a usar base64 ou eval; a execução de um ficheiro logo após a remoção do seu atributo de quarentena; o lançamento de um binário não assinado a partir de um registo de arranque automático recém-escrito (LaunchAgent ou LaunchDaemon); um processo-pai não assinado pela Apple a ler um segredo do chaveiro (como security find-generic-password -w); uma linha única de python3, perl, ruby, php ou pwsh com -c/-e/-Command, em que uma descodificação base64/eval e uma chamada a curl, wget, exec ou system surgem ambas na mesma linha (a partir de 1.10.0 — usar apenas base64 ou apenas eval não aciona isto); e o lançamento de um processo com a variável de ambiente DYLD_INSERT_LIBRARIES definida (a partir de 1.10.0 — para evitar fugas de segredos, é lido apenas o valor desta única variável; processos assinados pela Apple são excluídos); e uma linha de comandos que indica diretamente um ficheiro de credenciais isco colocado pelo RoamSwitch (como ~/.aws/credentials ou ~/.env.backup, reconhecido pela marca no conteúdo), por exemplo cat, tar ou curl -F; os ficheiros reais nunca correspondem e as leituras através de uma biblioteca ficam para a vigilância de honeytokens. Cada deteção transporta um ID de técnica MITRE ATT&CK.

Só as deteções de gravidade alta se tornam banners, e as do mesmo tipo são agrupadas numa por 10 minutos (cada evento fica no registo). As ferramentas de desenvolvimento como o Claude Code e a CLI do GitHub que leem as suas próprias credenciais não são notificadas por predefinição. Se outro programa as ler, a ação «Permitir este programa» da notificação permite apenas esse item (por Team ID se estiver assinado; caso contrário, pelo nome do executável, em par com o nome do item).

O registo é guardado legível apenas por root (diretório 0700, ficheiros 0600) e mantém 200 MB e 14 dias por predefinição. As variáveis de ambiente não são guardadas, mas os argumentos da linha de comandos podem conter segredos. Cada segmento é selado numa cadeia de hashes ligada ao hash do segmento anterior, e «Verificar cadeia» deteta edições, segmentos em falta e selos removidos (cada um confirmado num Mac real). Isto deteta a adulteração, não a impede. Apagar ou reverter o segmento mais recente não é detetável. A leitura passa pelo XPC do helper, e o MCP passa pela app (com nova verificação de Pro).

Limites: as execuções anteriores ao arranque do helper ou à ativação do registo não são registadas. O eslogger observa a posteriori e não pode bloquear. Com cargas como milhares de arranques de processo por segundo perdem-se eventos (a contagem é mostrada no ecrã e o funcionamento continua). Este registo não substitui um EDR (deteção e resposta em endpoints).

Honeytokens de credenciais — 1.10.0, Pro, ativados por predefinição

Coloca um ficheiro de credenciais-isco, marcado com um valor sentinela, em cada um destes três locais: ~/.aws/credentials, ~/.ssh/id_rsa_backup e ~/.docker/config.json. Nunca substitui um ficheiro real já presente nesse caminho. A deteção de acesso combina o mesmo mecanismo kqueue (DispatchSource.makeFileSystemObjectSource) usado pela proteção canary com uma sondagem da hora de acesso a cada 30 segundos, uma vez que sem EndpointSecurity um evento open() não pode ser capturado de forma fiável. A deteção limita-se a notificar; nunca bloqueia nada nem aciona o isolamento. A isca ssh usa um nome que o ssh não procura por predefinição (não id_rsa), por isso uma execução normal do ssh nem é quebrada nem sinalizada por engano. A isca AWS usa um nome de perfil distinto em vez de [default], a do Docker aponta para um registo interno inexistente e todas são criadas com modo 0600 (desde a 1.10.3). Além destes três, são colocados iscos também em ~/.pypirc e ~/.env.backup (para pesquisas de tokens PyPI, ficheiros .env e chaves de API de IA). Não se usa ~/.npmrc porque o npm o lê em cada execução, nem as credenciais GCP porque um ficheiro falso quebraria a autenticação real. Quando um isco é realmente acedido, é colocado adicionalmente exatamente um isco relacionado (~/.aws/config, ~/.ssh/config ou ~/.kube/config), também vigiado; a cadeia termina após um passo e nenhum ficheiro existente é sobrescrito.

Monitorização de acesso a credenciais do navegador — 1.10.0, opt-in mesmo no Pro, desativada por predefinição

Monitoriza, via FSEvents, as escritas nas bases de dados de palavras-passe/cookies guardados do Chrome, Brave, Edge, Vivaldi, Chromium e Firefox. Quando ocorre uma rajada, o lsof identifica o processo; tudo o que não seja o próprio navegador, um indexador como o Spotlight ou um daemon de cópia de segurança conta como deteção. Ao contrário das outras novas proteções, ativar o Pro não a ativa automaticamente — uma extensão do navegador ou a função de importação de um gestor de palavras-passe podem acioná-la, pelo que o risco de falso positivo é suficientemente inerente para que seja lançada como opt-in.

Comunicação com o Sensor (TLS e assinaturas)

O emparelhamento, os pedidos de auditoria e a obtenção de resultados com um RoamSwitch Sensor (um produto à parte) na mesma LAN usam TLS 1.3 com a impressão digital do certificado (SHA-256) fixada (Sensor 0.3.9 e posteriores). A impressão digital não é obtida pela rede; o utilizador escreve o valor mostrado no ecrã do Sensor. Não são validados a autoridade certificadora, o nome do anfitrião nem a validade, e só se confia no valor fixado. Se o TLS falhar, não se recorre a texto simples (recorrer a ele permitiria a um intermediário fazer o downgrade da ligação). As assinaturas dos pedidos incluem carimbo temporal e nonce (v2) para impedir a repetição. Só um emparelhamento antigo sem impressão digital continua a ligar-se em texto simples como antes, com um aviso.

Se o certificado mudar, a ligação é recusada. A atualização da impressão fixada nunca é automática: só é guardada quando o utilizador escreve e confirma o novo valor e uma sonda assinada tem realmente êxito por TLS. Limite: esta sonda não verifica por chave que o Sensor é o mesmo que foi emparelhado antes. Depende de o utilizador escrever o valor que confirmou no ecrã do Sensor.

Detecção multicamada de downloads (assinaturas estáticas + ClamAV) — camada de assinaturas estáticas adicionada na 1.8.7

Arquivos recém-colocados nas pastas Downloads, Área de Trabalho ou Documentos passam primeiro por uma verificação leve de assinaturas estáticas via StaticSignatureScanner (apenas os primeiros 4 MB, com correspondência simples de padrões de bytes AND/OR — sem expressões regulares nem cálculos de entropia). Não requer o entitlement EndpointSecurity e continua funcionando mesmo onde o ClamAV não está instalado. A detecção é deliberadamente limitada ao que pode ser afirmado como fato sem um corpus real de malware: a assinatura de teste EICAR, padrão do setor, e cinco reverse shells clássicas de uma linha documentadas em referências públicas de segurança ofensiva (por exemplo, PayloadsAllTheThings) — /dev/tcp/ do bash/sh, -e do netcat, pty.spawn do Python, Socket do Perl e fsockopen do PHP. Não escrevemos «assinaturas» em nível de byte para famílias de malware nomeadas sem dispor de uma amostra real da qual extraí-las — isso seria apenas uma falsa sensação de segurança. Os próprios padrões são armazenados ofuscados com XOR 0x5A, para que o binário do RoamSwitch nunca contenha literalmente uma string EICAR ou uma reverse shell pela qual o próprio ClamAV poderia sinalizá-lo.

Somente os arquivos que a camada de assinaturas estáticas classifica como «limpos» seguem para a verificação do ClamAV. Arquivos recém-colocados nas pastas Downloads, Área de Trabalho ou Documentos são verificados imediatamente pelo ClamAV, independentemente de terem extensão .tmp ou atributo com.apple.quarantine (por exemplo, uma cópia feita pelo Terminal). O EICAR e outras assinaturas de teste padrão do setor são tratados como inofensivos: nenhuma notificação é exibida, apenas um registro é feito no histórico de notificações — e nunca são colocados em quarentena nem bloqueados (somente amostras reais de malware são colocadas em quarentena). E se já existia anteriormente um arquivo de mesmo nome em quarentena, a nova ameaça ainda assim é movida para a pasta de quarentena com um nome exclusivo e com timestamp, de modo que uma ameaça nunca pode permanecer em seu local original por causa de uma colisão de nomes.

Deteção de web shells nas pastas web de desenvolvimento (Web Rules)

Os ficheiros do desenvolvimento web local (~/Sites, MAMP, Herd, Local, /Library/WebServer/Documents e as pastas de projeto registadas para a comparação de CVE de pacotes) são verificados com as mesmas 18 regras de deteção da edição Linux: PHP, JavaScript, JSP e ASP. As regras descrevem apenas técnicas documentadas publicamente e não contêm qualquer assinatura extraída de uma amostra real. Há três camadas: integradas na app, uma atualização assinada só de receção uma vez por dia (incluída na obtenção única existente) e as suas próprias regras em ~/Library/Application Support/RoamSwitch/rules.d (só são lidos ficheiros pertencentes a root ou a si que nem o grupo nem outros possam escrever). As pastas que a app já vigia, como Transferências, são verificadas logo após uma escrita; as pastas web de desenvolvimento, uma vez por dia e a pedido (a verificação diária não entra em node_modules nem .git; a de uma pasta escolhida entra em node_modules). Como o EndpointSecurity não é usado, nada pode ser parado antes de executar: toda a deteção é posterior.

Por predefinição, só critical é posto em quarentena automaticamente. Um resultado high não toca no ficheiro e aparece como notificação e numa lista nas Definições, onde pode escolher «não detetar novamente» (o nível de quarentena altera-se nas Definições). O hook posterior à deteção (~/Library/Application Support/RoamSwitch/hooks/post-detection) só é executado se pertencer a root ou a si e mais ninguém o puder escrever, e é parado aos 30 segundos. A resposta sobre contas (desativada por predefinição; ativá-la exige a palavra-passe de administrador) revoga a shell de início de sessão do dono de um ficheiro em quarentena, mas não toca em root, UID inferiores a 501 nem utilizadores com sessão iniciada (incluindo si, que usa este Mac). Executado como root sob a própria sandbox do auxiliar numa máquina virtual com macOS 26, foram verificadas a alteração real da shell com dscl e a sua reposição (a sandbox bloqueia escritas diretas em dslocal, mas não alterações feitas através de dscl). A confirmação com a palavra-passe de administrador e o caminho de chamada da app para o auxiliar ainda não foram verificados num equipamento real. Houve 0 resultados em 114 501 ficheiros de uma pasta de desenvolvimento.

Monitoramento de nova persistência de inicialização (LaunchAgent/LaunchDaemon) — 1.8.7

Novos arquivos .plist colocados em ~/Library/LaunchAgents, /Library/LaunchAgents ou /Library/LaunchDaemons são detectados em tempo real via FSEvents (PersistenceMonitorGuard). Sem o ES_EVENT_TYPE_AUTH_CREATE do EndpointSecurity, a própria gravação não pode ser bloqueada. O que é possível é perceber em questão de segundos e avaliar o conteúdo.

A avaliação se baseia no que está sendo iniciado diretamente, não na assinatura do próprio executável. Técnicas reais observadas em 2026 — um LaunchAgent disfarçado de Google Update escondendo um script bash decodificado em base64, ou um LaunchDaemon com privilégios de root reexecutando a cada inicialização um payload de AppleScript decodificado em base64 — escondem ambas o código malicioso no próprio script, enquanto chamam normalmente um /bin/bash ou /usr/bin/osascript validamente assinado. Uma verificação que olhasse apenas para a assinatura do executável deixaria passar os dois casos. Por isso, qualquer LaunchAgent/LaunchDaemon recém-registrado que invoque diretamente um interpretador de script bruto (bash, sh, zsh, osascript, python3, perl, ruby, php e similares) é sinalizado incondicionalmente, independentemente de a assinatura do próprio interpretador ser válida. Os argumentos passados a ele também são submetidos ao StaticSignatureScanner, e qualquer padrão conhecido como malicioso é incluído na notificação. Entradas registradas via BundleProgram (apontando para um binário compilado e assinado dentro de um bundle de aplicativo — incluindo o próprio Helper do RoamSwitch) são simplesmente verificadas da forma habitual, com codesign --verify --strict.

Detecção de ClickFix e bloqueio de emergência — 1.8.7, desativado por padrão

«ClickFix» é uma técnica de engenharia social em que uma falsa tela de CAPTCHA ou erro instrui você a abrir o Terminal e colar um comando «para verificação». As detecções aumentaram mais de 500% entre 2024 e 2025, e até 2026 é considerada um dos vetores de ataque mais prevalentes no macOS. Como é o próprio shell legítimo do usuário executando exatamente o que foi digitado, ela escapa completamente da verificação de assinatura do Gatekeeper.

Barra de menu → Proteção contra malware → Defesa ClickFix (Pro, desativada por padrão). Monitora via FSEvents novas linhas anexadas a ~/.zsh_history/~/.bash_history (somente linhas adicionadas após o início da proteção — nunca examina o histórico já existente), e corresponde apenas às reverse shells clássicas que compartilha com o StaticSignatureScanner, além da combinação específica de decodificar base64 diretamente para um shell ou para o osascript. Um simples curl | bash — extremamente comum em instaladores legítimos — é deliberadamente excluído, pois é indistinguível das instruções oficiais de instalação do Homebrew, rustup, nvm e similares. Assim que uma correspondência é encontrada, ela aciona o mesmo air-gap de emergência descrito acima. Como o comando já foi executado, isso é necessariamente reativo, mas ainda pode interromper um download de segundo estágio ou uma exfiltração de credenciais em andamento. Como qualquer outro gatilho de air-gap, ele é liberado sozinho em no máximo 10 minutos (veja o início desta seção).

Uma limitação conhecida. Como a detecção depende de uma gravação no histórico do shell, ela não consegue detectar a variante sem arquivo que evita totalmente o Terminal invocando diretamente o Editor de Script pelo esquema de URL applescript://, sem gravar nada em disco. A Apple já introduziu alguma mitigação para isso (uma caixa de diálogo de confirmação para scripts não identificados), mas foram relatadas variantes que a contornam. Testamos esse caminho em hardware real usando o log unificado do macOS (log show/log stream) e não encontramos nenhum sinal de log distintivo no qual basear a detecção. Preferimos declarar essa limitação com clareza a alegar uma cobertura que não temos.

Deteção de Riscos do Docker — 1.8.9, Pro, desativado por predefinição

Barra de menus → "Proteção contra Malware" → "Detetar Contentores Docker Privilegiados e Montagens docker.sock" (DockerEventGuard). Como esta base de código não tem precedente de uma ligação de streaming persistente como docker events, utiliza a mesma abordagem de sondagem por temporizador que o PortAnomalyGuard. A cada 20 segundos obtém o conjunto de IDs de contentores com um docker ps -q leve, executando depois docker inspect --format apenas sobre a diferença (contentores recém-iniciados) para uma inspeção detalhada. A cadeia de formato utilizada para deteção é deliberadamente idêntica à da edição Linux (roamswitch_core::health::LinuxHealthChecker::DOCKER_INSPECT_RISK_FORMAT), pelo que ambas as plataformas assinalam exatamente as mesmas condições (arranque em modo --privileged, ou uma montagem vinculada de /var/run/docker.sock).

Não é tomada qualquer ação automática ao detetar. Um contentor privilegiado ou uma montagem docker.sock é uma "configuração" de risco que pode permitir uma fuga de contentor, mas não é um comprometimento confirmado — também há usos legítimos, como executar deliberadamente um agente de monitorização em modo privilegiado. Como a maioria dos utilizadores não usa Docker de todo, esta funcionalidade permanece desativada por predefinição mesmo com uma licença Pro. Não utiliza o entitlement EndpointSecurity; a CLI docker é localizada verificando primeiro o caminho de instalação predefinido do Docker Desktop, depois o Homebrew, depois o which. Verificada numa instalação real do Docker Desktop (29.7.2) em três cenários — um contentor privilegiado, um contentor com montagem docker.sock e um contentor normal — sem falsos positivos nem deteções em falta.

Auditoria de Fugas de Segredos/Chaves API — adicionada em 1.8.4, análise de pastas adicionada em 1.8.9

Barra de menus → "Proteção contra Malware" → "Auditoria de Fugas de Segredos/Chaves API". Cole texto e é imediatamente verificado em busca de chaves API e tokens expostos, mostrando o número da linha, uma cadeia mascarada e uma recomendação para cada ocorrência (SecretLeakAuditor, inteiramente no dispositivo). A versão 1.8.9 adicionou a opção "Escolher Pasta para Analisar", permitindo ao mesmo motor de deteção auditar recursivamente um diretório — como um checkout de código-fonte — através de auditDirectory(at:). .git, node_modules, target, vendor, dist, build, __pycache__ e venv são automaticamente excluídos, tal como ficheiros com mais de 2 MB ou detetados como binários. O processamento é executado fora da thread principal para não bloquear a interface, e nada é jamais enviado para o exterior.

Link Guard (bloqueio de conexões de phishing) — 1.7.2 e posterior, reforçado em 1.8.0

Barra de menu → "Proteção contra malware" → "Link Guard" (Pro). Bloqueia conexões com sites de phishing/fraude no dispositivo, em todos os navegadores e aplicativos. Existem dois pontos de aplicação, usados ​​em conjunto em ordem de prioridade.

  • ① Extensão do sistema de filtro de conteúdo (RoamSwitchLinkFilter, 1.8.0+, preferencial após aprovação). Uma extensão do sistema NEFilterDataProvider — sem revisão da Apple (os provedores de filtro de conteúdo são de autoatendimento, sem fila de aprovação). Ele inspeciona a resolução de nomes do fluxo TCP de saída real after. O nome de destino vem do nome do host resolvido pelo sistema operacional ou, na falta disso, o TLS SNI analisado a partir dos primeiros bytes do fluxo - portanto, um navegador que faz seu próprio DoH/DoT e se conecta a um IP vazio ainda está bloqueado. Nunca reescreve /etc/hosts e atribui cada fluxo a um processo. QUIC (UDP/443) não tem SNI legível, portanto, no modo block ele é descartado, forçando o navegador a voltar para HTTP/2 sobre TCP. Precisa de uma aprovação única nas configurações do sistema.
  • ② /etc/hosts sinkhole (fallback, enquanto a extensão não é aprovada/recusada). O auxiliar privilegiado grava os domínios de destino em uma seção gerenciada delimitada de /etc/hosts como 0.0.0.0 e libera o cache. Limite de 60.000. Assim que a extensão estiver ativa, esta seção será removida.
  • Três modos. "Desligado" desativa-o. "Avisar apenas" pausa a conexão correspondente e pergunta ao usuário (veja Real warn abaixo). "Bloquear automaticamente sites fraudulentos óbvios (recomendado)" cai imediatamente. O padrão é bloquear a partir de 1.7.2.
  • Real warn (1.8.0+, extensão apenas). Em um warn, a extensão do sistema holds o fluxo (tanto o nome do sistema operacional quanto o caminho SNI) com .pause() e o aplicativo gera uma notificação "Permitir/Bloquear". No momento em que o toque do usuário atinge allowlist.txt / extra.txt (a extensão pesquisa os pequenos arquivos de estado a cada 1,5 s), os fluxos retidos são retomados (permitidos) ou descartados (bloqueados). Nenhuma resposta dentro de 25 segundos falha open (um aviso é consultivo). A decisão é armazenada em cache por host (permitir 5 minutos/bloco de 1 hora), para que os outros fluxos da página e as visitas posteriores sejam instantâneos e aplicados. A notificação block permanece inalterada: "Bloqueado…" com um botão "Permitir uma vez (5 min)".
  • Apenas casos claros são bloqueados. Uma listagem no feed de ameaças ou um homógrafo Unicode de marca — todo o resto (TLDs de alto risco, representação de subdomínio,…) é um aviso. O mecanismo de veredicto é compartilhado com a edição Linux e não envia URLs para lugar nenhum.
  • Recuperando de um bloqueio errado. Permitir um domínio da notificação ou do menu (5 minutos ou permanente). A lista de permissões é subtraída quando a seção é regenerada.
  • Pro-gated. A aplicação (applyMode()) só acontece em uma licença Pro válida. Sem Pro, o modo é armazenado, mas /etc/hosts nunca é tocado. A ativação ou caducidade de uma licença entra em vigor no meio da sessão.
  • Feed e seed empacotado. A lista de bloqueios vem do feed de ameaças assinado (§7, verificado com uma chave dedicada ao feed). Ele funciona no pacote inicial do aplicativo (cerca de 60.000 entradas) antes mesmo da primeira busca, e desligar a "atualização automática" significa que não há tráfego de saída.

Bloqueio preventivo de gateway ARP/NDP — 1.7.5 e posterior

Barra de menu → "Monitor de portas e dispositivos" → "Fixar o ARP/NDP do gateway em redes não confiáveis ​​(preventivo)" (Pro, desativado por padrão).

  • Como funciona. Ao ingressar em uma rede não confiável, o MAC atual do gateway IPv4, o roteador padrão IPv6 e os resolvedores DNS no link são coletados de route / scutil --dns / arp -n / ndp -an, e o auxiliar fixa cada um como uma entrada permanent com arp -s / ndp -s (confiança no primeiro uso - o primeiro MAC observado é confiável). As respostas ARP/NDP falsificadas para esses IPs são então ignoradas, portanto, um ataque man-in-the-middle não pode ser configurado.
  • Scope. Somente esses três tipos de entrada são fixados. Redes confiáveis ​​(abertas) nunca são fixadas. Em cada mudança de rede, ele é desbloqueado uma vez e fixado novamente. Os resolvedores públicos fora do link (8.8.8.8,…) não têm entrada ARP no link e são automaticamente excluídos.
  • arp -s é preventivo; o entreferro é posterior ao fato. Este pino existe, então uma paródia não pode ter sucesso; A detecção de falsificação de ARP (ARPSpoofContainmentManager) e o entreferro de emergência existem para "cortar mais rápido que um humano" quando um é visto. Eles funcionam de forma independente.
  • Persistence. O conjunto de pinos é salvo em /Library/Application Support/RoamSwitch/gateway_arp_lock.json para que possa ser desbloqueado por meio de uma reconexão XPC ou reinicialização auxiliar.

O túnel VPN (WireGuard/Tailscale) e seu kill switch — 1.7.6 e posterior, backend selecionável em 1.8.0

Barra de menu → "Monitor de portas e dispositivos" → "Túnel VPN" (Pro, desativado por padrão). Esta é a defesa anti-MITM primary — não depende da integridade do L2 (ARP/NDP). O backend é selecionável (submenu → "Backend") entre "WireGuard (arquivo de configuração)" e "Tailscale (Nó de saída)". RoamSwitch não implementa nenhuma criptografia; apenas o backend escolhido está armado.

  • (A) Backend WireGuard. Aciona o wireguard-tools do Homebrew (sem direito de extensão de rede Apple). Importe seu próprio .conf; o auxiliar salva em 0600. Kill-switch: pf block drop all mais um pass quick apenas para lo, a interface do túnel, o handshake UDP para o(s) IP(s) do endpoint fixado(s), DHCP e ICMP. Um túnel não completo AllowedIPs gera um aviso de túnel dividido.
  • (B) Backend Tailscale (1.8.0+). Para usuários que já executam Tailscale. RoamSwitch not executa tailscale up / faz login / instala - ele lê tailscale status --json e executa tailscale set --exit-node=<node>. Recomenda-se a compilação Standalone (download direto em tailscale.com) com a integração de CLI ativada (Definições → “CLI integration” → “Install Now”, que adiciona /usr/local/bin/tailscale) — a compilação da App Store (GUI) não pode ser conduzida de fora do aplicativo (sandbox); com ele você escolhe o nó de saída no aplicativo Tailscale e o RoamSwitch mostra apenas o status. É necessário um nó de saída (roteia todo o tráfego através do túnel); ele é desarmado automaticamente se ficar offline ou inacessível.
  • ⚠️ A compilação Homebrew (brew install tailscale) não é recomendada. O tailscaled distribuído pelo Homebrew tem um bug conhecido no macOS que pode impedí-lo de reescrever corretamente a tabela de rotas (tailscale/tailscale#21395, #21216) — um exit node pode aparecer como “conectado” enquanto o tráfego continua a vazar, ou desativá-lo pode derrubar completamente a ligação à Internet do próprio Mac. O RoamSwitch verifica a tabela de rotas real do sistema operativo e avisa quando isto acontece, mas usar a compilação Standalone acima evita o problema desde o início.
  • Reconfiguração de rede ao desconectar. O macOS autônomo tailscaled não restaura corretamente o roteamento + DNS quando um nó de saída é limpo, então o RoamSwitch devolve o(s) serviço(s) de rede ativo(s) (o mesmo que alternar o Wi-Fi manualmente; apenas serviços com um endereço IPv4, seguro para multi-NIC e IP estático). O tráfego cai por aproximadamente 5 a 10 segundos e o nível de proteção não é rebaixado durante essa janela.
  • O kill switch Tailscale está desativado por padrão (opt-in). O nó de saída já encapsula tudo; o pf block drop all (permite apenas CGNAT/MagicDNS/STUN/DERP/DHCP/ICMP/DNS) é para usuários avançados e é "resistente a vazamentos, não à prova de vazamentos" (DNS é permitido para que tailscaled sobreviva).
  • Automático / em caso de perda de licença. Aparece em redes não confiáveis, mas cai em redes confiáveis; cancelado quando a licença Pro expirar.

§6

Quando você se conecta a uma rede que não está registrada, a opção "nível de proteção" não usa pf. Ele simplesmente altera as configurações padrão do sistema operacional de uma forma que pode ser revertida posteriormente.

Operações por nível de proteção
OperaçãoImplementaçãoPrivilégioComo é restaurado
Firewall + modo furtivo ativado socketfilterfw --setblockall ativado / --setstealthmode ativado raiz (ajudante) off quando você retornar para uma rede segura
Parar SSH/SMB/compartilhamento de tela launchctl descarregar -w raiz (ajudante) Registra apenas aqueles que estavam em execução quando parados e load -w no retorno (com SSH também acoplado a /usr/sbin/systemsetup -setremotelogin on para restauração garantida do macOS moderno)
Desativar AirDrop padrões escrevem com.apple.sharingd DiscoverableMode Usuário (o próprio aplicativo) Salva o valor anterior e grava-o de volta no retorno

Nada disso é um novo mecanismo de bloqueio adicionado pelo RoamSwitch – é apenas alternar as configurações do sistema operacional. Se você exclui o aplicativo, a única coisa que impede é a comutação dependente da rede; o último sistema operacional as configurações aplicadas permanecem como estão. Nada fica bloqueado, mas se você quiser errar no lado seguro, configure-o novamente para "Aberto" em uma rede confiável antes de desinstalar.

§7

O que fica no Mac

Dados armazenados em disco
DadosLocalizaçãoConteúdo
Token de licençaChaveiro
com.tetsuharu.RoamSwitch.license
Um token assinado por Ed25519. kSecAttrAccessibleAfterFirstUnlock
Configurações do aplicativo/proteção ativada/desativadaSuíte UserDefaults
com.tetsuharu.RoamSwitch
Registros em redes confiáveis, política de proteção, listas de exclusão e assim por diante
estado de PF/Biblioteca/Suporte a aplicativos/RoamSwitch/O carimbo de data/hora do air gap, JSON das portas protegidas, o estado do kill switch da VPN, o arquivo temporário do conjunto de regras que está sendo aplicado
Feed de ameaças do Link Guard~/Library/Application Support/RoamSwitch/threatfeed/feed.txtA lista de domínios de phishing/fraude baixada (ou a semente agrupada do aplicativo, se ainda não foi obtida). A versão do feed está em UserDefaults
Seção gerenciada pelo Link Guard/etc/hostsUma seção delimitada delimitada por # BEGIN RoamSwitch link guard… # END, anulando domínios bloqueados para 0.0.0.0. Removido quando o modo está "Desligado" (§5)
Pinos de bloqueio ARP/NDP/Biblioteca/Suporte a aplicativos/RoamSwitch/gateway_arp_lock.jsonO conjunto IP → MAC feito permanent pelo bloqueio preventivo (§5). Excluído quando desbloqueado
Configuração do WireGuard/Biblioteca/Suporte a aplicativos/RoamSwitch/ (área auxiliar, 0600)O .conf que o usuário importou. O nome do host do endpoint também está armazenado em UserDefaults (o aplicativo resolve isso)
UUID substituto do dispositivoPadrões do usuárioUm valor aleatório, gerado apenas quando o IOKit não retorna um UUID (§9)
Registrosos.Logger/NSLogRegistro unificado. Nada é enviado externamente

Tráfego que sai da máquina (a lista completa)

Não existe código que recolha e envie resultados de diagnóstico, informações de portas, URLs ou registos. Não está incluído nenhum SDK de análise nem de relatórios de falhas. A única biblioteca externa é o Sparkle (atualizações). O que sai para a rede são os oito caminhos seguintes, mais as funcionalidades iniciadas pelo utilizador, as funcionalidades desativadas por predefinição e a proteção DNS contra ameaças descritas abaixo da tabela, e mais nada (o sexto só se o utilizador configurar uma VPN, o sétimo só se o emparelhamento do RoamSwitch Sensor estiver ativado).

Conexões de saída que o RoamSwitch faz
ConexãoDestinoQuando isso aconteceO que é enviado
Ativação/desativação de licença lafine.net
/api/v1/license/*
Somente quando o usuário insere uma chave de licença ou desativa o Pro Chave de licença, hash do dispositivo, nome do dispositivo (o «Nome» nas Definições do Sistema) e versão da aplicação. Na libertação, apenas a chave e o hash. As informações pessoais são tratadas pela Stripe no momento da compra; a aplicação não as trata
Verificação de atualização lafine.net
/updates/appcast.xml
O Sparkle verifica a cada 24 horas (e também no arranque, se tiverem passado 24 horas ou mais desde a última verificação). Sempre ativo por predefinição; não são enviadas informações do sistema Uma solicitação HTTP (um UA e uma versão padrão). O item baixado é verificado pela assinatura EdDSA (§10)
Feed de ameaças do Link Guard lafine.net
/updates/v1/{manifest, feed/.txt}
Na versão Pro, quando a «Atualização automática» da proteção de links está ativada (predefinição): a cada 24 horas (e no arranque). Desativar a «Atualização automática» elimina esta via Apenas GET. Sem string de consulta, sem cookies, nada que identifique o equipamento (o User-Agent padrão inclui o nome e a versão da aplicação e a versão do macOS). Um ficheiro estático assinado de apenas receção; o manifesto e o corpo do feed são verificados com Ed25519. A chave de assinatura é dedicada ao feed e é uma chave distinta da SUPublicEDKey das atualizações da aplicação (assim, uma fuga fica limitada a «uma lista de bloqueio incorreta»)
Dados de atualização da verificação de CVE de pacotes / da verificação ativa de vulnerabilidades lafine.net
/updates/v1/manifest
No arranque da aplicação (se tiverem passado 30 dias ou mais desde a última obtenção). A verificação ocorre a cada 24 horas e a obtenção efetiva uma vez a cada 30 dias. Sempre (os dados de atualização da verificação de CVE de pacotes não têm interruptor de desativação, pois são dados exclusivamente recebidos usados apenas para uma comparação local de inventário; aplica-se a todos os utilizadores, incluindo a versão Free. O mapa de CVE da verificação ativa de vulnerabilidades só é obtido se «Verificação ativa de vulnerabilidades (verificação ativa de acessibilidade)» estiver ativada) Apenas um GET. Sem cadeia de consulta, sem cookies, nada que identifique a máquina (o User-Agent padrão inclui o nome e a versão da aplicação e a versão do macOS). Um ficheiro estático assinado e apenas de receção; tanto o manifesto como o corpo do feed são verificados com Ed25519. Reutiliza a mesma chave e o mesmo manifesto do feed de ameaças do Link Guard, mas os feeds efetivamente obtidos (mapas de CVE conhecidos para Homebrew/npm/PyPI/crates.io/RubyGems/Packagist/Go/Maven, o mapa de CVE da verificação ativa de vulnerabilidades, e a lista de nomes de pacotes npm populares usada para a deteção de typosquatting) são ficheiros separados
Atualização da definição de vírus ClamAV Espelhos oficiais ClamAV Quando o utilizador instalou o ClamAV. Na versão Pro, inicia o freshclam automaticamente no arranque, a cada 24 horas e nas rondas autónomas; na versão Free, apenas quando se prime «Atualizar definições de vírus» no menu A obtenção padrão de definições do ClamAV. Não contém informações provenientes do RoamSwitch (podem estar incluídas as informações de identificação padrão do próprio ClamAV)
Túnel VPN (§5, WireGuard / Tailscale) O endpoint WireGuard que o usuário configurou, ou o plano de controle do Tailscale (Tailscale, Inc.) Somente quando o usuário configurou o túnel VPN (Pro, desativado por padrão) e ingressa em uma rede não confiável. Com o WireGuard, o nome do host do endpoint é resolvido via DNS uma vez antes do túnel ser ativado. Com o Tailscale, trata-se de tráfego do plano de controle tratado pelo tailscaled já instalado e autenticado pelo próprio usuário — o RoamSwitch apenas lê tailscale status --json e executa tailscale set --exit-node= Com o WireGuard: o handshake WireGuard (UDP) e o tráfego dentro do túnel. O destino é o próprio servidor VPN do usuário e o conteúdo é o tráfego do próprio usuário. Com o Tailscale: tráfego entre a própria conta Tailscale do usuário e o plano de controle da Tailscale, Inc. (registro de dispositivo, relay DERP, etc., regido pela própria política de privacidade da Tailscale). Em ambos os casos, o RoamSwitch não adiciona nenhum identificador nem dados de diagnóstico
Emparelhamento e ligação de auditoria do RoamSwitch Sensor RoamSwitch Sensor na mesma LAN (hardware configurado pelo próprio utilizador, em execução com um IP fixo) Apenas quando o «Emparelhamento do RoamSwitch Sensor» (Pro, desativado por predefinição) está ativado e introduz o endereço IP e o código de emparelhamento do Sensor para emparelhar (uma troca pontual). Após o emparelhamento, volta a ligar-se ao mesmo IP fixo apenas quando prime «Solicitar auditoria ao Sensor», e durante a obtenção automática do resultado (a partir de 5 minutos após o pedido, depois a cada 5 minutos, até 5 tentativas) O emparelhamento envia o nome de anfitrião e a chave pública Ed25519 deste endpoint, e recebe a chave pública emitida pelo Sensor. Os pedidos de auditoria e a obtenção de resultados são autenticados com uma assinatura Ed25519 — só são enviados pedidos assinados. Os resultados de diagnóstico e as informações de portas só são recebidos como resposta do Sensor; o conteúdo de ficheiros nunca é enviado. O único destino é o endereço IP fixo do Sensor configurado pelo próprio utilizador — nunca chega a nenhum servidor externo, incluindo o lafine.net. A confiança mútua só é estabelecida através da troca explícita do código de emparelhamento
Página de check-out Check-out de listras Somente quando o usuário pressiona o botão comprar (ele abre no navegador) — (uma navegação no navegador)
Escopo de "Zero Telemetry"

Aqui, «Zero Telemetry» significa que não existe telemetria que recolha e envie dados de utilização ou resultados de diagnóstico. Não significa que não haja qualquer tráfego de rede. As oito vias da tabela acima e as funcionalidades descritas abaixo da tabela existem de facto. Mas cada uma delas é iniciada pelo utilizador, é uma obtenção de apenas receção verificada por assinatura ou é uma comunicação com um serviço externo escolhido pelo utilizador (os espelhos de distribuição do ClamAV, um fornecedor de DNS de proteção, o URL verificado, etc.), e os resultados de diagnóstico, as portas e o conteúdo dos ficheiros do Mac nunca são enviados ao programador.

O feed de ameaças do Link Guard (linha 3) adiciona "ele puxa atualizações" em cima de "ele não envia nada" defesa. Os dois são mantidos separados; o whitepaper do Linux §1.1 também divide "Zero Telemetria" de "atualizações somente para recebimento". Desligue a "atualização automática" e o Link Guard será executado no pacote dados (aproximadamente 60.000 domínios de phishing/fraude) mais detecção homográfica off-line, e esse caminho não ocorre.

O túnel VPN (linha 6) só acontece se o usuário configurar seu próprio servidor WireGuard, ou escolher o backend Tailscale e já estiver conectado ao tailscaled com sua própria conta Tailscale; o destino e o conteúdo estão sob o controle do usuário. O RoamSwitch apenas abre o túnel, mantém o interruptor de interrupção, ou lê o status do Tailscale para trocar o nó de saída — não adiciona nenhum identificador nem dados de uso. Sem uma VPN configurada, esse caminho não existe.

O emparelhamento do RoamSwitch Sensor (linha 7) é uma funcionalidade exclusiva Pro, desativada por predefinição; este caminho não existe a menos que o ative. O próprio emparelhamento só é estabelecido através de uma ação explícita — a introdução do código de emparelhamento emitido pelo operador do Sensor juntamente com o endereço IP fixo do Sensor. Toda a comunicação subsequente (pedidos de auditoria, obtenção de resultados) é também dirigida a esse mesmo IP fixo pertencente ao utilizador e autenticada com uma assinatura Ed25519. Nunca chega a nenhum servidor externo, incluindo o lafine.net. Os resultados de diagnóstico ou o conteúdo de ficheiros nunca são enviados, exceto como resposta do próprio Sensor.

Há comunicações que não constam da tabela. Nenhuma delas envia resultados de diagnóstico nem conteúdo de ficheiros ao programador. (1) A proteção DNS contra ameaças da versão Pro muda o DNS do sistema para Quad9, Cloudflare, AdGuard ou CleanBrowsing (âmbito predefinido: «fora de casa / Wi-Fi não fidedigna»). Enquanto está aplicada, as consultas de resolução de nomes seguem para esse fornecedor. (2) A verificação de assinaturas npm da versão Pro pede confirmação em cada execução e depois consulta o registo npm. (3) O diagnóstico ativo de vulnerabilidades (verificação ativa de acessibilidade) limita-se ao próprio Mac (127.0.0.1). (4) O pedido HEAD que a folha «Diagnóstico de segurança de links» envia diretamente ao URL de destino está descrito no parágrafo seguinte.

A planilha de "verificação de segurança do link" no aplicativo envia uma solicitação HEAD ao URL de destino para ver onde chega um URL encurtado (seguindo os redirecionamentos para endereços privados ou locais são interrompidos pelo mitigação SSRF v1.4.5). O MCP audit_url_safety, por outro lado, é uma análise offline que é concluído na hora e não envia o URL para lugar nenhum (§8).

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

Não é apenas uma afirmação. Em 2026-08-29, uma instalação 1.4.7 em execução foi auditada com tcpdump + atribuição por processo (nettop / lsof / captura pktap filtrada) + LuLu, durante cerca de 2 horas, com o nível de segurança fixado em Bloqueio máximo e a verificação do appcast forçada. Resultado (versão 1.4.7): nenhum fluxo de saída atribuído a RoamSwitch, RoamSwitchHelper ou RoamSwitchMCPServer além da verificação do appcast para lafine.net; os únicos sockets do servidor MCP foram para localhost; os dumps de entitlements estão vazios. Esta medição diz respeito apenas à versão 1.4.7. As vias acrescentadas desde então constam da tabela acima e está prevista uma nova medição da versão atual.

Artigo completo e um script que qualquer um pode executar para reproduzi-lo: auditoria/RESULTADOS-2026-08-29.md

Esta medição é da versão 1.4.7. Desde então foram acrescentadas estas vias: o feed de ameaças da proteção de links (1.7.2, linha 3 da tabela), o túnel VPN (1.7.6, linha 6), os dados de atualização da verificação de CVE de pacotes (linha 4) e o emparelhamento do Sensor (linha 7). Ao auditar a versão atual verá GET de apenas receção para lafine.net (o appcast, o feed de ameaças e os dados de atualização); se houver uma VPN configurada, verá também UDP para o servidor WireGuard do próprio utilizador (destino e conteúdo sob o seu controlo). O feed de ameaças desaparece com «Atualização automática: desativada» e o tráfego VPN sem VPN configurada (o appcast e os dados de atualização da verificação de CVE de pacotes não têm interruptor de desativação).

§8

RoamSwitchMCPServer é uma ferramenta de linha de comando independente incluída em RoamSwitch.app/Contents/MacOS/. Um cliente MCP como Claude Desktop ou Claude Code inicia-o como um subprocesso e conversa com ele por meio de stdio (JSON-RPC delimitado por nova linha 2.0). O SDK oficial não seria compilado no SDK do macOS desta máquina, por isso é implementado manualmente em cima do JSONSerialization da Fundação.

O que o design restringe

  • É somente leitura. Simplesmente não há API para alterar o nível de segurança, isolar uma porta ou ejetar um dispositivo. Isso não é algo esquecido na v1 – é deixado de lado deliberadamente. Permitir que um código externo (aqui, um LLM) reescreva o estado de proteção de uma ferramenta de segurança quebraria a confiança de todos os usuários.
  • Não abre nenhum socket de escuta. Não regista nenhum serviço nem fica à escuta. Lê uma linha do stdin, devolve uma linha no stdout e depois o cliente termina o processo.
  • Não envia telemetria. Só a run_npm_audit_signatures (registo npm), opcional e exclusiva do Pro no Mac, comunica com um anfitrião externo. A run_active_vuln_scan (desativada por predefinição) só sonda 127.0.0.1, e no Mac a get_exposed_ports limita-se a enviar um HTTP GET por cada porta exposta para 127.0.0.1 para verificar os cabeçalhos. Para obter o endereço MAC do gateway predefinido, as ferramentas de diagnóstico podem enviar um ping ICMP, normalmente um (alguns se o MAC não for resolvido), ao gateway da LAN.
  • Ele lê as configurações de um domínio diferente. UserDefaults(suiteName: "com.tetsuharu.RoamSwitch") lê explicitamente o domínio do aplicativo (seu próprio domínio de ID de pacote está vazio). Ele apenas lê; não escreve.

As ferramentas que expõe

As principais ferramentas devolvidas por tools/list e os dados que devolvem (a lista completa está em Configuração do MCP)
FerramentaO que retornaTrânsito
get_security_reportUma verificação de 18 itens (FileVault / SIP / Gatekeeper / atualização automática / XProtect / firewall / encriptação Wi-Fi / ARP / fixação ARP da gateway / auditoria de configuração SSH / auditoria sudo NOPASSWD / portas expostas / configuração de proteções, entre outros) com uma pontuação e conselhos de correção por item. Cada item também traz os IDs de controlo NIST CSF 2.0 e CIS Controls v8 correspondentes (a partir de 1.10.0).Somente locais
verify_security_findingsReavalia os itens (checkId) do get_security_report com base no estado atual e devolve um veredicto por item, stillPresent (continua a aplicar-se), resolved (resolvido, ou não aplicável por conceção) ou inconclusive (não foi possível determinar), juntamente com um código de motivo independente do idioma. Serve para confirmar, depois de uma correção, que esta teve mesmo efeito. Um item que não foi possível medir, por exemplo por falta de privilégios, porque o helper privilegiado não está ligado ou porque o Docker não está acessível, nunca é comunicado como seguro e é devolvido como inconclusive. Se omitir checkIds, são verificados todos os itens (até 50 identificadores no Mac e 64 no Linux; no Linux também é aceite isServer). No Mac, host_firewall, network_stealth_mode, gateway_arp_lock, malware_scanning, dns_threat_guard e usb_zero_trust são decididos pelas definições da app RoamSwitch, pelo que refletem essas definições e não são uma nova medição do estado real do sistema operativo. É só de leitura, não se liga a anfitriões externos e não repara nada nem altera definições. No Linux também não envia nada para a LAN e só lê o gateway das caches locais. No Mac, para obter o endereço MAC do gateway, pode enviar um ping ICMP, normalmente um (alguns se o MAC não for resolvido), ao gateway da LAN.Somente locais
audit_mcp_configsLê os ficheiros de configuração MCP dos clientes de IA (Claude Desktop, Claude Code, Cursor, Windsurf, VS Code, Cline, Gemini CLI, além de .mcp.json e ficheiros semelhantes nas pastas de projeto registadas) e assinala definições de risco. Deteta ferramentas aprovadas automaticamente sem confirmação em cada chamada (auto_approve), servidores http:// em texto simples num anfitrião que não é loopback, indicados quer no campo url quer como argumento de arranque, como --server http://… (remote_plain_http), comandos de arranque que são linhas únicas de shell ou intérprete (shell_wrapper), lançadores como npx / uvx / docker run que obtêm um pacote ou imagem sem versão fixa (unpinned_package_launcher), docker run com --privileged ou com o socket do Docker ou o sistema de ficheiros raiz montados (container_host_access), as variáveis de ambiente que carregam código adicional no arranque, como LD_PRELOAD ou --require em NODE_OPTIONS (nomes sem distinção de maiúsculas, valores nunca devolvidos; code_loading_env), entradas que apontam para o HexStrike AI, um backend de ferramentas de ataque sem autenticação (hexstrike), ficheiros de configuração graváveis pelo grupo ou por outros (world_writable_config), a opção enableAllProjectMcpServers do Claude Code (auto_trust_project_servers) e um ficheiro de configuração que reúne um servidor que lê textos escritos por outras pessoas (páginas web, e-mails, conversas, issues) e um servidor que pode executar comandos ou alterar ficheiros, a combinação de que a injeção de prompt indireta precisa (reconhecida por nomes de pacote conhecidos, sem ver o conteúdo das ferramentas do servidor; injection_exposure). Não tem argumentos, é só de leitura e não envia nada para a rede. Nunca devolve valores de ambiente ou de cabeçalhos, argumentos nem segredos, apenas nomes de servidores, o nome base de um comando, um anfitrião de URL e, se for um nome de registo simples, o nome do pacote ou da imagem. Os ficheiros que não foi possível analisar ficam listados em unreadableFiles e não foram auditados.Nenhum
audit_agent_setupLê os ficheiros de instruções e as skills dados a agentes de IA (CLAUDE.md, commands, agents e skills em ~/.claude, além de ~/.codex, ~/.cursor/rules, ~/.gemini, ~/.agents, ~/.openclaw, e CLAUDE.md, AGENTS.md, .cursorrules e semelhantes nas pastas de projeto registadas) e o openclaw.json do OpenClaw, e assinala conteúdo e definições de risco. Nos ficheiros de instruções e de skills reporta frases escondidas em carateres de etiqueta Unicode invisíveis (hidden_tag_characters), carateres de largura zero e de controlo de direção (invisible_characters), redação que anula instruções anteriores ou esconde algo do utilizador (override_instruction), descarregar e executar como curl … | sh (remote_script_exec), descodificar e executar (encoded_payload_exec), referências a armazéns de chaves e credenciais (credential_access) junto de um envio ou de um serviço de receção (credential_exfil_shape, exfil_endpoint), configuração de mecanismos persistentes (persistence_instruction), código compilado dentro de uma skill (skill_precompiled_code) e ficheiros que outros podem escrever (agent_file_writable). O que estiver dentro de um comentário HTML é avaliado mais alto, porque uma página renderizada o esconde. Para o OpenClaw reporta autenticação desativada (openclaw_auth_disabled), uma escuta acessível a partir da rede (openclaw_wide_bind), um token com menos de 24 carateres (openclaw_weak_token), uma origem universal (openclaw_origin_wildcard), interruptores dangerously… ligados (openclaw_dangerous_flag), uma lista de proxies que confia em todos (openclaw_broad_trusted_proxy, openclaw_proxy_trust_loosened), uma política de mensagens diretas ou de grupo aberta a qualquer pessoa (openclaw_open_dm), mDNS em full (openclaw_mdns_full), resultados de autodiagnóstico ocultos (openclaw_audit_suppressed) e permissões demasiado largas (openclaw_loose_permissions). Cada regra é determinística: uma correspondência significa «uma pessoa deveria ler esta linha», não «malicioso». Não aceita argumentos, é só de leitura e não envia nada para a rede. Nunca devolve o conteúdo dos ficheiros nem segredos (um token é reportado apenas pelo comprimento), apenas um nome de regra, um número de linha, um anfitrião de URL e um caminho de chave de configuração. Os ficheiros que não foi possível ler ficam em unreadableFiles e não foram auditados.
get_exposed_portsLista de portas TCP em escuta. Para cada porta exposta além do localhost, é comparada com uma base de dados de serviços perigosos conhecidos e é enviado um único HTTP GET para 127.0.0.1:port (local, fechado) para verificar CORS e cabeçalhos.Somente a sonda para 127.0.0.1
get_guard_statusO estado ativado/desativado dos protetores de resposta automática Pro (anomalia de porta/ARP/USB/Bluetooth/download Web+Mail/proteção contra ameaças DNS), o nível de proteção atual e o estado da rede confiávelSomente locais
audit_url_safetyUm julgamento de um URL para phishing/homógrafo (falsificação de Unicode)/falsificação de subdomínio de marca/TLD de alto risco/HTTP de texto simples. É síncrono e totalmente offline (analyzeURL; não segue redirecionamentos)Nenhum
get_app_helpUma pesquisa de texto completo da base de conhecimento incluída (especificações de recursos/configurações/solução de problemas/explicações de mensagens de notificação)Nenhum
audit_secretsDeteta chaves de API e chaves privadas expostas em texto, num ficheiro ou numa árvore de diretórios (os resultados são mascarados na saída).Nenhum
audit_security_logsAgrega os registos de segurança recentes (Mac: falhas de autenticação sudo, tentativas de força bruta SSH, bloqueios do Gatekeeper, deteções do XProtect, etc. / Linux: falhas de autenticação sudo, força bruta SSH, bloqueios da firewall, recusas do AppArmor, deteções do ClamAV, etc.), mascara automaticamente informação sensível como chaves de API e tokens, e devolve também os resultados da deteção de novos padrões (através de modelos de registo) e de anomalias de frequência (picos estatísticos). No Linux, se o journal não puder ser lido (por exemplo, por tempo esgotado), os zeros da contagem não significam que nada aconteceu; journalReadComplete passa a false e journalError indica o motivo.Somente locais
run_active_vuln_scanVerificação ativa de vulnerabilidades, não destrutiva e limitada a 127.0.0.1. Não contacta nenhum outro host, mas envia pedidos de rede, por isso está desativada por predefinição e requer ativação explícita nas definições.Apenas 127.0.0.1 (desativado por predefinição)
run_package_cve_scanCompara os pacotes instalados (Mac: Homebrew / Linux: dpkg, dnf, zypper, pacman) com um mapa de CVE local. Não ocorre qualquer comunicação de rede. No Linux, se a lista de pacotes não puder ser lida ou o tempo se esgotar, não é comunicado «sem correspondências»; complete passa a false e incompleteReason indica o motivo (um resultado incompleto não é um resultado limpo). No Ubuntu (20.04, 22.04, 24.04, 26.04) usa os dados próprios do Ubuntu, compara por nome e versão do pacote de origem e verifica apenas as CVE com CVSS 7,0 ou superior para as quais o Ubuntu já publicou uma correção, pelo que «sem correspondências» não significa que não existam CVE conhecidas. O resultado acrescenta ecosystem e release, cada deteção traz binary (o nome do pacote binário instalado) e as correções disponíveis apenas com o Ubuntu Pro têm proOnly. As derivadas do Ubuntu (Linux Mint, Pop!_OS e outras) e as versões não suportadas devolvem complete = false com o motivo em incompleteReason, sem mudar para os dados do Debian.Nenhum
run_package_cve_scan_languagesCompara os ficheiros de bloqueio de dependências do npm, PyPI, crates.io, RubyGems, Packagist, Go e Maven com o mesmo mapa de CVE local. Não ocorre qualquer comunicação de rede.Nenhum
get_quarantine_statusDevolve o conteúdo do cofre de quarentena: caminho original, nome da ameaça detetada, data de quarentena e tamanho.Somente locais
get_canary_statusDevolve o estado dos ficheiros-isco anti-ransomware (canary) e até aos 50 incidentes detetados mais recentes.Somente locais
get_ransomware_recovery_snapshotsDevolve os snapshots APFS de recuperação (anteriores ao dano, de deteção, manuais), o recomendado para recuperar, o estado do modo de retenção e o intervalo dos snapshots. Um snapshot de deteção pode já conter ficheiros cifrados. Apenas Pro.Somente locais
search_exec_eventsPesquisa o registo de execução de processos (eslogger) por caminho, argumentos ou ID de assinatura e devolve o estado do registador e se o registo é legível. Apenas Pro.Somente locais
get_process_treeReconstrói os antepassados e descendentes de um processo a partir do registo de execução. Apenas Pro.Somente locais
get_notification_historyDevolve o histórico das notificações enviadas pelo RoamSwitch (anomalias de auditoria de registos de segurança, deteções de ClickFix e outras) dos últimos 7 dias, mais recentes primeiro.Somente locais
get_port_anomaly_incidentsDevolve o estado da linha de base da proteção contra portas anómalas, as portas atualmente bloqueadas automaticamente e até aos 50 incidentes mais recentes. A resposta indica explicitamente que as portas atualmente bloqueadas são uma captura do estado atual, sem marca temporal, distinta do histórico de incidentes, que tem marca temporal.Somente locais
get_ssh_guard_incidentsDevolve o estado e o histórico da guarda contra força bruta SSH: se bloqueia ou apenas deteta (por exemplo, porque outra ferramenta já protege o sshd), as origens bloqueadas neste momento com o tempo restante e até aos 200 registos mais recentes (uma origem foi bloqueada, foi detetada sem bloqueio, ou um início de sessão teve êxito depois de uma série de falhas da mesma origem). Os nomes de utilizador experimentados não são guardados no Linux (apenas quantos) e no Mac são devolvidos higienizados, até 5. O Mac devolve ainda os inícios de sessão SSH recentes (origem, utilizador, método, impressão digital da chave). Só de leitura: nenhuma ferramenta MCP levanta um bloqueio. No Linux a lista de bloqueios só pode ser lida por root, pelo que quem chama sem root recebe apenas o número. Linux: a Server Edition e a Client Edition enquanto o sshd estiver em execução. Mac: apenas Pro.Somente locais
get_runtime_threat_statusIndica se este Mac está isolado (Air-Gap) devido a uma deteção de malware do XProtect e qual o incidente que a desencadeou. É o primeiro a consultar durante um Air-Gap ativo.Somente locais
get_ransomware_entropy_guard_statusDevolve se a deteção genérica de ransomware baseada em entropia está ativada e os seus incidentes recentes de rajada de entropia.Somente locais
get_honeytoken_statusDevolve se a proteção de honeytokens de credenciais está ativada e os seus eventos recentes de deteção de acesso.Somente locais
get_browser_credential_watch_statusDevolve se a monitorização de acesso a credenciais do navegador está ativada e os seus eventos recentes de deteção de acesso.Somente locais
get_forensic_evidence_bundlesDevolve a lista de pacotes de evidências forenses (lista de processos, ligações de rede, ficheiros modificados, etc.) guardados quando um Air-Gap de emergência foi acionado.Somente locais
get_vulnerability_scan_historyDevolve o último resultado registado e os dias decorridos (pass_age) para cada sonda que o run_active_vuln_scan registou.Somente locais

O campo instructions na resposta initialize também declara claramente: "Cannot change security level, isolate ports, or eject devices", comunicando assim o limite de capacidades ao LLM do lado do cliente. Os recursos MCP (roamswitch://docs/*) são também documentos Markdown só de leitura. O 1.10.0 adiciona quatro documentos de triagem no formato agentskills.io (roamswitch://skills/*, apenas em inglês, por design).

Uma atribuição incorreta descoberta — e corrigida — num exercício com um LLM local

Em setembro de 2026, durante um exercício (fire drill) em que um LLM local (Qwen 3.8 27B) investigava um incidente através deste servidor MCP, descobrimos um caso em que o modelo interpretou mal os dados devolvidos por get_port_anomaly_incidents e incorporou, por engano, um bloqueio de porta antigo e sem relação na narrativa de um incidente em curso. A causa: uma captura sem marca temporal das portas atualmente bloqueadas era devolvida na mesma resposta que o histórico real de incidentes, esse sim com marca temporal, sem nada que distinguisse os dois. A correção acrescenta campos de nota explícitos que indicam qual dos campos é uma linha temporal e qual é apenas uma captura. Ao executar novamente o mesmo cenário com o mesmo modelo após a correção, este citou o texto da nota e recusou explicitamente atribuir a porta antiga ao incidente atual. A eficácia da correção foi verificada tanto por um teste de regressão automatizado como por uma nova execução do LLM local real.

Ponto-chave: o código-fonte é público

O código-fonte deste servidor e da lógica de detecção que ele usa (monitoramento de ARP, varredura de portas, auditoria de portas, a verificação de integridade de 18 pontos, análise de segurança de URL) está publicado em github.com/lafine1211/roamswitch-mcp (MIT, um espelho do código distribuído, marcado a cada lançamento). Você pode conferir diretamente no código que ele é somente leitura, o que ele passa ao LLM e que não envia nada. Ele não inclui o helper privilegiado, o controle do pf, as proteções que agem, nem o licenciamento — esses permanecem no repositório do aplicativo.

Os testes também vêm com ele – testes de unidade espelhados, testes de entrada adversária e difusão de mutação, executado por swift test e verificado no CI. Fuzzing revelou um acidente desprotegido (JSONSerialization estouros de pilha em um objeto JSON profundamente aninhado); está consertado com uma verificação de profundidade de aninhamento antes do analisador e registrada em SECURITY_TESTING.md.

§9

O token

  • Ele usa Ed25519 (assinaturas Curve25519). A chave pública está incorporada no aplicativo (LicenseVerifier.embeddedPublicKeyBase64). A chave privada correspondente existe apenas no back-end da licença (uma variável de ambiente do Firebase Functions) e não está no repositório.
  • Os dados assinados são JSON canônico. A assinatura é criada e verificada nos bytes exatos produzidos pela codificação LicensePayload (chave de licença, nível, hash do dispositivo, emissão em, expiração, contagem de assentos) com .sortedKeys e .withoutEscapingSlashes de JSONEncoder.
  • Ele foi projetado para falhar ao fechar. Se a chave incorporada estiver ausente ou malformada, ou se a assinatura ou o JSON canônico não puder ser produzido, o resultado não será "verificado" - ele retornará invalidSignature.

Vinculação de dispositivo

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

O UUID de hardware bruto não é enviado ao servidor. No raro caso em que o IOKit não retorna um UUID, ele volta para um UUID aleatório mantido em UserDefaults. No momento da verificação, se o device_hash do token não corresponde ao hash do dispositivo atual, o resultado é deviceMismatch.

Funciona off-line

Ponto-chave: funciona sem servidor

Tudo o que validateSavedLicense() faz na inicialização é ler o token do Keychain e verifique-o localmente com a chave pública incorporada. Ele não se conecta à rede. Se o Se o servidor de licença for desligado, os recursos do Pro continuarão funcionando em um Mac que já esteja ativado. O servidor é contatado apenas para uma nova ativação e para uma desativação explícita. O aviso de desativação para o servidor é o melhor esforço - mesmo se falhar, o aviso de desativação local sempre completa.

O padrão é uma compra única (vitalícia); expires_at é verificado somente quando is_lifetime é falso. A contagem de assentos é expressa por nível – 2 para Pro pessoal, 5 para Equipe.

§10

Assinatura e reconhecimento de firma (scripts/release.sh)

  1. Execute xcodebuild archive (versão, assinatura manual, aplicativo de ID do desenvolvedor).
  2. Exporte com -exportArchive como method: developer-id.
  3. Após notarytool submit --wait, execute stapler staple no .app.
  4. Verifique com spctl -a -t exec -vv.
  5. Rezipe após grampear para produzir o artefato de atualização do Sparkle (para que o tíquete de reconhecimento de firma seja incluído e seja executado offline sem um aviso do Gatekeeper).
  6. Crie o DMG, autentique e grampeie o DMG também e verifique com stapler validate.

Atualizações (faísca 2.9.6)

Atualizar configurações em Info.plist
ChaveValor
SUFeedURLhttps://lafine.net/updates/appcast.xml
SUPublicEDKeyCNxzwijMzMCJzliId76Yl88S/9np6t/xg/zQ9YbYzHs=
SUEnableAutomaticChecksverdade
SUScheduledCheckInterval86400

Antes de uma atualização ser aplicada, a Assinatura EdDSA listada no appcast é verificada contra o SUPublicEDKey incorporado no aplicativo. A chave de assinatura privada existe apenas em o ambiente de construção. O appcast é servido por HTTPS. As atualizações Delta são verificadas por assinatura da mesma maneira.

Ponto-chave: o appcast é projetado para ser público

O fato de a URL appcast.xml ser pública não é um ponto fraco em si. Seu conteúdo são apenas números de versão, notas de lançamento, URLs de download, tamanhos de arquivo e a assinatura EdDSA de cada construção – nada de segredo. A âncora de confiança não é "o appcast é autêntico em trânsito", mas verificar a assinatura do artefato com a chave pública inserida no aplicativo. Um invasor que pode substituir completamente o appcast (MITM, sequestro de DNS, comprometimento do host da web) ainda não é possível enviar uma atualização maliciosa sem a chave de assinatura. Gatekeeper (ID do desenvolvedor e reconhecimento de firma) é um segundo portão.

Dois riscos permanecem. Uma delas é que as atualizações não chegam porque o host está inativo ou o appcast está quebrado (nenhuma instalação ruim acontece - você simplesmente não é atualizado). O outro é um ataque de congelamento que retém deliberadamente uma atualização de segurança. Sparkle 2.x rejeita downgrades e replays verificando ordenação de versões, mas uma defesa completa contra congelamento precisa de um servidor de atualização dedicado com expiração. Isso está em nossa lista para resolver.

§11

O que o RoamSwitch deve suportar

  • Sondagem e ataques de um invasor na mesma LAN ou de um dispositivo IoT comprometido. Ele responde com sigilo, auditoria de portas expostas e isolamento externo.
  • Exposição em uma rede em que você não confia. Ele interrompe automaticamente o compartilhamento de serviços e AirDrop.
  • Ataques man-in-the-middle (falsificação de ARP/NDP). A partir da versão 1.7.6, a defesa é feita em camadas: (1) o túnel VPN + kill-switch (§5, a defesa principal, que não depende da integridade de L2), (2) a fixação preventiva ARP/NDP do gateway em redes não confiáveis (§5) e (3) a detecção de falsificação com um air-gap de emergência (a posteriori). O túnel VPN e a fixação do gateway são funções Pro e vêm desativados por padrão. A detecção de falsificação vem ativada por padrão e o air-gap de emergência é ativado por padrão ao ativar o Pro.
  • Encontrar servidores de desenvolvimento e bancos de dados (Redis, MongoDB, Elasticsearch e assim por diante) expostos em 0.0.0.0 sem autenticação e bloqueá-los externamente.
  • Detectar antecipadamente atividades de criptografia não autorizadas semelhantes a ransomware e interromper todo o tráfego (não depende de assinaturas).
  • BadUSB e proteção de aprovação de teclado físico (USBKeyboardGuard, CGEventTap + IOKit) que intercepta e descarta pressionamentos de tecla de teclados/cabos USB não aprovados (Rubber Ducky, O.MG Cable, etc.) para evitar ataques automatizados de injeção de comando.
  • Um prompt de aprovação para armazenamento USB desconhecido (uma unidade não reconhecida é mantida como somente leitura em vez de ejetada imediatamente) e uma verificação automática do ClamAV no armazenamento conectado (opcional).
  • Detecta chaves de API sensíveis (OpenAI, Anthropic, GitHub, AWS, etc.) na área de transferência de forma totalmente local, evitando que sejam coladas acidentalmente em um formulário web ou chat de IA antes que isso aconteça.
  • Detecta e alerta sobre arquivos de modelos de IA perigosos em formato Pickle (risco de execução arbitrária de código) baixados do Hugging Face ou da web.
  • A assinatura de teste EICAR, padrão do setor, e reverse shells clássicas de uma linha bem documentadas dentro de arquivos baixados (StaticSignatureScanner, a partir da 1.8.7, funciona mesmo sem o ClamAV instalado).
  • Um LaunchAgent/LaunchDaemon recém-registrado que invoca diretamente um interpretador de script bruto (PersistenceMonitorGuard, a partir da 1.8.7).
  • Ataques do tipo «ClickFix», nos quais uma falsa tela de aviso induz você a executar você mesmo um comando no Terminal — detectados por meio de padrões de comando conhecidos como maliciosos no histórico do shell, acionando um air-gap de emergência (ClickFixGuard, a partir da 1.8.7, Pro, desativado por padrão).
  • Deteta e notifica o momento em que um contentor é iniciado com uma configuração Docker com risco de fuga de contentor, como o modo --privileged ou uma montagem vinculada de /var/run/docker.sock (DockerEventGuard, a partir da 1.8.9, Pro, desativado por predefinição, apenas notificação).
  • Auditoria de fugas de segredos/chaves API que agora cobre não só o texto colado, mas também a análise recursiva ao nível de pasta (SecretLeakAuditor, análise de pastas adicionada na 1.8.9, inteiramente no dispositivo).

O que decidimos não fazer

  • Não é um substituto de antivírus. O ClamAV e o XProtect são usados como auxiliares; o RoamSwitch por si só não é um detector de malware de uso geral.
  • Decidimos não solicitar o entitlement EndpointSecurity. As taxas de aprovação para uma conta individual de Apple Developer são baixas, e qualquer recurso que realmente precise de bloqueio pré-execução (por exemplo, ES_EVENT_TYPE_AUTH_EXEC) é tratado como um não objetivo encerrado enquanto isso permanecer verdadeiro. Em vez de impedir a própria execução, implementamos, sem esse entitlement, o que é viável na prática como detecção posterior ao fato: detecção por assinaturas estáticas, monitoramento de inicialização automática e defesa ClickFix.
  • Não é uma garantia. É uma camada em uma pilha de defesa profunda, não algo que “impede completamente o ransomware”. A cópia de marketing também é revisada com base nessa premissa.
  • Ele não pode proteger uma raiz ou kernel já comprometido. Se um invasor já tiver raiz, ele também poderá remover as regras de PF do auxiliar.
  • Ele não restaura a integridade L2 por si só. O pino ARP/NDP preventivo é confiável no primeiro uso – se um invasor já estiver no local antes de você se conectar, ele poderá fixar um MAC falsificado. O túnel VPN (§5) é a resposta quando você não quer fazer essa suposição: mesmo com L2 envenenado, o conteúdo é criptografado e o kill switch impede o vazamento de texto simples. Ele não substitui a espionagem de DHCP empresarial ou a inspeção dinâmica de ARP.
  • Não fornece um servidor VPN. O recurso de túnel usa uma configuração WireGuard fornecida pelo usuário; RoamSwitch não se torna um provedor de VPN.

Superfície de ataque que a instalação do RoamSwitch adiciona

Superfície de ataque adicionada e como ela é contida
Superfície de ataqueComo está contido
Um LaunchDaemon que roda como root e seu serviço mach (com.tetsuharu.RoamSwitch.Helper) A superfície de operação é fixada em HelperProtocol (tabela §3). Não há canal de comando arbitrário. As conexões são autorizadas por um requisito de assinatura de código, usando audit_token.
Se o próprio RoamSwitch.app estiver comprometido, todas as operações do auxiliar passarão para o invasor O Hardened Runtime está ativado e a aplicação não recebe privilégios desnecessários. O envio pela rede limita-se às vias da tabela do §7 (e às funcionalidades iniciadas pelo utilizador ou desativadas por predefinição descritas abaixo dessa tabela). Não é declarado nenhum entitlement de envio pela rede, mas, como a aplicação não está em sandbox, isso por si só não proíbe comunicações (medições no final do §7). Planeamos que isto seja revisto por terceiros.
Os caminhos binários do sistema que o auxiliar gera Caminhos absolutos como /sbin/pfctl são especificados diretamente, sem dependência de PATH. Os argumentos também são codificados (além dos números de porta, strings DNS, pares ARP IP/MAC e IP do endpoint VPN — todos com validação de formato).
Reescrita do Link Guard /etc/hosts (setLinkGuardSinkhole) As gravações são confinadas a uma seção gerenciada delimitada; as linhas fora dele são preservadas literalmente. Os domínios são normalizados e validados, IPs e lixo eletrônico são eliminados, a lista é limitada a 60.000 e o arquivo é gravado por meio de um arquivo temporário + substituição atômica. Ele apenas analisa casos claros (listagem de feed de ameaças ou homógrafo de marca), decididos por um mecanismo de veredicto apenas local. A própria lista de bloqueios vem do feed de ameaças com assinatura verificada (chave dedicada ao feed).
Busca de feed de ameaças do Link Guard (um GET diário somente para recebimento) Uma busca de arquivo estático sem string de consulta e sem identificador por dispositivo (apenas o User-Agent padrão). O manifesto e o corpo do feed são verificados pelo Ed25519, e uma busca que falha na verificação é descartada (sem fallback para dados não assinados). "Atualização automática: desativada" remove totalmente o caminho.
Pino ARP/NDP preventivo (lockGatewayARP) Somente as entradas de cache vizinho para "o gateway, o roteador IPv6, DNS no link" são fixadas. Os pares IP/MAC transmitidos são validados por formato e reconciliados com o conjunto de solicitações (ele nunca adiciona entradas por conta própria). Funciona apenas em redes não confiáveis; Pro, desativado por padrão. O limite de confiança na primeira utilização é indicado no §11 "O que decidimos não fazer".
Túnel VPN (wireGuardImport/Up/…, Homebrew wireguard-tools) O .conf é armazenado pelo auxiliar em 0600. O IP do endpoint é resolvido pelo aplicativo e passado para o auxiliar (o auxiliar nunca resolve um nome de host arbitrário). O destino e o conteúdo do túnel estão sob o controle do usuário. O recurso será desativado se wireguard-tools não estiver instalado. Pro, desativado por padrão. O interruptor de interrupção passa por PFRulesetCoordinator (§4).
O servidor MCP passando o estado do sistema para um LLM (um deputado confuso) É somente leitura, sem API de gravação implementada. As verificações de URL estão off-line. O domínio de configurações é referenciado como somente leitura.
Sequestrando o caminho de atualização Verificação de assinatura EdDSA (SUPublicEDKey), além de um tíquete de reconhecimento de firma incluído. O appcast é via HTTPS.

Apêndice A

Tudo o que é declarado neste documento pode ser verificado em relação ao artefato distribuído com os comandos a seguir.

Assinatura e reconhecimento de firma

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

# Se o ticket de notarização está anexado (staple)
stapler validate /Applications/RoamSwitch.app
spctl -a -t exec -vvv /Applications/RoamSwitch.app

# Assinatura do helper incluído / servidor MCP
codesign -dvvv /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchHelper
codesign -dvvv /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchMCPServer

Entitlements (confirmação de que nenhum entitlement de envio pela rede é declarado; como a aplicação não está em sandbox, isso por si só não proíbe comunicações)

codesign -d --entitlements :- /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchHelper
codesign -d --entitlements :- /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchMCPServer
# → Um dicionário de entitlements vazio. Nenhuma chave app-sandbox / network client.

Medindo o tráfego

# Execute o tcpdump em paralelo para confirmar que, na utilização normal, não há tráfego fora das vias da tabela
sudo tcpdump -i any -n 'host not 127.0.0.1' and 'not port 53'
# Nenhum tráfego além das vias da tabela (ativação de licença, verificações de atualização, dados de atualização assinados, atualizações do ClamAV, etc.)

Para uma verificação mais rigorosa e automatizada com atribuição por processo, consulte rs-zerotel-audit.sh em roamswitch-support/auditoria/

Arquitetura de defesa e testes de penetração (5 limites de defesa)

# Verifica automaticamente em lote a fronteira de autorização XPC, a prioridade Air-Gap do pf, a detecção de exposição de portas, o modo somente leitura do MCP e o monitoramento ARP
git clone https://github.com/lafine1211/roamswitch-support
cd roamswitch-support/audit
./rs-defense-audit.sh all

Teste de penetração ao vivo e auditoria de defesa multicamadas de uma VM macOS: RESULTS-DEFENSE-2026-08-30

Verificação de proteção contra ameaças DNS (Malware Quad9 e bloqueio C2)

# Consultar o domínio de teste oficial da Quad9 para verificar o bloqueio de ameaças
nslookup test.dns9.quad9.net
# → Deve retornar ** server can't find test.dns9.quad9.net: NXDOMAIN, ou seja, a resolução de nomes está bloqueada

Verificação do Web & Mail Download Guard (detecção em tempo real com ClamAV)

# Criar a string de teste antivírus padrão e inofensiva (EICAR) em Downloads
echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > ~/Downloads/eicar_test.com
# → A detecção do FSEvents aciona uma varredura imediata do ClamAV; nenhuma notificação é exibida e "assinatura de teste EICAR detectada (inofensiva)"
#   é registrada no histórico de notificações. Como o EICAR é um arquivo de teste padrão do setor e não uma ameaça real,
#   não é colocado em quarentena nem bloqueado, e o arquivo permanece no lugar (somente uma amostra real de malware é movida para o gerenciador de quarentena).

Verificação da detecção por assinaturas estáticas (sem necessidade de EndpointSecurity)

## Uma reverse shell clássica de uma linha extraída de referências públicas de segurança ofensiva (inofensiva, nunca é executada)
echo 'bash -i >& /dev/tcp/127.0.0.1/4444 0>&1' > ~/Downloads/rs_test.sh
## → Mesmo sem o ClamAV instalado, aparece uma notificação «Arquivo baixado perigoso colocado em quarentena», e o arquivo é
##   movido imediatamente para a pasta Quarantine (independentemente do banco de assinaturas do ClamAV)

Verificação do novo monitoramento de inicialização automática (PersistenceMonitorGuard)

# Criar um LaunchAgent inofensivo que invoca diretamente um interpretador de script bruto (nunca é executado de fato)
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
# → Em poucos segundos deve aparecer uma notificação «Novo registro de inicialização automática detectado». Remova-o em seguida:
rm ~/Library/LaunchAgents/com.example.selfcheck.plist

Link Guard (bloqueio de conexões de phishing)

# Se a seção gerenciada está presente (2 linhas BEGIN/END no modo Pro + padrão)
sudo grep -c 'RoamSwitch link guard' /etc/hosts

# O conteúdo da seção e a contagem 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'

# Se realmente é descartado (verificado com uma entrada da seção; 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 (resolução de nomes bloqueada)

# Confirme também que a seção acima desaparece via Menu → "Proteção de links" → "Desligado"

O feed de ameaças somente para recebimento (assinatura verificada)

# Feed e manifesto públicos (qualquer pessoa pode obtê-los e verificá-los)
curl -s https://lafine.net/updates/v1/manifest        # version/generated/threatfeed{...}
curl -sI https://lafine.net/updates/v1/manifest.sig   # → text/plain
# Apenas um GET sem query string, cookies ou identificador por dispositivo (apenas o User-Agent padrão) é enviado. Verificável executando o tcpdump em paralelo.

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

# Conecte-se a uma rede não confiável com o bloqueio preventivo ativado → verifique as entradas permanent
arp -an | grep -i permanent          # O gateway e outros IPs são listados como (permanent)
ndp -an | grep -i 'P '               # Lado IPv6 (P = permanent)
sudo cat "/Library/Application Support/RoamSwitch/gateway_arp_lock.json"  # O conjunto de mapeamentos IP→MAC atualmente fixados
# Libere o bloqueio pelo menu → o item acima deve desaparecer

Túnel VPN + kill switch (§5, 1.7.6+)

# Pressupõe wireguard-tools (via Homebrew)
brew list wireguard-tools >/dev/null && echo "wireguard-tools: OK"

# Ative a VPN em uma rede não confiável → antes que o túnel seja estabelecido, o pf está em estado de kill-switch
sudo pfctl -sr | grep -E 'block drop all|pass .*(utun|udp)'   # block drop all + um conjunto limitado de regras pass quick

# Depois que o túnel está ativo (wg-quick up já executado)
wg show                              # handshake / transfer está ativo
route -n get default | grep interface  # → utunN (a rota padrão é o túnel)
# Desative a VPN → o kill-switch é liberado e o pf retorna ao estado anterior

O próprio ajudante privilegiado

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

# As regras pf atualmente carregadas (estado real do Air-Gap / guarda de portas)
sudo pfctl -sr

# O diretório de estado do helper
ls -la "/Library/Application Support/RoamSwitch/"

A resposta do servidor MCP (verificação offline)

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"
# Retorna serverInfo e as definições das ferramentas. Nenhuma conexão de rede ocorre.

A origem e os testes do servidor MCP

git clone https://github.com/lafine1211/roamswitch-mcp
cd roamswitch-mcp
swift build -c release        # O mesmo código-fonte do binário distribuído
swift test                    # Testes unitários, de entrada adversarial, stdio e fuzzing por mutação
# Consulte SECURITY_TESTING.md para o conteúdo dos testes e problemas encontrados

Identificador do dispositivo

# O valor bruto vinculado ao token (apenas um SHA-256 salgado é enviado)
ioreg -d2 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}'
White paper de Arquitetura / Segurança do RoamSwitch v1.6 · Escopo 1.10.0 (build 116) · Lafine Systems Design · 2026-09-23
Relate quaisquer erros ou omissões em lafine.net/contact.html.