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.5 Escopo RoamSwitch 1.9.47 (build 104) Requisitos macOS 13.0+ / Apple Silicon Publicado 2026-09-15 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 Qualquer outra coisa. Não há interface para executar comandos arbitrários. Também não tem direito de envio de rede
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 Altere configurações, alterne o bloqueio, isole uma porta, ejete um dispositivo. Não abre nenhum soquete. Não envia nada pela rede

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).

§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 entreferro é evitado

  • É liberado em no máximo 10 minutos. O próprio helper continua executando releaseAirGapIfExpired() logo após a inicialização e em um temporizador de 60 segundos, mas como uma rede de segurança totalmente independente, um LaunchDaemon dedicado (AirGapFailsafe) sem nenhum KeepAlive desperta sozinho a cada 3 minutos e realiza a mesma verificação e liberação. Se o próprio helper travar e arrastar consigo seu próprio temporizador de autoliberação, essa rede de segurança não é afetada (a partir da 1.8.6).
  • 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 uma reinicialização ou um respawn do daemon. Na inicialização, o helper lê o estado em disco com reapplyFromDisk() e o restaura sozinho, na ordem air-gap, proteção de portas, padrão do sistema (a regra dos 10 minutos também se aplica aqui).
  • 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".
  • Você pode retomar o controle a qualquer momento – com o botão de elevação no modal ou simplesmente desligando o Wi-Fi.

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.

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.

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>. A CLI independente (brew install tailscale) é recomendada — 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.
  • 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 há código em nenhum lugar que colete e envie resultados de diagnóstico, informações de porta, URLs ou registros. Nenhum SDK analítico e nenhum SDK de relator de falhas estão incluídos. A única biblioteca externa é Sparkle (atualizações). O que sai para a rede são esses oito, e pronto (o sexto só se o utilizador configurar 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 host, versão do aplicativo. As informações pessoais são tratadas pela Stripe no momento da compra; o aplicativo não lida com isso
Verificação de atualização lafine.net
/updates/appcast.xml
Sparkle, a cada 24 horas e no lançamento 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}
Quando o Link Guard (§5) está ativado e a "Atualização automática" está ativada, a cada 24 horas (e no lançamento). Desativar a "atualização automática" remove esse caminho Apenas GET. Nenhuma string de consulta, nenhum cookie, nada que identifique a máquina. Um arquivo estático assinado receive-only; o manifesto e o corpo do feed são verificados com Ed25519. A chave de assinatura é dedicada ao feed — uma chave separada da atualização do aplicativo SUPublicEDKey (portanto, um vazamento está confinado a "uma lista de bloqueio inválida")
Dados de atualização da verificação de CVE de pacotes / da verificação ativa de vulnerabilidades lafine.net
/updates/v1/manifest
A cada 24 horas a partir do arranque da aplicação (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; 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. 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 Somente quando o usuário instalou o ClamAV e usa o recurso de digitalização. Ele lança freshclam Uma busca de definição padrão do ClamAV. Não contém informações derivadas do RoamSwitch
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"

"Telemetria Zero" aqui significa que não há telemetria que coleta e envia dados de uso ou resultados diagnósticos. Isso não significa que não haja tráfego de rede. Os oito caminhos do tabela acima existe. Mas cada um deles é algo iniciado pelo usuário ou um busca verificada por assinatura, receive-only e os resultados de diagnóstico, portas, URLs, e o conteúdo dos arquivos no Mac nunca sai dele.

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.

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

Isto não é apenas afirmado. Em 29/08/2026, uma instalação 1.4.7 em execução foi auditada com tcpdump + atribuição por processo (nettop / lsof / a filtrado pktap captura) + LuLu, durante uma janela de aproximadamente 2 horas, com o nível de segurança fixado em Bloqueio máximo e verificação de appcast forçada. Result: nenhum fluxo de saída atribuído a RoamSwitch, RoamSwitchHelper, ou RoamSwitchMCPServer diferente da verificação do appcast para lafine.net; o PCM os únicos soquetes do servidor eram para localhost; os dumps de direitos estão vazios.

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

Esta medição é de 1.4.7, antes do feed de ameaças do Link Guard (1.7.2, linha 3) e da VPN túnel (1.7.6, linha 5). Auditado em 1.7.2 ou posterior, você verá two somente recebimento GETs a lafine.net (o appcast e o feed de ameaças); se uma VPN está configurado você também verá o UDP para o servidor WireGuard do próprio usuário (destino e conteúdo sob o controle do usuário). Tudo isso desaparece com “Atualização automática: Desligada” e nenhuma VPN configurada.

§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 soquete. Não registra um serviço nem escuta. Ele lê uma linha do stdin, retorna uma linha do stdout e então o cliente encerra o processo.
  • Não envia nada. Todos os diagnósticos são concluídos dentro do Mac.
  • 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 ferramentas/lista de ferramentas retornam e os dados que elas retornam
FerramentaO que retornaTrânsito
get_security_reportUma verificação de 18 itens (FileVault / SIP / Gatekeeper / atualização automática / XProtect / firewall / criptografia Wi-Fi / ARP / bloqueio de ARP do gateway / auditoria de configuração SSH / auditoria de sudo NOPASSWD / portas expostas / configuração das proteções, e mais) com uma pontuação e recomendações de correção para cada itemSomente locais
get_exposed_portsUma lista de portas TCP de escuta. Para qualquer exposição além do localhost, ele faz referência cruzada a um banco de dados de serviço conhecido como perigoso e verifica CORS/cabeçalhos com uma sonda HTTP para 127.0.0.1:port (local, fechado)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).Somente locais
run_active_vuln_scanVerificação ativa de vulnerabilidades, não destrutiva e limitada a 127.0.0.1. A única ferramenta que usa a rede: desativada por predefinição, exige ativação 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.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_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_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

O campo instructions na resposta initialize também afirma claramente, "Não é possível alterar o nível de segurança, isolar portas ou ejetar dispositivos", comunicando a capacidade limite para o LLM do lado do cliente. Os recursos MCP (roamswitch://docs/*) são somente leitura Documentos Markdown também.

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 de 1.7.6, isso é dividido em camadas: (1) o túnel VPN + kill-switch (§5, a defesa primária - não depende da integridade L2), (2) um pino ARP/NDP de gateway preventivo em redes não confiáveis (§5) e (3) detecção de falsificação com um air-gap de emergência (após o fato). All Pro, desativado por padrão (a detecção está ativada por padrão).
  • 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 o aplicativo não recebe privilégios desnecessários. O tráfego de saída é limitado aos oito caminhos acima (o sexto somente se o utilizador configurar uma VPN, o sétimo somente se o emparelhamento do RoamSwitch Sensor estiver ativado). Planejamos que isso seja revisado 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 identificadores. 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

Direitos (sem permissão de envio pela rede)

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 não há tráfego durante o uso normal
sudo tcpdump -i any -n 'host not 127.0.0.1' and 'not port 53'
# Nenhum tráfego além da ativação de licença, verificações de atualização e atualizações do ClamAV

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 identificadores é 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 5 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.5 · Escopo 1.9.28 (build 85) · Lafine Systems Design · 2026-09-15
Relate quaisquer erros ou omissões em lafine.net/contact.html.