Lafine Systems Design · Documento tecnico

Libro bianco sull'architettura e la sicurezza di RoamSwitch

Questo documento spiega con quali privilegi viene eseguito RoamSwitch e cosa fa a quel limite. Non contiene linguaggio di marketing; tutto quanto indicato qui può essere verificato rispetto all'app di spedizione binario e il suo comportamento reale.

Versione v1.6 Ambito RoamSwitch 1.11.14 (build 161) Requisiti macOS 13.0+ / Apple Silicon Pubblicato 2026-09-23 Team ID GV76B6G4YU

§1

RoamSwitch è un'app della barra dei menu per Mac. A seconda di quanto ti fidi della rete che sei attualmente connesso, cambia automaticamente il firewall macOS, i servizi di condivisione, AirDrop, e DNS. Controlla anche lo spoofing ARP, le porte aperte verso l'esterno, l'archiviazione USB e attività di crittografia simile a un ransomware e quando rileva qualcosa di pericoloso arriverà fino al punto tagliare il traffico con il filtro dei pacchetti (pf) — un air-gap di emergenza.

In altre parole, RoamSwitch installa un demone con privilegi di root e, se lo volesse, potrebbe bloccare tutto traffico di rete sul Mac. È stato costruito da una sola persona e "per favore fidati di me" non è sufficiente giustificare quel privilegio. Quindi, invece, questo documento spiega il progetto in un formato che puoi verificare.

Per chi è questo

  • Gli ingegneri decidono se installarlo
  • Ricercatori e giornalisti di sicurezza che vogliono comprendere i dettagli prima di una recensione o di un articolo
  • Il personale addetto alla sicurezza presso le aziende partner sta valutando un'implementazione interna o un raggruppamento OEM

Cosa non copre questo documento

Non riguarda l'ottimizzazione della soglia di rilevamento, le statistiche sui falsi positivi o le procedure dettagliate dell'interfaccia utente. Ciò che spiega sono quattro cose: privilegi, limiti del processo, flusso di dati e crittografia. Le specifiche delle funzionalità stesse si trovano nella risorsa MCP in bundle roamswitch://docs/features e nella guida in-app.

Cosa viene divulgato e in che misura (politica di divulgazione)

Questo documento è scritto partendo dal presupposto che un utente malintenzionato abbia già il file binario distribuito. Ogni URL, identificatore, percorso file, protocollo XPC e chiave pubblica incorporata dell'endpoint visualizzato qui può essere estratto dalla spedizione RoamSwitch.app in pochi minuti con strings, codesign -d o un proxy di traffico. Scriveteli qui dunque non dà nulla di nuovo all'attaccante. L'unica cosa che avanza è la comprensione del revisore.

D'altra parte, l'implementazione lato server che non può essere vista dal binario è un limite di velocità soglie, chiavi, endpoint di amministrazione, schema DB, layout del progetto Firebase non sono inclusi. Anche la progettazione dell'integrazione OEM e partner non rientra nell'ambito di questo contesto ed è trattata in un documento separato documento interno. "Cosa puoi imparare osservando il cliente" è la riga di divulgazione per questo documento.

Nota aggiuntiva

Questo documento corrisponde alla fonte della versione indicata in alto. Dove il comportamento cambia in una versione successiva, il documento viene rivisto e il numero di versione e la build di destinazione vengono aggiornati. Se trovi una discrepanza tra il testo e il codice, faccelo sapere a lafine.net/contact.html.

§2

RoamSwitch.app è composto da tre eseguibili. Solo uno di essi è privilegiato; il gli altri due vengono eseguiti con diritti di utente di accesso. Tutti e tre vengono forniti con Hardened Runtime abilitato, ID sviluppatore firmato e autenticato.

Diagramma dei componenti di RoamSwitch e dei confini di fiducia Client MCP Claude Desktop / Code MCPServer Privilegio utente Sola lettura RoamSwitch.app Privilegio utente Barra dei menu / Monitoraggio / UI Helper root / LaunchDaemon SMAppService.daemon Binari di sistema pfctl / socketfilterfw … stdio (JSON-RPC) Sola lettura * XPC mach service Autorizzato tramite firma del codice Esecuzione del processo
* MCPServer non si connette all'app stessa; legge direttamente il dominio delle preferenze condivise e i moduli di monitoraggio (§8).
Privilegi e ruolo di ciascun componente
EseguibilePrivilegioPuò fareNon posso farlo
RoamSwitch
.app
Utente di accesso Monitora lo stato della rete, esegui la diagnostica, disegna l'interfaccia utente, chiama l'helper su XPC, modifica AirDrop tramite defaults, avvia ClamAV (opzionale) Gestisci direttamente il firewall, pf o i demoni di sistema (tutto questo passa attraverso l'helper)
RoamSwitch
Aiuto
root Solo le operazioni elencate in HelperProtocol (tabella §3): firewall/stealth, caricamento/scaricamento dei demoni di condivisione, applicazione del ruleset pf, modifica DNS, invio segnali ai processi Tutto il resto. Non esiste un'interfaccia per eseguire comandi arbitrari. Non è dichiarato alcun entitlement di invio in rete, ma poiché non è sottoposto a sandbox, ciò di per sé non vieta le comunicazioni (la misurazione del traffico in uscita è nel §7)
RoamSwitch
MCPServer
Utente di accesso Leggere e formattare i valori diagnostici, effettuare ricerche nella knowledge base locale; i risultati vengono restituiti al client tramite stdio Modificare le impostazioni, attivare/disattivare il blocco, isolare una porta, espellere un dispositivo. Per impostazione predefinita non apre socket e non invia nulla in rete (le uniche eccezioni sono la verifica delle firme npm, disattivata per impostazione predefinita, e la diagnosi delle vulnerabilità rivolta a questo stesso Mac, 127.0.0.1; vedi §8). Non è dichiarato alcun entitlement di invio in rete, ma poiché non è sottoposto a sandbox, ciò di per sé non vieta le comunicazioni

Diritti e firma

  • Tutti e tre gli obiettivi hanno ENABLE_HARDENED_RUNTIME = true.
  • La sandbox dell'app è disabilitata (com.apple.security.app-sandbox = false). I diritti dell'helper e di MCPServer sono dizionari vuoti.
  • Le build di distribuzione sono firmate dall'applicazione ID sviluppatore, autenticate da Apple e pinzate (§10).
Compromesso di design

L'app Sandbox non viene utilizzata. RoamSwitch deve leggere l'UUID hardware da IOKit, utilizzare CoreWLAN e DiskArbitration, enumerare i socket di ascolto di altri processi (lsof), aprire un XPC connessione a un LaunchDaemon e generare file binari di sistema. Niente di tutto questo è possibile all'interno del sandbox, quindi viene lasciato disabilitato.

Quattro cose lo compensano. Innanzitutto, runtime rafforzato. In secondo luogo, firma dell'ID sviluppatore e notarile. In terzo luogo, solo un eseguibile viene eseguito come root, l'helper, e cosa può fare quell'helper è fissa ed enumerata (tabella §3). In quarto luogo, le connessioni con l'helper sono limitate dal codice firma (§3).

§3

Come viene registrato

L'helper è registrato come LaunchDaemon utilizzando SMAppService.daemon(plistName:). Il plist incorporato nell'app (Contents/Library/LaunchDaemons/com.tetsuharu.RoamSwitch.Helper.plist) dichiara solo Label, BundleProgram, una singola voce MachServices e AssociatedBundleIdentifiers. A causa di come funziona SMAppService, la registrazione non è nemmeno possibile a meno che l'app non sia in /Applications. Sul primo registrazione, l'helper non diventa attivo finché l'utente non lo approva manualmente nel sistema Impostazioni.

Di quali connessioni accetta (ClientValidator)

L'aiutante controlla il processo di connessione in NSXPCListener shouldAcceptNewConnection e distribuisce HelperProtocol solo a quelli che passare. L'ispezione utilizza audit_token anziché il PID evitare il riutilizzo del PID e il TOCTOU.

# Requisiti di firma del codice richiesti dalla build Release ai client che si connettono
identifier "com.tetsuharu.RoamSwitch"
  and anchor apple generic
  and certificate leaf[subject.OU] = "GV76B6G4YU"

Questo requisito viene verificato con SecCodeCopyGuestWithAttributes e SecStaticCodeCheckValidity e la connessione viene interrotta se non passa. Solo Le build DEBUG rilasciano il pin ID squadra, per comodità di sviluppo. Ciò che effettivamente viene spedito è sempre a Rilascia la build.

Punti chiave del confine

La sicurezza dell'aiutante si basa su questo unico requisito di firma del codice. Tutto ciò che lo soddisfa (a firmato correttamente RoamSwitch.app) può chiamare ogni operazione nella tabella seguente. C'è nessun canale per dargli comandi arbitrari, ma le operazioni in quella tabella non sono deboli stessi. Se lo stesso RoamSwitch.app viene rilevato, queste operazioni passano al attaccante.

Cosa può fare l'aiutante (l'elenco completo)

Le operazioni privilegiate definite in Shared/HelperProtocol.swift sono tutte. Non esiste alcuna API privilegiata che non sia elencata qui.

HelperProtocol: tutto ciò che viene eseguito come root
MetodoCosa faBinario/API richiamata
impostaBloccaTutto(_:) Attiva o disattiva il firewall dell'applicazione e la modalità invisibile /usr/libexec/ApplicationFirewall/socketfilterfw
--setblockall / --setstealthmode
getBlockAllStatus(...) Legge i valori attuali di cui sopra socketfilterfw --getblockall
setSharingServicesAbilitato(_:) scaricare/caricare i demoni SSH/SMB/Condivisione schermo. Quando si ferma, registra solo "quelli che erano in esecuzione" e ripristina solo quelli /bin/launchctl list / unload -w / load -w
(fisso su tre: ssh.plist / com.apple.smbd.plist / com.apple.screensharing.plist)
abilitaNetworkAirGap(...)
disableNetworkAirGap(...)
Applica e rimuove il blocco completo di emergenza (block drop all). Passa per PFRulesetCoordinator (§4) /sbin/pfctl -f / -e / -sr
setGuardedDevServerPorts(_:) Utilizza pf per bloccare solo le connessioni external alle porte del server di sviluppo specificate (passa l'host locale). Passare un array vuoto li solleva tutti /sbin/pfctl (anche tramite il Coordinatore)
setSecureDNSServers(_:)
restoreOriginalDNSServers(...)
getCurrentDNSServers(...)
Cambia il DNS dei servizi di rete attivi in DNS che bloccano il malware (Quad9 9.9.9.9 / Cloudflare 1.1.1.2), eseguendo il backup delle impostazioni originali e ripristinandole /usr/sbin/networksetup
-listallnetworkservices / -getdnsservers / -setdnsservers
setLinkGuardSinkhole(_:) Protezione del collegamento (§5). Scrive i domini di phishing/truffa specificati in una sezione gestita delimitata di /etc/hosts come 0.0.0.0, quindi svuota la cache DNS. Un array vuoto rimuove la sezione. I domini vengono normalizzati e deduplicati; IP e spazzatura vengono eliminati; limitato a 60.000; scritto tramite un file temporaneo + sostituzione atomica riscrive /etc/hosts (FileManager.replaceItemAt)
/usr/bin/dscacheutil -flushcache
/usr/bin/killall -HUP mDNSResponder
lockGatewayARP(_:)
unlockGatewayARP(...)
getGatewayARPLockStatus(...)
Blocco preventivo ARP/NDP del gateway (§5). Blocca le mappature IP → MAC fornite (il gateway IPv4, il router predefinito IPv6, i risolutori DNS on-link) come voci permanent della cache dei vicini. I formati IP e MAC sono convalidati; un modello di riconciliazione (i pin non presenti nella richiesta vengono rimossi). Il set di pin viene mantenuto su gateway_arp_lock.json /usr/sbin/arp -s / -d
/usr/sbin/ndp -s / -d
wireGuardImport(_:)
wireGuardForget(...)
Tunnel VPN (§5). L'assistente salva/elimina il testo WireGuard .conf in 0600 sola scrittura del file
wireGuardUp(endpointIPv4:endpointIPv6:porta:)
wireGuardDown(...)
wireGuardStatus(...)
Porta il tunnel su/giù/leggi lo stato. Il nome host dell'endpoint è risolto dall'app e l'IP viene passato all'helper (il DNS dell'helper può essere interrotto dal kill switch) Homebrew wg-quick up/down, wg show (wireguard-tools; disabilitato se non installato)
terminateProcess(pid:forceKill:) Sospende (SIGSTOP) o chiude forzatamente (SIGKILL) un processo. Utilizzato per contenere processi simili a ransomware. Solo pid > 1 chiamata di sistema kill(2) (non un sottoprocesso)
getHelperVersion(...) Restituisce la stringa della versione dell'helper (utilizzata per i controlli di compatibilità dell'app) —
Compromesso di design

terminateProcess può inviare SIGKILL a qualsiasi processo, purché pid > 1. setSecureDNSServers accetta qualsiasi stringa del server DNS. Questo è la larghezza necessaria all'elemento, ma non è stretta. Giudicalo con la consapevolezza che il il controllo della firma del codice di fronte ad esso (ClientValidator) è l'unico cancello.

Stato all'interno dell'aiutante

  • HelperTool.shared è una singola istanza condivisa tra connessioni. In passato si trattava di un'istanza separata per connessione, quindi un contenimento di emergenza che apriva una nuova connessione poteva colpire una gara che aveva perso traccia di "quali servizi ripristinare".
  • Il servizio di condivisione e i backup DNS vengono modificati solo su una coda seriale (stateQueue).

Separazione dei privilegi nell’helper ed eccezione per la lettura del log SSH

Subito dopo l'avvio, l'helper si impone da solo un limite applicato dal kernel (un sandbox in esecuzione, distinto dall'entitlement App Sandbox citato sopra), che ereditano anche i processi figli che avvia. Questo limite impedisce di sostituire programmi di sistema, registrare un LaunchDaemon o un LaunchAgent, riscrivere la configurazione di sudoers, PAM o sshd, scrivere direttamente nell'archivio locale delle directory ed eseguire programmi che si trovano in posizioni in cui possono scrivere gli utenti, i volumi esterni o le cartelle temporanee. Non impedisce le modifiche agli account fatte tramite Open Directory (dscl) e non limita il traffico di rete (non è questo il suo scopo).

Sotto questo limite, /usr/bin/log non viene eseguito. Su macOS 26, log si rifiuta di funzionare dentro un sandbox (log: Cannot run while sandboxed) e anche come nobody (Must be admin). Quindi né l'app dell'utente connesso né l'helper root dentro il suo sandbox possono leggere il log di sshd (il log unificato). Il rilevamento della forza bruta SSH deve leggere quel log, perciò la separazione dei privilegi ha un'eccezione.

L'eccezione è un piccolo processo root: prima di entrare nel proprio sandbox, l'helper avvia lo stesso binario con --ssh-log-reader. Quel processo accetta solo un comando di un byte (avvio o arresto) e non fa altro che lanciare /usr/bin/log con argomenti fissi (log stream --style ndjson --level info e un filtro fisso che lascia passare solo le righe di sshd). Non passa variabili d'ambiente e chi chiama non ha modo di scegliere argomenti o un comando. Non legge né interpreta il log; l'output va in una pipe consegnata dall'helper.

Le righe del log contengono stringhe che un attaccante può scrivere, come il nome utente, quindi l'helper root non le interpreta. Passa ogni riga a un worker di analisi che gira come nobody in un sandbox che vieta l'accesso alla rete, la scrittura di file e l'avvio o la duplicazione di processi, e l'indirizzo di origine viene preso dalla fine della riga (IP port N), su cui il nome utente non può influire. Su un Mac con il login remoto disattivato, questo processo non avvia log e non legge nulla. Le operazioni SSH richiamabili tramite l'XPC dell'helper sono cinque: applicare le impostazioni, ottenere stato, record e accessi, e rimuovere un blocco. Nessuna accetta un comando.

§4

Quattro funzionalità toccano pf: l'air-gap di emergenza, la protezione della porta del server di sviluppo, il tunnel VPN Kill-switch WireGuard (§5, 1.7.6+) e il suo kill-switch Tailscale (§5, 1.8.0+ — solo il backend selezionato). Vanno sempre tutti attraverso un unico punto di ingresso, PFRulesetCoordinator, e non eseguire mai pfctl -f stessi.

Perché esiste un unico punto di ingresso

In precedenza, le due funzionalità caricavano ciascuna regole con pfctl -f in modo indipendente, competendo sull'unico set di regole principali di pf. Se la regola ristretta della guardia del porto fosse block ... port {…} caricato dopo block drop all dell'air-gap, potresti ritrovarti in uno stato in cui screen ha detto "isolato" ma il Mac era ancora raggiungibile. Questo bug è stato trovato attaccando effettivamente il file macchina da un altro host ed è stato corretto nella versione 1.4.3 (la storia è scritta in docs/marketing/zenn/03_lan_side_attack_test.md).

Come funziona adesso

  • Ricostruito completamente ogni volta. L'intero set di regole richiesto viene ricostruito dallo stato corrente e applicato in un colpo solo. Non viene mai applicato come diff.
  • Una coda seriale. Ogni modifica pf viene eseguita sullo stesso DispatchQueue, quindi, indipendentemente dal fatto che provenga da una connessione XPC, dall'avvio dell'helper o dal timer di failsafe, le modifiche vengono elaborate in ordine.
  • L'ordine di priorità è il seguente (gli elementi più in alto hanno la precedenza).
    1. Air-gap di emergenza → set skip on lo0 e block drop all (nient'altro viene considerato)
    2. Kill-switch VPN → block drop all più pass quick solo per: lo, l'interfaccia tunnel (utunN), l'handshake UDP agli IP dell'endpoint bloccato, DHCP e ICMP
    3. Guardia del server di sviluppo → block drop in quick proto tcp ... port { … }
    4. Nessuno → ricarica /etc/pf.conf e riporta pf al suo stato originale
  • Rileggi dopo l'applicazione. pfctl -sr rilegge le regole per confermare che block drop all, o la regola di ciascuna porta, è effettivamente caricata. Un caso in cui pfctl -f è stato silenziosamente ignorato non viene considerato un successo.
  • Il file temporaneo viene scritto in un percorso contenente UUID ed eliminato una volta applicato (la versione 1.4.5 ha abbandonato il percorso fisso a favore di uno difficile da indovinare). La directory dello stato è /Library/Application Support/RoamSwitch.
Comportamento dell'API

Le risposte XPC da enableNetworkAirGap e setGuardedDevServerPorts, (Bool, String?), segnala se l'operazione è passata completamente attraverso il rilettura. Il chiamante (come ARPSpoofContainmentManager) riprova in caso di errore e se continua a fallire e visualizza direttamente il messaggio sullo schermo: "Il traffico non è ancora stato fermato. Spegni Wi-Fi adesso."

§5

I tipi di blocco sono diversi

Tipi di blocco
GentileAmbitoInnescoloopback
Intercapedine d'emergenza Arresta tutto il traffico, in entrata e in uscita Quando viene rilevata un'attività di crittografia simile a un ransomware. Al rilevamento di ARP spoofing, si attiva immediatamente solo in modalità Lockdown; su reti bilanciate/attendibili, notifica invece (lo si attiva manualmente). Su un pattern di comando noto di tipo «ClickFix» nella cronologia della shell, come la decodifica base64 direttamente verso una shell o AppleScript (1.8.7+, Pro, disattivato di default). Non utilizzato per la protezione quotidiana fuori casa Superato con set skip on lo0
Kill switch VPN Tutto tranne il tunnel, il suo handshake, DHCP e ICMP Quando il tunnel VPN (§5, Pro, disattivato per impostazione predefinita) è attivo e ti colleghi a una rete non attendibile. Trattenuto finché il tunnel non viene stabilito (e mentre è giù) Superato con set skip on lo0
Protezione della porta del server di sviluppo Solo external connessioni TCP alle porte indicate Isolamento manuale con un clic o blocco automatico quando viene rilevata una porta di ascolto sconosciuta (Pro) Da localhost, invariato
Protezione quotidiana delle reti non affidabili Firewall e stealth, condivisione interrotta (§6). pf non è utilizzato Quando ti connetti a una rete non sei registrato —
Guardia dei collegamenti Solo risoluzione dei nomi di domini phishing/truffa (0.0.0.0 tramite /etc/hosts). pf non è utilizzato Una destinazione nel feed delle minacce o un omografo di un marchio. Attivo per impostazione predefinita (Pro) Non influenzato
Blocco preventivo ARP/NDP Solo il MAC del gateway, del router IPv6 e il DNS on-link (cache dei vicini). pf non è utilizzato Quando si accede a una rete non attendibile (Pro, disattivata per impostazione predefinita). Re-pinnato ad ogni modifica della rete Non influenzato

Come l'Air-Gap non si prolunga e non può essere aspettato

  • Il modo in cui termina dipende da ciò che lo ha attivato (da 1.9.52). Un'esca antiransomware manomessa, un rilevamento XProtect confermato e un isolamento manuale scelto da un avviso ARP sono di «alta affidabilità»; ClickFix, le anomalie di porta e simili sono di «bassa affidabilità». Un isolamento a bassa affidabilità si rimuove comunque da solo al massimo dopo 10 minuti. Uno ad alta affidabilità non si rimuove a 10 minuti: una rimozione completa basata solo sul tempo permetterebbe a un attaccante di limitarsi ad aspettare.
  • Un isolamento ad alta affidabilità resta completamente chiuso fino a un limite (1 ora per impostazione predefinita, da 5 minuti a 24 ore nelle impostazioni) e lì verifica la causa. Nel caso del ransomware considera la causa scomparsa solo se il processo sospetto è stato identificato ed è terminato e le esche non sono cambiate dal rilevamento. Se non è possibile confermarlo, passa alla «modalità ridotta»: le nuove connessioni restano bloccate (tranne loopback e DHCP) e viene ripristinata solo la radio Wi-Fi, così da poter vedere cosa accade. Non si apre mai per il solo trascorrere del tempo. Si apre solo se lo rimuovi tu o se la causa è confermata scomparsa.
  • All'inizio di un isolamento vengono interrotte anche le connessioni in uscita già aperte. La tabella degli stati di pf viene svuotata (pfctl -F states), fermando il traffico iniziato prima dell'isolamento. Lo stesso avviene al passaggio alla modalità ridotta.
  • Un isolamento causato da ARP spoofing viene rimosso automaticamente solo quando si conferma, senza alcun traffico, che la causa è scomparsa. Il controllo legge soltanto la cache ARP (non invia pacchetti) e considera la causa scomparsa quando il MAC attendibile del gateway viene visto in 3 controlli consecutivi che coprono almeno 60 secondi. Durante l'isolamento, però, la voce ARP del gateway può scomparire dalla tabella dei vicini (confermato su un Mac reale) e allora la causa non può essere confermata. In quel caso resta in modalità ridotta finché non lo rimuovi tu.
  • Anche se l'helper si blocca, il controllo di rimozione non si ferma. Un LaunchDaemon dedicato senza alcun KeepAlive (AirGapFailsafe) si avvia da solo ogni 3 minuti e prende la stessa decisione dell'helper (bassa affidabilità: rimozione a 10 minuti; alta affidabilità: verifica della causa al limite, poi modalità ridotta). Se l'helper stesso si blocca e perde il proprio timer di rimozione, questa rete di sicurezza non ne risente (da 1.8.6; la decisione in base all'affidabilità da 1.9.52).
  • Questo daemon dedicato non tocca mai la configurazione di avvio dell'helper interattivo stesso. Un tentativo precedente (1.8.5) usava il KeepAlive.PathState proprio dell'helper per questo ripristino, ma a volte interferiva con una normale chiusura dell'app causando un riavvio indesiderato, per cui è stato annullato a favore dell'attuale daemon completamente indipendente.
  • Il controllo del timestamp utilizza una combinazione ibrida di tempo di attività monotono (ProcessInfo.systemUptime) e ora dell'orologio. Normalmente privilegia il tempo di attività monotono, così le correzioni NTP e i cambi manuali dell'ora non hanno effetto. Ricorre all'ora dell'orologio solo quando il tempo di attività non è affidabile — subito dopo un riavvio, rilevato quando il valore attuale è inferiore a quello memorizzato.
  • Viene riapplicato dopo un riavvio o la ricreazione del demone. All'avvio l'helper legge lo stato dal disco con reapplyFromDisk() e ripristina da sé Air-Gap, protezione delle porte e impostazioni di sistema predefinite, in quest'ordine. Riprende anche lo stato dell'isolamento (affidabilità, completo o ridotto) e il tempo trascorso; il conteggio prosegue dalla prima attivazione (confermato su un Mac reale riavviando l'helper).
  • Un sollevamento fallito viene trattato come un fallimento. Se block drop all non può essere effettivamente rimosso, il timestamp viene riscritto in modo che il timer di failsafe e il nuovo tentativo abbiano qualcosa su cui convergere. Lo schermo non mostra mai erroneamente "sollevato".
  • Puoi riprendere il controllo in qualsiasi momento: con «Rimuovi isolamento Air-Gap» nella barra dei menu, con il pulsante di rimozione nella finestra modale, o semplicemente spegnendo il Wi-Fi. La rimozione non richiede Pro (funziona anche se Pro scade nel mezzo di un isolamento).

Come viene recuperato un falso positivo Port-Guard

Il blocco automatico delle porte sconosciute (Pro, attivo per impostazione predefinita una volta attivato Pro) può interrompere un traffico legittimo Ricevitore LAN: LocalSend, Syncthing, qualsiasi cosa avviata dopo l'attivazione della guardia. Quando quello accade, consentilo dal pulsante "Consenti" sul banner di notifica o sulla riga corrispondente la schermata "Porte esposte". Un eseguibile consentito una volta viene registrato come noto e non viene bloccato ancora (PortAnomalyGuard.allowPort(_:)). Tieni presente che gli interpreti di script generici (Python, Node.js, Netcat, ecc.) hanno un ambito rigorosamente path:port anziché binario da solo per prevenire gli attacchi di chi vive fuori terra. Demoni di sistema Apple che soddisfano anchor apple (rapportd, che sostiene Handoff e simili) non lo sono guardato in primo luogo.

Esca canary ransomware autoriparante e soppressione del contenimento

Se i file esca canary vengono manomessi o rinominati e attivano l'air-gap di emergenza, rilasciando il contenimento dopo aver verificato la sicurezza rigenera automaticamente i file Canary mancanti o danneggiati al loro hash di base autentico generato da modelli incorporati, ripristinando immediatamente la sorveglianza ininterrotta della coda (prevenendo la contaminazione della linea di base avversaria). Inoltre, durante il contenimento attivo (mentre è visualizzata la modalità di emergenza), gli avvisi di notifica ridondanti e gli eventi di riattivazione dal polling periodico dell'integrità in background vengono automaticamente soppressi per evitare di distrarre l'utente durante la risposta agli incidenti.

Rilevamento generico del ransomware basato sull'entropia — 1.10.0, Pro, attivo per impostazione predefinita

Monitora le cartelle Documenti, Scrivania, Download e Immagini tramite FSEvents e scatta quando scritture con entropia di Shannon superiore alla soglia (7,92) arrivano in un burst di 20 o più file entro 5 secondi. A differenza dell'approccio canary (file esca), non dipende dalla manomissione di un file esca, quindi rileva anche la cifratura in luoghi privi di file canary. Solo al raggiungimento della soglia usa lsof per identificare il processo e confrontarlo con una lista consentiti (cp, mv, ditto, tar, ffmpeg, Xcode, Time Machine, browser principali, ecc.). Alla rilevazione, attiva lo stesso blocco automatico della rete, tramite RansomwareContainmentManager, usato dalla guardia canary.

L'edizione Linux può tracciare i burst per processo perché fanotify associa un PID a ogni evento di scrittura. macOS non dispone né di fanotify né (per scelta) di EndpointSecurity, quindi questa implementazione legge solo il percorso tramite FSEvents e conta il burst globalmente, indipendentemente dal processo. Limite: quando più processi legittimi scrivono contemporaneamente grandi quantità di file ad alta entropia (ad esempio lavori di compressione o cifratura concorrenti), qui è più probabile un falso positivo rispetto all'edizione Linux.

Snapshot precedenti al danno per il recupero da ransomware (Mac)

Uno snapshot fatto al momento del rilevamento contiene già i file cifrati prima di esso, quindi non è una via di ritorno. RoamSwitch crea perciò snapshot locali APFS a intervalli regolari, indipendentemente dal rilevamento (ogni 6 ore per impostazione predefinita; disattivato, 1, 3, 6, 12 o 24 ore). Quando scatta un rilevamento, i nuovi snapshot precedenti al danno e la pulizia si fermano fino a 7 giorni (modalità di conservazione), così lo stato cifrato non scalza l'ultima generazione precedente alla cifratura. Il recupero è sempre manuale: solo i file scelti vengono copiati in ~/RoamSwitch-Recovered/. I file attuali non vengono mai sovrascritti e non è previsto il ripristino dell'intero volume. Per copiare occorre montare lo snapshot, quindi l'helper privilegiato richiede l'accesso completo al disco (gli snapshot vengono creati comunque senza). macOS può eliminare da solo gli snapshot locali, per esempio quando lo spazio libero scarseggia, e non c'è garanzia che durino oltre circa 24 ore. La finestra di recupero è una funzione Pro (la creazione degli snapshot funziona anche senza Pro).

Pacchetto di prove forensi — 1.10.0

Quando inizia un Air-Gap di emergenza — attivato dalla guardia canary, dalla guardia ransomware basata sull'entropia o simili — una coda in background che non blocca il contenimento stesso salva un'istantanea dell'elenco processi (ps), delle connessioni di rete (lsof -i), dei file modificati negli ultimi cinque minuti e dei file aperti dal processo sospetto, in ~/Library/Application Support/RoamSwitch/incident-evidence/<timestamp>/manifest.json. Il manifest registra lo SHA-256 di ogni artefatto, così da poter rilevare manomissioni successive. L'elenco dei pacchetti è disponibile tramite lo strumento MCP get_forensic_evidence_bundles. Limite: essendo costruito senza un entitlement EndpointSecurity, non acquisisce tracce di pacchetti né istantanee di memoria/descrittori di file del processo.

Registrazione delle esecuzioni dei processi (Mac, Pro, disattivata per impostazione predefinita)

L'helper privilegiato avvia come processo figlio il /usr/bin/eslogger incluso in macOS (exec, fork, exit) e registra gli avvii dei processi solo su questo Mac. Non richiede l'entitlement EndpointSecurity (non chiama mai es_new_client) e RoamSwitch deve essere consentito in Accesso completo al disco (Impostazioni di Sistema; se non è in elenco, aggiungi /Applications/RoamSwitch.app con «+»). Finché manca il permesso, mostra lo stato «non disponibile», attende e ricontrolla ogni 60 secondi (non passa a un polling oneroso). Si limita a notificare. Non blocca l'esecuzione né la comunicazione e non isola.

Notifica questi dieci comportamenti: un browser, un'app Office o di posta che avvia direttamente una shell; l'esecuzione di un binario non firmato da una cartella temporanea o da una posizione in quarantena; un comando su una riga che scarica e reindirizza a una shell (combinato con un URL a IP grezzo, base64, eval o verifica TLS disattivata); osascript che usa base64 o eval; l'esecuzione di un file subito dopo la rimozione del suo attributo di quarantena; l'avvio di un binario non firmato da una registrazione di avvio automatico appena scritta (LaunchAgent o LaunchDaemon); un processo genitore non firmato da Apple che legge un segreto del portachiavi (come security find-generic-password -w); una riga singola python3, perl, ruby, php o pwsh con -c/-e/-Command in cui una decodifica base64/eval e una chiamata a curl, wget, exec o system compaiono entrambe sulla stessa riga (da 1.10.0 — il solo uso di base64 o eval non attiva questo rilevamento); e l'avvio di un processo con la variabile d'ambiente DYLD_INSERT_LIBRARIES impostata (da 1.10.0 — per evitare fughe di segreti, viene letto solo il valore di questa unica variabile; i processi firmati da Apple sono esclusi); e una riga di comando che indica direttamente un file di credenziali esca collocato da RoamSwitch (come ~/.aws/credentials o ~/.env.backup, riconosciuto dal marcatore al suo interno), ad esempio cat, tar o curl -F; i file reali non corrispondono mai e le letture tramite una libreria restano alla sorveglianza degli honeytoken. Ogni rilevamento porta un ID tecnica MITRE ATT&CK.

Solo i rilevamenti di gravità alta diventano banner, e quelli dello stesso tipo sono raggruppati in uno ogni 10 minuti (ogni evento resta nel registro). Gli strumenti di sviluppo come Claude Code e la CLI di GitHub che leggono le proprie credenziali non vengono notificati per impostazione predefinita. Se le legge un altro programma, l'azione «Consenti questo programma» della notifica consente solo quella voce (per Team ID se è firmato, altrimenti per nome dell'eseguibile, abbinato al nome della voce).

Il registro è salvato leggibile solo da root (cartella 0700, file 0600) e conserva per impostazione predefinita 200 MB e 14 giorni. Le variabili d'ambiente non vengono salvate, ma gli argomenti della riga di comando possono contenere segreti. Ogni segmento è sigillato in una catena di hash collegata all'hash del segmento precedente, e «Verifica catena» rileva modifiche, segmenti mancanti e sigilli rimossi (ciascuno confermato su un Mac reale). Questo rileva la manomissione, non la impedisce. L'eliminazione o il ripristino del segmento più recente non è rilevabile. La lettura passa per l'XPC dell'helper, e MCP passa per l'app (con nuova verifica di Pro).

Limiti: le esecuzioni prima dell'avvio dell'helper o prima dell'attivazione della registrazione non vengono registrate. eslogger osserva a posteriori e non può bloccare. Con carichi come migliaia di avvii di processo al secondo, alcuni eventi vanno persi (il conteggio è mostrato a schermo e il funzionamento continua). Questa registrazione non sostituisce un EDR (rilevamento e risposta sugli endpoint).

Honeytoken delle credenziali — 1.10.0, Pro, attivi per impostazione predefinita

Colloca un file di credenziali esca, contrassegnato con un valore sentinella, in ciascuna di queste tre posizioni: ~/.aws/credentials, ~/.ssh/id_rsa_backup e ~/.docker/config.json. Non sovrascrive mai un file reale già presente in quel percorso. Il rilevamento degli accessi combina lo stesso meccanismo kqueue (DispatchSource.makeFileSystemObjectSource) usato dalla guardia canary con un polling della data di accesso ogni 30 secondi, poiché senza EndpointSecurity un evento open() non può essere intercettato in modo affidabile. Il rilevamento si limita a notificare; non blocca nulla né attiva l'isolamento. L'esca ssh usa un nome che ssh non cerca per impostazione predefinita (non id_rsa), quindi una normale esecuzione di ssh non viene interrotta né segnalata per errore. L'esca AWS usa un nome di profilo distinto invece di [default], quella Docker punta a un registro interno inesistente e tutte vengono create con modalità 0600 (dalla 1.10.3). Oltre a questi tre, vengono collocate esche anche in ~/.pypirc e ~/.env.backup (per le ricerche di token PyPI, file .env e chiavi API di IA). Non si usa ~/.npmrc perché npm lo legge a ogni esecuzione, né le credenziali GCP perché un file falso comprometterebbe l'autenticazione reale. Quando un'esca viene davvero consultata, viene collocata in più esattamente un'esca correlata (~/.aws/config, ~/.ssh/config o ~/.kube/config), sorvegliata a sua volta; la catena si ferma dopo un passo e nessun file esistente viene sovrascritto.

Monitoraggio dell'accesso alle credenziali del browser — 1.10.0, opt-in anche in Pro, disattivato per impostazione predefinita

Monitora, tramite FSEvents, le scritture nei database di password/cookie salvati di Chrome, Brave, Edge, Vivaldi, Chromium e Firefox. In caso di burst, lsof identifica il processo; qualsiasi cosa diversa dal browser stesso, da un indicizzatore come Spotlight o da un demone di backup conta come rilevamento. A differenza delle altre nuove guardie, l'attivazione di Pro non la attiva automaticamente — un'estensione del browser o la funzione di importazione di un gestore di password possono attivarla, per cui il rischio di falsi positivi è intrinsecamente abbastanza alto da renderla opt-in di serie.

Comunicazione con il Sensor (TLS e firme)

L'abbinamento, le richieste di audit e il recupero dei risultati con un RoamSwitch Sensor (un prodotto separato) nella stessa LAN usano TLS 1.3 con l'impronta del certificato (SHA-256) fissata (Sensor 0.3.9 e successivi). L'impronta non viene ottenuta via rete; l'utente digita il valore mostrato sullo schermo del Sensor. Autorità di certificazione, nome host e scadenza non vengono verificati, ci si fida solo del valore fissato. Se TLS fallisce, non si ripiega sul testo in chiaro (il ripiego permetterebbe a un man in the middle di degradare la connessione). Le firme delle richieste includono marca temporale e nonce (v2) per impedire il replay. Solo un vecchio abbinamento senza impronta si connette ancora in chiaro come prima, con un avviso.

Se il certificato cambia, la connessione viene rifiutata. L'aggiornamento dell'impronta fissata non è mai automatico: viene salvato solo quando l'utente digita e conferma il nuovo valore e una sonda firmata riesce davvero via TLS. Limite: questa sonda non verifica con una chiave che il Sensor sia lo stesso abbinato in precedenza. Presuppone che l'utente digiti il valore che ha confermato sullo schermo del Sensor.

Rilevamento multilivello dei download (firme statiche + ClamAV) — livello di firme statiche aggiunto nella 1.8.7

I file appena inseriti nelle cartelle Download, Scrivania o Documenti passano prima per un leggero controllo di firme statiche tramite StaticSignatureScanner (solo i primi 4 MB, semplice corrispondenza di pattern di byte AND/OR, senza espressioni regolari né calcoli di entropia). Non richiede l'entitlement EndpointSecurity e continua a funzionare anche dove ClamAV non è installato. Il rilevamento è deliberatamente limitato a ciò che può essere affermato come un fatto senza un corpus reale di malware: la firma di test EICAR, standard del settore, e cinque reverse shell da manuale documentate in riferimenti pubblici di offensive security (ad esempio PayloadsAllTheThings) — /dev/tcp/ di bash/sh, -e di netcat, pty.spawn di Python, Socket di Perl e fsockopen di PHP. Non scriviamo «firme» a livello di byte per famiglie di malware specifiche senza disporre di un campione reale da cui estrarle: sarebbe solo un falso senso di sicurezza. I pattern stessi sono memorizzati offuscati con XOR 0x5A, in modo che il binario di RoamSwitch non contenga mai letteralmente una stringa EICAR o una reverse shell per cui ClamAV stesso potrebbe segnalarlo.

Solo i file che il livello di firme statiche giudica «puliti» passano poi alla scansione ClamAV. I file appena inseriti nelle cartelle Download, Scrivania o Documenti vengono scansionati immediatamente da ClamAV, indipendentemente da un'estensione .tmp o da un attributo com.apple.quarantine (ad esempio una copia fatta da Terminale). EICAR e altre firme di test standard del settore sono considerate innocue: non viene mostrata alcuna notifica, ma solo registrata una voce nella cronologia delle notifiche, e non vengono mai messe in quarantena o bloccate (solo i campioni di malware autentici vengono messi in quarantena). E se un file con lo stesso nome era già stato messo in quarantena in precedenza, la nuova minaccia viene comunque spostata nella cartella di quarantena con un nome univoco e con timestamp, così una minaccia non può mai restare nella sua posizione originale a causa di una collisione di nomi.

Rilevamento di web shell nelle cartelle web di sviluppo (Web Rules)

I file dello sviluppo web locale (~/Sites, MAMP, Herd, Local, /Library/WebServer/Documents e le cartelle di progetto registrate per il confronto delle CVE dei pacchetti) vengono controllati con le stesse 18 regole di rilevamento dell'edizione Linux: PHP, JavaScript, JSP e ASP. Le regole descrivono solo tecniche documentate pubblicamente e non contengono alcuna firma ricavata da un campione reale. I livelli sono tre: integrate nell'app, un aggiornamento firmato in sola ricezione una volta al giorno (incluso nel recupero unico esistente) e le tue regole in ~/Library/Application Support/RoamSwitch/rules.d (si leggono solo file di proprietà di root o tua che né il gruppo né altri possono scrivere). Le cartelle già monitorate, come Download, vengono controllate subito dopo una scrittura; le cartelle web di sviluppo una volta al giorno e su richiesta (il controllo giornaliero non entra in node_modules e .git; quello di una cartella scelta entra in node_modules). Poiché EndpointSecurity non è usato, nulla può essere fermato prima dell'esecuzione: ogni rilevamento avviene a posteriori.

Per impostazione predefinita viene messo in quarantena automaticamente solo critical. Un risultato high non tocca il file e compare come notifica e in un elenco nelle Impostazioni, dove puoi scegliere «non rilevare più» (il livello di quarantena si cambia nelle Impostazioni). L'hook post-rilevamento (~/Library/Application Support/RoamSwitch/hooks/post-detection) viene eseguito solo se appartiene a root o a te e nessun altro può scriverlo, e viene interrotto dopo 30 secondi. La risposta sugli account (disattivata per impostazione predefinita; per attivarla serve la password di amministratore) revoca la shell di login del proprietario di un file in quarantena, ma non tocca root, UID inferiori a 501 né utenti connessi (compreso te, che stai usando questo Mac). Eseguito come root sotto il sandbox dell'helper in una macchina virtuale macOS 26, sono stati verificati il vero cambio di shell con dscl e il suo ripristino (il sandbox blocca le scritture dirette in dslocal, ma non le modifiche fatte tramite dscl). La conferma con la password di amministratore e il percorso di chiamata dall'app all'helper non sono ancora stati verificati su una macchina reale. Ci sono stati 0 risultati in 114.501 file di una cartella di sviluppo.

Monitoraggio di nuova persistenza di avvio (LaunchAgent/LaunchDaemon) — 1.8.7

I nuovi file .plist collocati in ~/Library/LaunchAgents, /Library/LaunchAgents o /Library/LaunchDaemons vengono rilevati in tempo reale tramite FSEvents (PersistenceMonitorGuard). Senza ES_EVENT_TYPE_AUTH_CREATE di EndpointSecurity, la scrittura stessa non può essere bloccata. Ciò che si può fare è accorgersene entro pochi secondi e valutarne il contenuto.

La valutazione si basa su cosa viene avviato direttamente, non sulla firma dell'eseguibile stesso. Tecniche reali osservate nel 2026 — un LaunchAgent camuffato da Google Update che nasconde uno script bash decodificato in base64, oppure un LaunchDaemon con privilegi root che riesegue a ogni avvio un payload AppleScript decodificato in base64 — nascondono entrambe il codice malevolo nello script stesso, mentre richiamano semplicemente un /bin/bash o /usr/bin/osascript firmato validamente. Un controllo che guardasse solo la firma dell'eseguibile lascerebbe passare entrambi i casi. Per questo, qualsiasi LaunchAgent/LaunchDaemon appena registrato che invochi direttamente un interprete di script grezzo (bash, sh, zsh, osascript, python3, perl, ruby, php e simili) viene segnalato incondizionatamente, indipendentemente dalla validità della firma dell'interprete stesso. Gli argomenti passati vengono inoltre sottoposti a StaticSignatureScanner, e ogni pattern noto come malevolo viene incluso nella notifica. Le voci registrate tramite BundleProgram (che puntano a un binario compilato e firmato all'interno di un bundle applicativo, incluso l'Helper stesso di RoamSwitch) vengono semplicemente verificate nel modo consueto, con codesign --verify --strict.

Rilevamento di ClickFix e blocco di emergenza — 1.8.7, disattivato di default

«ClickFix» è una tecnica di ingegneria sociale in cui una falsa schermata CAPTCHA o di errore invita ad aprire il Terminale e a incollare un comando «per la verifica». Le rilevazioni sono aumentate di oltre il 500% dal 2024 al 2025, e entro il 2026 è considerata uno dei vettori di attacco macOS più diffusi. Poiché è la shell legittima dell'utente stesso a eseguire esattamente ciò che è stato digitato, elude completamente la verifica della firma di Gatekeeper.

Barra dei menu → Protezione da malware → Difesa ClickFix (Pro, disattivata di default). Monitora tramite FSEvents le nuove righe aggiunte a ~/.zsh_history/~/.bash_history (solo le righe aggiunte dopo l'avvio della protezione — non esamina mai la cronologia già esistente), e verifica la corrispondenza solo con le reverse shell da manuale condivise con StaticSignatureScanner, oltre alla specifica combinazione di decodifica base64 direttamente verso una shell o verso osascript. Un semplice curl | bash — estremamente comune negli installer legittimi — è deliberatamente escluso, poiché è indistinguibile dalle istruzioni ufficiali di installazione di Homebrew, rustup, nvm e simili. Non appena viene trovata una corrispondenza, si attiva lo stesso air-gap di emergenza descritto sopra. Poiché il comando è già stato eseguito, si tratta necessariamente di una reazione successiva, ma può comunque interrompere un download di seconda fase o un'esfiltrazione di credenziali in corso. Come qualsiasi altro trigger dell'air-gap, si disattiva da solo dopo al massimo 10 minuti (vedi l'inizio di questa sezione).

Un limite noto. Poiché il rilevamento dipende da una scrittura nella cronologia della shell, non può individuare la variante fileless che elude completamente il Terminale invocando direttamente l'Editor di script tramite lo schema URL applescript://, senza scrivere nulla su disco. Apple ha introdotto una certa mitigazione per questo (una finestra di conferma per gli script non identificati), ma sono state segnalate varianti che la aggirano. Abbiamo testato questo percorso su hardware reale usando il log unificato di macOS (log show/log stream) e non abbiamo trovato alcun segnale di log distintivo su cui basare il rilevamento. Preferiamo dichiarare apertamente questo limite piuttosto che rivendicare una copertura che non abbiamo.

Rilevamento rischi Docker — 1.8.9, Pro, disattivato per impostazione predefinita

Barra dei menu → "Protezione da malware" → "Rileva container Docker privilegiati e mount docker.sock" (DockerEventGuard). Poiché questa codebase non ha precedenti di connessioni di streaming persistenti come docker events, utilizza lo stesso approccio di polling a timer di PortAnomalyGuard. Ogni 20 secondi recupera l'insieme degli ID dei container con un leggero docker ps -q, quindi esegue docker inspect --format solo sulla differenza (container appena avviati) per un'ispezione dettagliata. La stringa di formato usata per il rilevamento è deliberatamente identica a quella dell'edizione Linux (roamswitch_core::health::LinuxHealthChecker::DOCKER_INSPECT_RISK_FORMAT), quindi entrambe le piattaforme segnalano esattamente le stesse condizioni (avvio in modalità --privileged, o un mount collegato di /var/run/docker.sock).

Nessuna azione automatica viene intrapresa al rilevamento. Un container privilegiato o un mount docker.sock è una "configurazione" rischiosa che potrebbe consentire l'escape dal container, ma non è una compromissione confermata: esistono anche usi legittimi, come l'esecuzione deliberata di un agente di monitoraggio in modalità privilegiata. Poiché la maggior parte degli utenti non utilizza affatto Docker, questa funzione resta disattivata per impostazione predefinita anche con una licenza Pro. Non utilizza l'entitlement EndpointSecurity; la CLI docker viene individuata controllando prima il percorso di installazione predefinito di Docker Desktop, poi Homebrew, poi which. Verificata su un'installazione reale di Docker Desktop (29.7.2) in tre scenari — un container privilegiato, un container con mount docker.sock e un container normale — senza falsi positivi né rilevamenti mancati.

Controllo fughe di segreti/chiavi API — aggiunto in 1.8.4, scansione cartelle aggiunta in 1.8.9

Barra dei menu → "Protezione da malware" → "Controllo fughe di segreti/chiavi API". Incolla del testo e viene verificato all'istante alla ricerca di chiavi API e token trapelati, mostrando il numero di riga, una stringa mascherata e un consiglio per ogni risultato (SecretLeakAuditor, interamente sul dispositivo). La versione 1.8.9 ha aggiunto l'opzione "Scegli cartella da scansionare", che consente allo stesso motore di rilevamento di controllare ricorsivamente una directory — come un checkout del codice sorgente — tramite auditDirectory(at:). .git, node_modules, target, vendor, dist, build, __pycache__ e venv vengono esclusi automaticamente, così come i file superiori a 2 MB o rilevati come binari. L'elaborazione viene eseguita fuori dal thread principale per non bloccare l'interfaccia, e non viene mai inviato nulla all'esterno.

Link Guard (blocco delle connessioni di phishing) — 1.7.2 e versioni successive, rafforzato in 1.8.0

Barra dei menu → "Protezione malware" → "Link Guard" (Pro). Blocca le connessioni a siti di phishing/truffa sul dispositivo, su ogni browser e app. Esistono due punti di applicazione, utilizzati insieme in ordine di priorità.

  • ① Estensione del sistema di filtro dei contenuti (RoamSwitchLinkFilter, 1.8.0+, preferibile una volta approvata). Un'estensione del sistema NEFilterDataProvider: nessuna revisione da parte di Apple (i fornitori di filtri dei contenuti sono self-service, nessuna coda di approvazione). Esamina l'effettivo flusso TCP in uscita dopo risoluzione dei nomi. Il nome di destinazione deriva dal nome host risolto dal sistema operativo o, in caso contrario, TLS SNI viene analizzato dai primi byte del flusso, quindi un browser che esegue il proprio DoH/DoT e si connette a un IP nudo è ancora bloccato. Non riscrive mai /etc/hosts e attribuisce ogni flusso a un processo. QUIC (UDP/443) non ha SNI leggibile, quindi in modalità block viene eliminato, costringendo il browser a ricorrere a HTTP/2 su TCP. Richiede un'approvazione una tantum nelle Impostazioni di sistema.
  • ② /etc/hosts sinkhole (fallback, mentre l'estensione non è approvata/rifiutata). L'helper privilegiato scrive i domini di destinazione in una sezione gestita delimitata di /etc/hosts come 0.0.0.0 e svuota la cache. Limite massimo a 60.000. Una volta attiva l'estensione, questa sezione verrà rimossa.
  • Tre modalità. "Off" lo disabilita. "Solo avviso" mette in pausa la connessione corrispondente e chiede all'utente (vedi Real warn di seguito). "Blocco automatico dei siti truffa evidenti (consigliato)" viene eliminato immediatamente. L'impostazione predefinita è blocco a partire dalla versione 1.7.2.
  • Real warn (1.8.0+, solo estensione). Su warn premi l'estensione di sistema holds il flusso (sia il nome del sistema operativo che il percorso SNI) con .pause() e l'app genera una notifica "Consenti/Blocca". Nel momento in cui il tocco dell'utente raggiunge allowlist.txt / extra.txt (l'estensione esegue il polling dei piccoli file di stato ogni 1,5 s) i flussi sospesi riprendono (consenti) o interrompono (bloccano). Nessuna risposta entro 25 s fallisce open (un avviso è consultivo). La decisione viene memorizzata nella cache per host (consenti 5 minuti/blocco 1 ora), quindi gli altri flussi della pagina e le visite successive sono istantanei e applicati. La notifica block è invariata: "Bloccato..." con un pulsante "Consenti una volta (5 min)".
  • Solo i casi chiari vengono bloccati. Un elenco nel feed delle minacce o un omografo Unicode di un marchio: tutto il resto (TLD ad alto rischio, impersonificazione di sottodomini, ...) è un avvertimento. Il motore del verdetto è condiviso con l'edizione Linux e non invia URL da nessuna parte.
  • Recupero da un blocco sbagliato. Consenti un dominio dalla notifica o dal menu (5 minuti o permanente). L'elenco consentito viene sottratto quando la sezione viene rigenerata.
  • Pro-gate. L'applicazione (applyMode()) avviene solo su una licenza Pro valida. Senza Pro la modalità viene memorizzata ma /etc/hosts non viene mai toccata. L'attivazione o la decadenza di una licenza ha effetto a metà sessione.
  • Feed e seed in bundle. L'elenco dei blocchi proviene dal feed delle minacce firmato (§7, verificato con una chiave dedicata al feed). Funziona sul seed in bundle dell'app (~ 60.000 voci) anche prima del primo recupero e la disattivazione dell'"Aggiornamento automatico" significa assenza di traffico in uscita.

Blocco preventivo ARP/NDP del gateway: 1.7.5 e versioni successive

Barra dei menu → "Monitoraggio porta e dispositivo" → "Aggiungi ARP/NDP del gateway su reti non attendibili (preventivo)" (Pro, disattivato per impostazione predefinita).

  • Come funziona. Quando si accede a una rete non attendibile, il MAC corrente del gateway IPv4, del router predefinito IPv6 e dei risolutori DNS sul collegamento viene raccolto da route / scutil --dns / arp -n / ndp -an e l'helper blocca ciascuno come voce permanent con arp -s / ndp -s (attendibilità al primo utilizzo — il primo MAC osservato è attendibile). Le risposte ARP/NDP contraffatte per tali IP vengono quindi ignorate, quindi non è possibile impostare un attacco man-in-the-middle.
  • Scope. Vengono bloccati solo questi tre tipi di voce. Le reti affidabili (aperte) non vengono mai bloccate. Ad ogni modifica della rete si sblocca una volta e si ricollega. I risolutori pubblici off-link (8.8.8.8, …) non hanno alcuna voce ARP on-link e sono automaticamente esclusi.
  • arp -s è preventivo; l'air-gap è successivo al fatto. Questo pin esiste quindi uno spoofing non può avere successo; Il rilevamento dello spoofing ARP (ARPSpoofContainmentManager) e l'air-gap di emergenza esistono per "tagliare più velocemente di un essere umano" una volta che ne viene visto uno. Corrono in modo indipendente.
  • Persistenza. Il set di pin viene salvato in /Library/Application Support/RoamSwitch/gateway_arp_lock.json in modo che possa essere sbloccato tramite una riconnessione XPC o un riavvio dell'assistente.

Il tunnel VPN (WireGuard/Tailscale) e il suo kill switch — 1.7.6 e versioni successive, backend selezionabile nella 1.8.0

Barra dei menu → "Monitor porta e dispositivo" → "Tunnel VPN" (Pro, disattivato per impostazione predefinita). Questa è la difesa primary anti-MITM: non dipende dall'integrità di L2 (ARP/NDP). Il backend è selezionabile (sottomenu → "Backend") tra "WireGuard (file di configurazione)" e "Tailscale (Nodo di uscita)". RoamSwitch non implementa alcuna crittografia stessa; solo il backend scelto è armato.

  • (A) Backend WireGuard. Guida wireguard-tools di Homebrew (nessun diritto all'estensione di rete Apple). Importa il tuo .conf; l'aiutante lo salva in 0600. Kill-switch: pf block drop all più un pass quick solo per lo, l'interfaccia del tunnel, l'handshake UDP agli IP dell'endpoint bloccato, DHCP e ICMP. Un tunnel AllowedIPs non pieno genera un avviso di tunnel diviso.
  • (B) Backend Tailscale (1.8.0+). Per gli utenti che già eseguono Tailscale. RoamSwitch non esegue tailscale up / accede / lo installa: legge tailscale status --json ed esegue tailscale set --exit-node=<node>. Si consiglia la build Standalone (download diretto da tailscale.com) con l’integrazione CLI attivata (Impostazioni → “CLI integration” → “Install Now”, che aggiunge /usr/local/bin/tailscale): la build dell'App Store (GUI) non può essere gestita dall'esterno dell'app (sandbox); con esso scegli il nodo di uscita nell'app Tailscale e RoamSwitch mostra solo lo stato. È richiesto un nodo di uscita (indirizza tutto il traffico attraverso il tunnel); viene disattivato automaticamente se va offline o non è raggiungibile.
  • ⚠️ La build Homebrew (brew install tailscale) non è consigliata. Il tailscaled distribuito da Homebrew presenta un bug noto su macOS che può impedirgli di riscrivere correttamente la tabella di routing (tailscale/tailscale#21395, #21216) — un exit node può risultare “connesso” mentre il traffico continua a fuoriuscire, oppure la sua disattivazione può interrompere completamente la connessione Internet del Mac. RoamSwitch verifica l’effettiva tabella di routing del sistema operativo e avvisa quando ciò accade, ma usare la build Standalone sopra indicata evita il problema a monte.
  • Riconfigurazione della rete alla disconnessione. Il macOS autonomo tailscaled non ripristina in modo pulito il routing + DNS quando un nodo di uscita viene cancellato, quindi RoamSwitch rimbalza i servizi di rete attivi (lo stesso che attivare manualmente il Wi-Fi; solo servizi con un indirizzo IPv4, sicuro per multi-NIC e IP statico). Il traffico diminuisce per circa 5-10 secondi e il livello di protezione non viene abbassato durante tale periodo.
  • Il kill switch di Tailscale è disattivato per impostazione predefinita (opt-in). Il nodo di uscita esegue già il tunneling di tutto; il pf block drop all (consente solo CGNAT/MagicDNS/STUN/DERP/DHCP/ICMP/DNS) è per utenti avanzati ed è "resistente alle perdite, non a prova di perdite" (il DNS è consentito, quindi tailscaled sopravvive).
  • Automatico / in caso di perdita della licenza. Si attiva su reti non attendibili, diminuisce su quelle attendibili; revocato alla scadenza della licenza Pro.

§6

Quando ti connetti a una rete che non hai registrato, l'interruttore del "livello di protezione" non viene utilizzato pf. Modifica semplicemente le impostazioni standard del sistema operativo in un modo che può essere invertito in seguito.

Operazioni per livello di protezione
OperazioneAttuazionePrivilegioCome viene ripristinato
Firewall + modalità invisibile attiva socketfilterfw --setblockall attivo / --setstealthmode attivo radice (aiutante) off quando ritorni su una rete sicura
Interrompi SSH/SMB/Condivisione schermo launchctl scarica -w radice (aiutante) Registra solo quelli che erano in esecuzione al momento dell'arresto e load -w al ritorno (con SSH accoppiato anche a /usr/sbin/systemsetup -setremotelogin on per un ripristino macOS moderno garantito)
Disabilita AirDrop le impostazioni predefinite scrivono com.apple.sharingd DiscoverableMode Utente (l'app stessa) Salva il valore precedente e lo riscrive al ritorno

Niente di tutto questo è un nuovo meccanismo di blocco aggiunto da RoamSwitch: si tratta semplicemente di attivare/disattivare le impostazioni del sistema operativo. Se elimini l'app, l'unica cosa che si ferma è la commutazione dipendente dalla rete; l'ultimo sistema operativo le impostazioni applicate rimangono invariate. Niente viene lasciato bloccato, ma se vuoi sbagliare sul per sicurezza, reimpostarlo su "Aperto" su una rete attendibile prima di disinstallarlo.

§7

Cosa rimane sul Mac

Dati archiviati su disco
DatiPosizioneContenuto
Token di licenzaPortachiavi
com.tetsuharu.RoamSwitch.license
Un token firmato Ed25519. kSecAttrAccessibleAfterFirstUnlock
Impostazioni app/attivazione/disattivazione protezioneUserDefaults suite
com.tetsuharu.RoamSwitch
Registrazioni di reti attendibili, criteri di protezione, elenchi di esclusione e così via
stato pf/Libreria/Supporto applicazioni/RoamSwitch/Il timestamp dell'air gap, il JSON delle porte protette, lo stato del kill switch della VPN, il file temporaneo per il set di regole applicato
Feed delle minacce di Link Guard~/Library/Application Support/RoamSwitch/threatfeed/feed.txtL'elenco dei domini di phishing/truffa scaricati (o il seed in bundle dell'app se non ancora recuperato). La versione del feed è in UserDefaults
Sezione gestita Link Guard/etc/hostsUna sezione delimitata da # BEGIN RoamSwitch link guard … # END, annullando i domini bloccati a 0.0.0.0. Rimosso quando la modalità è "Off" (§5)
Perni di bloccaggio ARP/NDP/Libreria/Supporto applicazioni/RoamSwitch/gateway_arp_lock.jsonL'insieme IP → MAC effettuato permanent dal blocco preventivo (§5). Eliminato quando sbloccato
Configurazione WireGuard/Libreria/Supporto applicazioni/RoamSwitch/ (area assistenza, 0600)Il .conf importato dall'utente. Anche il nome host dell'endpoint è nascosto in UserDefaults (l'app lo risolve)
UUID di fallback del dispositivoUserDefaultsUn valore casuale, generato solo quando IOKit non restituisce un UUID (§9)
Registrios.Logger/NSLogRegistrazione unificata. Nulla viene inviato all'esterno

Traffico in uscita dalla macchina (l'elenco completo)

Non esiste alcun codice che raccolga e invii risultati diagnostici, informazioni sulle porte, URL o log. Non sono inclusi SDK di analisi né SDK di crash reporting. L'unica libreria esterna è Sparkle (aggiornamenti). Ciò che esce in rete sono i seguenti otto percorsi, più le funzioni avviate dall'utente, le funzioni disattivate per impostazione predefinita e la protezione DNS dalle minacce descritte sotto la tabella, e nient'altro (il sesto solo se l'utente configura una VPN, il settimo solo se è attivata l'associazione RoamSwitch Sensor).

Connessioni in uscita effettuate da RoamSwitch
ConnessioneDestinazioneQuando succedeCosa viene inviato
Attivazione/disattivazione della licenza lafine.net
/api/v1/license/*
Solo quando l'utente inserisce una chiave di licenza o disattiva Pro Chiave di licenza, hash del dispositivo, nome del dispositivo (il «Nome» in Impostazioni di Sistema) e versione dell'app. Al rilascio, solo chiave e hash. Le informazioni personali sono gestite da Stripe all'acquisto; l'app non le gestisce
Controllo degli aggiornamenti lafine.net
/updates/appcast.xml
Sparkle controlla ogni 24 ore (e anche all'avvio se dall'ultimo controllo sono trascorse 24 ore o più). Sempre attivo per impostazione predefinita; non vengono inviate informazioni di sistema Una richiesta HTTP (un UA e una versione standard). L'elemento scaricato è verificato dalla firma EdDSA (§10)
Feed delle minacce di Link Guard lafine.net
/updates/v1/{manifest, feed/.txt}
Nella versione Pro, quando l'«Aggiornamento automatico» della protezione dei link è attivo (impostazione predefinita): ogni 24 ore (e all'avvio). Disattivando l'«Aggiornamento automatico» questo percorso scompare Solo GET. Nessuna stringa di query, nessun cookie, nulla che identifichi il dispositivo (lo User-Agent standard include nome e versione dell'app e la versione di macOS). Un file statico firmato di sola ricezione; il manifest e il corpo del feed sono verificati con Ed25519. La chiave di firma è dedicata al feed ed è una chiave distinta dalla SUPublicEDKey degli aggiornamenti dell'app (così una fuga resta limitata a «una blocklist errata»)
Dati di aggiornamento della verifica CVE dei pacchetti / della verifica attiva delle vulnerabilità lafine.net
/updates/v1/manifest
All'avvio dell'app (se dall'ultimo recupero sono trascorsi 30 giorni o più). Il controllo avviene ogni 24 ore e il recupero effettivo una volta ogni 30 giorni. Sempre (i dati di aggiornamento della verifica CVE dei pacchetti non hanno un interruttore di disattivazione, poiché sono dati esclusivamente ricevuti usati solo per un confronto locale dell'inventario; vale per tutti gli utenti, inclusa la versione Free. La mappa CVE della verifica attiva delle vulnerabilità viene recuperata solo se è attivata «Verifica attiva delle vulnerabilità (verifica attiva della raggiungibilità)») Solo una GET. Nessuna stringa di query, nessun cookie, nulla che identifichi la macchina (lo User-Agent standard include nome e versione dell'app e la versione di macOS). Un file statico firmato e di sola ricezione; sia il manifest che il corpo del feed vengono verificati con Ed25519. Riutilizza la stessa chiave e lo stesso manifest del feed di minacce di Link Guard, ma i feed effettivamente recuperati (mappe CVE note per Homebrew/npm/PyPI/crates.io/RubyGems/Packagist/Go/Maven, la mappa CVE della verifica attiva delle vulnerabilità, e l'elenco dei nomi di pacchetti npm popolari usato per il rilevamento typosquatting) sono file separati
Aggiornamento della definizione del virus ClamAV Specchi ufficiali ClamAV Quando l'utente ha installato ClamAV. Nella versione Pro avvia freshclam automaticamente all'avvio, ogni 24 ore e durante i giri di controllo autonomi; nella versione Free solo quando si preme «Aggiorna definizioni dei virus» nel menu Il normale recupero delle definizioni di ClamAV. Non contiene informazioni provenienti da RoamSwitch (possono essere incluse le informazioni identificative standard di ClamAV stesso)
Tunnel VPN (§5, WireGuard / Tailscale) L'endpoint WireGuard configurato dall'utente, oppure il control plane di Tailscale (Tailscale, Inc.) Solo quando l'utente ha configurato il tunnel VPN (Pro, disattivato per impostazione predefinita) e si unisce a una rete non attendibile. Con WireGuard, il nome host dell'endpoint viene risolto tramite DNS una volta prima dell'attivazione del tunnel. Con Tailscale, si tratta di traffico del control plane gestito dal tailscaled che l'utente ha già installato e a cui ha già effettuato l'accesso — RoamSwitch si limita a leggere tailscale status --json ed eseguire tailscale set --exit-node= Con WireGuard: l'handshake WireGuard (UDP) e il traffico all'interno del tunnel. La destinazione è il server VPN dell'utente e i contenuti sono il traffico dell'utente. Con Tailscale: traffico tra l'account Tailscale dell'utente e il control plane di Tailscale, Inc. (registrazione del dispositivo, relay DERP, ecc., regolato dall'informativa sulla privacy di Tailscale). In entrambi i casi RoamSwitch non aggiunge alcun identificatore né dati diagnostici
Associazione e collegamento di audit di RoamSwitch Sensor RoamSwitch Sensor sulla stessa LAN (hardware configurato dall'utente stesso, in esecuzione con un IP fisso) Solo quando è attivata «Associazione RoamSwitch Sensor» (Pro, disattivata per impostazione predefinita) e si inserisce l'indirizzo IP e il codice di abbinamento di Sensor per associarsi (uno scambio una tantum). Dopo l'associazione, si riconnette allo stesso IP fisso solo quando si preme «Richiedi controllo al Sensor», e durante il recupero automatico del risultato (a partire da 5 minuti dopo la richiesta, poi ogni 5 minuti, fino a 5 tentativi) L'associazione invia il nome host e la chiave pubblica Ed25519 di questo endpoint, e riceve la chiave pubblica rilasciata da Sensor. Le richieste di controllo e il recupero dei risultati sono autenticati con una firma Ed25519 — vengono inviate solo richieste firmate. I risultati diagnostici e le informazioni sulla porta vengono ricevuti solo come risposta di Sensor; il contenuto dei file non viene mai inviato. L'unica destinazione è l'indirizzo IP fisso del Sensor configurato dall'utente stesso — non raggiunge mai alcun server esterno, incluso lafine.net. La fiducia reciproca si stabilisce solo tramite lo scambio esplicito del codice di abbinamento
Pagina di pagamento Pagamento a strisce Solo quando l'utente preme il pulsante Acquista (si apre nel browser) — (una navigazione nel browser)
Ambito di "Zero Telemetry"

Qui «Zero Telemetry» significa che non esiste alcuna telemetria che raccolga e invii dati di utilizzo o risultati diagnostici. Non significa che non ci sia alcun traffico di rete. Gli otto percorsi della tabella sopra e le funzioni descritte sotto la tabella esistono davvero. Ma ciascuno di essi è avviato dall'utente, è un recupero di sola ricezione con verifica della firma, oppure è una comunicazione con un servizio esterno scelto dall'utente (i mirror di distribuzione di ClamAV, un provider DNS protettivo, l'URL controllato e così via), e i risultati diagnostici, le porte e il contenuto dei file sul Mac non vengono mai inviati allo sviluppatore.

Il feed delle minacce di Link Guard (riga 3) aggiunge "ritira aggiornamenti" oltre a "non invia nulla" difesa. I due sono tenuti separati; allo stesso modo il whitepaper Linux §1.1 divide "Zero Telemetria" da "aggiornamenti di sola ricezione". Disattiva "Aggiornamento automatico" e Link Guard verrà eseguito sul pacchetto dati (~60.000 domini di phishing/truffa) più rilevamento di omografi offline e questo percorso non si verifica.

Il tunnel VPN (riga 6) avviene solo se l'utente configura il proprio server WireGuard, oppure sceglie il backend Tailscale ed è già connesso a tailscaled con il proprio account Tailscale; la destinazione e i contenuti sono sotto il controllo dell'utente. RoamSwitch si limita ad attivare il tunnel, mantenere il kill switch, o leggere lo stato di Tailscale per cambiare il nodo di uscita — non aggiunge alcun identificatore né dati di utilizzo. Senza una VPN configurata, questo percorso non esiste.

L'associazione RoamSwitch Sensor (riga 7) è una funzione esclusiva Pro, disattivata per impostazione predefinita; questo percorso non esiste a meno che tu non lo attivi. L'associazione stessa si stabilisce solo tramite un'azione esplicita: l'inserimento del codice di abbinamento rilasciato dall'operatore del Sensor insieme all'indirizzo IP fisso del Sensor. Ogni comunicazione successiva (richieste di controllo, recupero dei risultati) è indirizzata anch'essa a quello stesso IP fisso di proprietà dell'utente ed è autenticata con una firma Ed25519. Non raggiunge mai alcun server esterno, incluso lafine.net. I risultati diagnostici o il contenuto dei file non vengono mai inviati, se non come risposta dello stesso Sensor.

Alcune comunicazioni non sono nella tabella. Nessuna di esse invia risultati diagnostici o contenuto dei file allo sviluppatore. (1) La protezione DNS dalle minacce della versione Pro imposta il DNS di sistema su Quad9, Cloudflare, AdGuard o CleanBrowsing (ambito predefinito: «fuori casa / Wi-Fi non attendibile»). Finché è applicata, le query di risoluzione dei nomi vanno a quel provider. (2) La verifica delle firme npm della versione Pro chiede conferma a ogni esecuzione e poi interroga il registro npm. (3) La diagnosi attiva delle vulnerabilità (verifica attiva della raggiungibilità) si limita a questo stesso Mac (127.0.0.1). (4) La richiesta HEAD che la finestra «Diagnosi di sicurezza dei link» invia direttamente all'URL di destinazione è descritta nel paragrafo seguente.

Il foglio "controllo sicurezza collegamento" in-app invia una richiesta HEAD all'URL di destinazione per visualizzarlo dove arriva un URL abbreviato (i successivi reindirizzamenti a indirizzi privati o locali vengono fermati dal file v1.4.5 Mitigazione SSRF). L'MCP audit_url_safety, al contrario, è un'analisi offline che si completa sul posto e non invia l'URL da nessuna parte (§8).

Misurato (2026-08-29) — Verdetto: PASS

Non è solo un'affermazione. Il 2026-08-29 un'installazione 1.4.7 in esecuzione è stata controllata con tcpdump + attribuzione per processo (nettop / lsof / cattura pktap filtrata) + LuLu, per circa 2 ore, con il livello di sicurezza fissato su Blocco massimo e il controllo appcast forzato. Risultato (versione 1.4.7): nessun flusso in uscita attribuito a RoamSwitch, RoamSwitchHelper o RoamSwitchMCPServer oltre al controllo appcast verso lafine.net; gli unici socket del server MCP erano verso localhost; i dump degli entitlements sono vuoti. Questa misurazione riguarda solo la versione 1.4.7. I percorsi aggiunti da allora sono elencati nella tabella sopra ed è prevista una nuova misurazione della versione attuale.

Descrizione completa e uno script che chiunque può eseguire per riprodurlo: audit/RESULTS-2026-08-29.md

Questa misurazione risale alla versione 1.4.7. Da allora sono stati aggiunti questi percorsi: il feed delle minacce della protezione dei link (1.7.2, riga 3 della tabella), il tunnel VPN (1.7.6, riga 6), i dati di aggiornamento della verifica CVE dei pacchetti (riga 4) e l'associazione Sensor (riga 7). Controllando la versione attuale vedrai GET di sola ricezione verso lafine.net (appcast, feed delle minacce e dati di aggiornamento); se è configurata una VPN vedrai anche UDP verso il server WireGuard dell'utente (destinazione e contenuto sotto il suo controllo). Il feed delle minacce scompare con «Aggiornamento automatico: disattivato» e il traffico VPN senza VPN configurata (l'appcast e i dati di aggiornamento della verifica CVE dei pacchetti non hanno un interruttore di disattivazione).

§8

RoamSwitchMCPServer è uno strumento da riga di comando autonomo fornito in bundle su RoamSwitch.app/Contents/MacOS/. Un client MCP come Claude Desktop o Claude Code lo avvia come sottoprocesso e comunica con esso tramite stdio (JSON-RPC delimitato da nuova riga 2.0). L'SDK ufficiale non si basa sull'SDK macOS di questa macchina, quindi è così implementato a mano sopra JSONSerialization della Fondazione.

Ciò che il design vincola

  • È di sola lettura. Semplicemente non esiste alcuna API per modificare il livello di sicurezza, isolare una porta o espellere un dispositivo. Questo non è qualcosa di dimenticato nella v1: è stato omesso deliberatamente. Lasciare che il codice esterno (qui, un LLM) riscriva lo stato di protezione di uno strumento di sicurezza comprometterebbe la fiducia di ogni utente.
  • Non apre alcun socket in ascolto. Non registra alcun servizio e non si mette in ascolto. Legge una riga da stdin, restituisce una riga su stdout e poi il client termina il processo.
  • Non invia telemetria. Solo run_npm_audit_signatures (registro npm), facoltativo e riservato a Pro su Mac, comunica con un host esterno. run_active_vuln_scan (disattivato per impostazione predefinita) sonda soltanto 127.0.0.1, e su Mac get_exposed_ports si limita a inviare un HTTP GET per ogni porta esposta a 127.0.0.1 per controllare le intestazioni. Per ricavare l'indirizzo MAC del gateway predefinito, gli strumenti di diagnostica possono inviare un ping ICMP, di norma uno (pochi se il MAC non si risolve), al gateway della LAN.
  • Legge le impostazioni da un dominio diverso. UserDefaults(suiteName: "com.tetsuharu.RoamSwitch") legge esplicitamente il dominio dell'app (il suo dominio ID bundle è vuoto). Si limita a leggere; non scrive.

Gli strumenti che espone

I principali strumenti restituiti da tools/list e i dati che restituiscono (l'elenco completo è in Configurazione MCP)
StrumentoCosa restituisceTraffico
get_security_reportUn controllo a 18 voci (FileVault / SIP / Gatekeeper / aggiornamento automatico / XProtect / firewall / crittografia Wi-Fi / ARP / pinning ARP del gateway / audit configurazione SSH / audit sudo NOPASSWD / porte esposte / configurazione delle guardie, e altro) con un punteggio e consigli di correzione per voce. Ogni voce riporta anche gli ID dei controlli NIST CSF 2.0 e CIS Controls v8 corrispondenti (da 1.10.0).Solo locale
verify_security_findingsRivaluta le voci (checkId) di get_security_report in base allo stato attuale e restituisce un verdetto per ciascuna, stillPresent (ancora applicabile), resolved (risolta, oppure non applicabile per progetto) o inconclusive (non determinabile), insieme a un codice di motivo indipendente dalla lingua. Serve a verificare, dopo una correzione, che abbia davvero avuto effetto. Una voce che non è stato possibile misurare, per esempio per privilegi mancanti, helper privilegiato non connesso o Docker non raggiungibile, non viene mai segnalata come sicura e risulta inconclusive. Omettendo checkIds vengono verificate tutte le voci (fino a 50 identificatori su Mac e 64 su Linux; su Linux è accettato anche isServer). Su Mac, host_firewall, network_stealth_mode, gateway_arp_lock, malware_scanning, dns_threat_guard e usb_zero_trust sono determinati dalle impostazioni dell'app RoamSwitch, quindi riflettono tali impostazioni e non sono una nuova misurazione dello stato reale del sistema operativo. È di sola lettura, non si connette a host esterni e non ripara nulla né modifica impostazioni. Su Linux non invia nulla nemmeno alla LAN e legge il gateway solo dalle cache locali. Su Mac, per ricavare l'indirizzo MAC del gateway, può inviare un ping ICMP, di norma uno (pochi se il MAC non si risolve), al gateway della LAN.Solo locale
audit_mcp_configsLegge i file di configurazione MCP dei client IA (Claude Desktop, Claude Code, Cursor, Windsurf, VS Code, Cline, Gemini CLI, più .mcp.json e file simili nelle cartelle di progetto registrate) e segnala impostazioni a rischio. Rileva gli strumenti approvati automaticamente senza conferma a ogni chiamata (auto_approve), i server http:// in chiaro su un host diverso da loopback, indicati sia nel campo url sia come argomento di avvio, come --server http://… (remote_plain_http), gli avvii con comandi shell o interprete su una riga (shell_wrapper), i launcher come npx / uvx / docker run che scaricano un pacchetto o un'immagine senza versione fissata (unpinned_package_launcher), docker run con --privileged o con il socket Docker o il file system root montati (container_host_access), le variabili d'ambiente che caricano codice aggiuntivo all'avvio, come LD_PRELOAD o --require in NODE_OPTIONS (nomi senza distinzione tra maiuscole e minuscole, valori mai restituiti; code_loading_env), le voci che puntano a HexStrike AI, un backend di strumenti d'attacco senza autenticazione (hexstrike), i file di configurazione scrivibili da gruppo o altri (world_writable_config), l'impostazione enableAllProjectMcpServers di Claude Code (auto_trust_project_servers) e un file di configurazione che contiene insieme un server che legge testi scritti da altri (pagine web, e-mail, chat, issue) e un server in grado di eseguire comandi o modificare file, la combinazione di cui ha bisogno la prompt injection indiretta (riconosciuta dai nomi di pacchetto noti, senza vedere il contenuto degli strumenti del server; injection_exposure). Non ha argomenti, è di sola lettura e non invia nulla alla rete. Non restituisce mai valori di ambiente o di intestazioni, argomenti o segreti, solo nomi di server, il nome base di un comando, un host di URL e, per un semplice nome di registro, il nome del pacchetto o dell'immagine. I file che non è stato possibile analizzare sono elencati in unreadableFiles e non sono stati verificati.Nessuno
audit_agent_setupLegge i file di istruzioni e le skill dati agli agenti IA (CLAUDE.md, commands, agents e skills in ~/.claude, più ~/.codex, ~/.cursor/rules, ~/.gemini, ~/.agents, ~/.openclaw, e CLAUDE.md, AGENTS.md, .cursorrules e simili nelle cartelle di progetto registrate) e l'openclaw.json di OpenClaw, e segnala contenuti e impostazioni a rischio. Nei file di istruzioni e di skill segnala frasi nascoste in caratteri tag Unicode invisibili (hidden_tag_characters), caratteri a larghezza zero e di controllo della direzione (invisible_characters), formulazioni che annullano istruzioni precedenti o nascondono qualcosa all'utente (override_instruction), scarica-ed-esegui come curl … | sh (remote_script_exec), decodifica-ed-esegui (encoded_payload_exec), riferimenti ad archivi di chiavi e credenziali (credential_access) accanto a un invio o a un servizio di raccolta (credential_exfil_shape, exfil_endpoint), impostazione di meccanismi persistenti (persistence_instruction), codice compilato dentro una skill (skill_precompiled_code) e file scrivibili da altri (agent_file_writable). Ciò che si trova in un commento HTML è valutato più alto, perché una pagina renderizzata lo nasconde. Per OpenClaw segnala autenticazione disattivata (openclaw_auth_disabled), un'attesa raggiungibile dalla rete (openclaw_wide_bind), un token sotto i 24 caratteri (openclaw_weak_token), un'origine jolly (openclaw_origin_wildcard), interruttori dangerously… attivi (openclaw_dangerous_flag), un elenco di proxy che si fida di tutti (openclaw_broad_trusted_proxy, openclaw_proxy_trust_loosened), una politica di DM o di gruppo aperta a chiunque (openclaw_open_dm), mDNS in full (openclaw_mdns_full), rilievi di autodiagnosi nascosti (openclaw_audit_suppressed) e permessi troppo ampi (openclaw_loose_permissions). Ogni regola è deterministica: una corrispondenza significa «una persona dovrebbe leggere questa riga», non «malevolo». Non accetta argomenti, è di sola lettura e non invia nulla alla rete. Non restituisce mai il contenuto dei file né segreti (un token è riportato solo come lunghezza), ma solo un nome di regola, un numero di riga, un host di URL e un percorso di chiave di configurazione. I file non leggibili sono elencati in unreadableFiles e non sono stati verificati.
get_porte_esposteElenco delle porte TCP in ascolto. Per ogni porta esposta oltre localhost, viene confrontata con un DB di servizi pericolosi noti e viene inviato un solo HTTP GET a 127.0.0.1:port (locale, chiuso) per controllare CORS e intestazioni.Solo la sonda su 127.0.0.1
get_guard_statusLo stato di attivazione/disattivazione delle protezioni di risposta automatica Pro (anomalia porta/ARP/USB/Bluetooth/download Web+Mail/protezione dalle minacce DNS), il livello di protezione corrente e lo stato della rete attendibileSolo locale
audit_url_sicurezzaGiudizio di un URL per phishing/omografo (spoofing Unicode)/spoofing di sottodominio del marchio/TLD ad alto rischio/HTTP in testo normale. È sincrono e completamente offline (analyzeURL; non segue reindirizzamenti)Nessuno
get_app_helpUna ricerca nel testo completo della knowledge base in bundle (specifiche delle funzionalità/impostazioni/risoluzione dei problemi/spiegazioni dei messaggi di notifica)Nessuno
audit_secretsRileva chiavi API e chiavi private esposte in un testo, un file o un albero di directory (i risultati vengono mascherati nell'output).Nessuno
audit_security_logsAggrega i log di sicurezza recenti (Mac: errori di autenticazione sudo, tentativi di forza bruta SSH, blocchi Gatekeeper, rilevamenti XProtect, ecc. / Linux: errori di autenticazione sudo, forza bruta SSH, blocchi firewall, dinieghi AppArmor, rilevamenti ClamAV, ecc.), maschera automaticamente le informazioni sensibili come chiavi API e token, e restituisce anche i risultati del rilevamento di nuovi pattern (tramite template dei log) e delle anomalie di frequenza (picchi statistici). Su Linux, se il journal non può essere letto (per esempio per timeout), gli zeri nel conteggio non significano che non sia successo nulla; journalReadComplete diventa false e journalError indica il motivo.Solo locale
run_active_vuln_scanVerifica attiva delle vulnerabilità, non distruttiva e limitata a 127.0.0.1. Non contatta altri host, ma invia richieste di rete: per questo è disattivata per impostazione predefinita e richiede l'attivazione esplicita nelle impostazioni.Solo 127.0.0.1 (disattivato per impostazione predefinita)
run_package_cve_scanConfronta i pacchetti installati (Mac: Homebrew / Linux: dpkg, dnf, zypper, pacman) con una mappa CVE locale. Non avviene alcuna comunicazione di rete. Su Linux, se l'elenco dei pacchetti non può essere letto o scade il tempo, non viene riportato «nessuna corrispondenza»; complete diventa false e incompleteReason indica il motivo (un risultato incompleto non è un risultato pulito). Su Ubuntu (20.04, 22.04, 24.04, 26.04) usa i dati propri di Ubuntu, confronta per nome e versione del pacchetto sorgente e controlla solo le CVE con CVSS 7,0 o superiore per cui Ubuntu ha già pubblicato una correzione, quindi «nessuna corrispondenza» non significa che non esistano CVE note. Il risultato aggiunge ecosystem e release, ogni rilevamento riporta binary (il nome del pacchetto binario installato) e le correzioni disponibili solo con Ubuntu Pro hanno proOnly. Le derivate di Ubuntu (Linux Mint, Pop!_OS e altre) e le versioni non supportate restituiscono complete = false con il motivo in incompleteReason, senza passare ai dati di Debian.Nessuno
run_package_cve_scan_languagesConfronta i file di lock delle dipendenze di npm, PyPI, crates.io, RubyGems, Packagist, Go e Maven con la stessa mappa CVE locale. Non avviene alcuna comunicazione di rete.Nessuno
get_quarantine_statusRestituisce il contenuto del vault di quarantena: percorso originale, nome della minaccia rilevata, data e dimensione.Solo locale
get_canary_statusRestituisce lo stato dei file esca anti-ransomware (canary) e fino ai 50 incidenti rilevati più recenti.Solo locale
get_ransomware_recovery_snapshotsRestituisce gli snapshot APFS di recupero (precedenti al danno, di rilevamento, manuali), quello consigliato per il ripristino, lo stato della modalità di conservazione e l'intervallo degli snapshot. Uno snapshot di rilevamento può contenere già file cifrati. Solo Pro.Solo locale
search_exec_eventsCerca nel registro delle esecuzioni dei processi (eslogger) per percorso, argomenti o ID di firma e restituisce lo stato del registratore e se il registro è leggibile. Solo Pro.Solo locale
get_process_treeRicostruisce antenati e discendenti di un processo dal registro delle esecuzioni. Solo Pro.Solo locale
get_notification_historyRestituisce la cronologia delle notifiche inviate da RoamSwitch (anomalie di controllo dei log di sicurezza, rilevamenti ClickFix e altro) degli ultimi 7 giorni, le più recenti per prime.Solo locale
get_port_anomaly_incidentsRestituisce lo stato della baseline della protezione dalle porte anomale, le porte attualmente bloccate in automatico e fino ai 50 incidenti più recenti. La risposta indica esplicitamente che le porte attualmente bloccate sono un'istantanea dello stato attuale, priva di timestamp, distinta dalla cronologia degli incidenti, che invece è dotata di timestamp.Solo locale
get_ssh_guard_incidentsRestituisce lo stato e la cronologia della guardia contro la forza bruta SSH: se banna o si limita a rilevare (per esempio perché un altro strumento protegge già sshd), le origini bannate al momento con il tempo residuo e fino ai 200 record più recenti (un'origine è stata bannata, è stata rilevata senza ban, oppure un login è riuscito dopo una serie di fallimenti della stessa origine). I nomi utente provati non vengono salvati su Linux (solo quanti) e su Mac vengono restituiti ripuliti, fino a 5. Il Mac restituisce anche i login SSH recenti (origine, utente, metodo, impronta della chiave). Sola lettura: nessuno strumento MCP rimuove un ban. Su Linux l'elenco dei ban è leggibile solo da root, quindi chi chiama senza root riceve solo il numero. Linux: la Server Edition e la Client Edition finché sshd è in esecuzione. Mac: solo Pro.Solo locale
get_runtime_threat_statusIndica se questo Mac è isolato (Air-Gap) a causa di un rilevamento malware di XProtect e qual è l'incidente che lo ha attivato. È il primo strumento da consultare durante un Air-Gap attivo.Solo locale
get_ransomware_entropy_guard_statusRestituisce se il rilevamento generico del ransomware basato sull'entropia è attivo e i suoi recenti incidenti di burst di entropia.Solo locale
get_honeytoken_statusRestituisce se la guardia honeytoken delle credenziali è attiva e i suoi eventi recenti di rilevamento accessi.Solo locale
get_browser_credential_watch_statusRestituisce se il monitoraggio dell'accesso alle credenziali del browser è attivo e i suoi eventi recenti di rilevamento accessi.Solo locale
get_forensic_evidence_bundlesRestituisce l'elenco dei pacchetti di prove forensi (elenco processi, connessioni di rete, file modificati e simili) salvati quando è scattato un Air-Gap di emergenza.Solo locale
get_vulnerability_scan_historyRestituisce l'ultimo risultato registrato e i giorni trascorsi (pass_age) per ogni probe registrato da run_active_vuln_scan.Solo locale

Anche il campo instructions nella risposta initialize dichiara chiaramente: "Cannot change security level, isolate ports, or eject devices", comunicando così il limite delle capacità all'LLM lato client. Le risorse MCP (roamswitch://docs/*) sono anch'esse documenti Markdown di sola lettura. 1.10.0 aggiunge quattro documenti di triage in formato agentskills.io (roamswitch://skills/*, solo in inglese, per scelta progettuale).

Un'attribuzione errata scoperta — e corretta — durante un'esercitazione con un LLM locale

Nel settembre 2026, durante un'esercitazione (fire drill) in cui a un LLM locale (Qwen 3.8 27B) è stato chiesto di indagare su un incidente tramite questo server MCP, abbiamo scoperto un caso in cui il modello ha interpretato male i dati restituiti da get_port_anomaly_incidents, includendo erroneamente un blocco di porta vecchio e non correlato nella narrazione di un incidente in corso. La causa: un'istantanea priva di timestamp delle porte attualmente bloccate veniva restituita nella stessa risposta della cronologia reale degli incidenti, dotata di timestamp, senza nulla che distinguesse le due cose. La correzione aggiunge campi di nota espliciti che indicano quale campo rappresenti una linea temporale e quale sia solo un'istantanea. Rieseguendo lo stesso scenario con lo stesso modello dopo la correzione, il modello ha citato il testo della nota e ha esplicitamente rifiutato di attribuire la vecchia porta all'incidente attuale. L'efficacia della correzione è stata verificata sia con un test di regressione automatizzato sia con una nuova esecuzione del vero LLM locale.

Punto chiave: il codice sorgente è pubblico

Il codice sorgente di questo server e della logica di rilevamento che utilizza (monitoraggio ARP, scansione delle porte, verifica delle porte, il controllo di salute in 18 punti, analisi di sicurezza degli URL) è pubblicato su github.com/lafine1211/roamswitch-mcp (MIT, uno specchio del codice distribuito, taggato per ogni release). Potete verificare direttamente nel codice che è di sola lettura, cosa passa al LLM e che non invia nulla. Non include l'helper privilegiato, il controllo pf, le protezioni che agiscono, né le licenze: questi restano nel repository dell'app.

Con esso vengono forniti anche i test: unit test speculari, test di input contraddittorio e fuzzing delle mutazioni, eseguito da swift test e verificato in CI. Il fuzzing ha provocato uno schianto incustodito (JSONSerialization stack-overflow su un oggetto JSON profondamente annidato); è risolto con un controllo della profondità di nidificazione prima del parser e registrato in SECURITY_TESTING.md.

§9

Il gettone

  • Utilizza Ed25519 (firme Curve25519). La chiave pubblica è incorporata nell'app (LicenseVerifier.embeddedPublicKeyBase64). La chiave privata corrispondente esiste solo nel backend della licenza (una variabile di ambiente Firebase Functions) e non si trova nel repository.
  • I dati firmati sono JSON canonici. La firma viene creata e verificata sui byte esatti prodotti dalla codifica LicensePayload (chiave di licenza, livello, hash del dispositivo, data di emissione, scadenza, conteggio dei posti) con .sortedKeys e .withoutEscapingSlashes di JSONEncoder.
  • È progettato per non riuscire a chiudersi. Se la chiave incorporata è mancante o non valida oppure se non è possibile produrre la firma o il JSON canonico, il risultato non è "verificato" e restituisce invalidSignature.

Associazione del dispositivo

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

L'UUID hardware grezzo non viene inviato al server. Nel raro caso in cui IOKit non restituisce a UUID, ritorna a un UUID casuale conservato in UserDefaults. Al momento della verifica, se il device_hash del token non corrisponde all'hash del dispositivo corrente, il risultato è deviceMismatch.

Funziona offline

Punto chiave: funziona senza server

Tutto ciò che validateSavedLicense() fa all'avvio è leggere il token dal portachiavi e verificarlo localmente con la chiave pubblica incorporata. Non si connette alla rete. Se il il server delle licenze viene spento, le funzionalità Pro continuano a funzionare su un Mac già attivato. Il server viene contattato solo per una nuova attivazione e per una disattivazione esplicita. Il l'avviso di disattivazione al server è la soluzione migliore: anche se fallisce, la disattivazione locale completa sempre.

L'impostazione predefinita è un acquisto una tantum (a vita); expires_at viene controllato solo quando is_lifetime è falso. Il numero dei posti è espresso per livello: 2 per Personal Pro, 5 per Squadra.

§10

Firma e autenticazione notarile (scripts/release.sh)

  1. Esegui xcodebuild archive (rilascio, firma manuale, applicazione ID sviluppatore).
  2. Esporta con -exportArchive come method: developer-id.
  3. Dopo notarytool submit --wait, esegui stapler staple sul file .app.
  4. Verifica con spctl -a -t exec -vv.
  5. Ricomprimere dopo aver pinzato per produrre l'artefatto di aggiornamento Sparkle (in modo che il ticket di autenticazione sia incluso e venga eseguito offline senza un avviso di Gatekeeper).
  6. Costruisci il DMG, autentica e pinza anche il DMG e verifica con stapler validate.

Aggiornamenti (Sparkle 2.9.6)

Aggiorna le impostazioni in Info.plist
ChiaveValore
SUFeedURLhttps://lafine.net/updates/appcast.xml
SUPublicEDKeyCNxzwijMzMCJzliId76Yl88S/9np6t/xg/zQ9YbYzHs=
SUEnableAutomaticChecksvero
SUSscheduledCheckInterval86400

Prima che venga applicato un aggiornamento, viene verificata la firma EdDSA elencata nell'appcast contro SUPublicEDKey incorporato nell'app. La chiave di firma privata esiste solo in l'ambiente di costruzione. L'appcast viene servito tramite HTTPS. Gli aggiornamenti Delta sono verificati tramite firma allo stesso modo.

Punto chiave: l'appcast è pensato per essere pubblico

Il fatto che l'URL appcast.xml sia pubblico non è di per sé un punto debole. Il suo contenuto sono solo numeri di versione, note di rilascio, URL di download, dimensioni dei file e firma EdDSA di ogni build: niente di segreto. L'ancora di fiducia non è "l'appcast è autentico in transito" ma verificando la firma dell'artefatto con la chiave pubblica inserita nell'app. Un attaccante che può sostituire completamente l'appcast (MITM, dirottamento DNS, compromissione dell'host web) non è ancora possibile eseguire il push un aggiornamento dannoso senza la chiave di firma. Gatekeeper (ID sviluppatore e autenticazione) è a secondo cancello.

Rimangono due rischi. Uno è che gli aggiornamenti non arrivano, perché l'host è inattivo o l'appcast non funziona (non si verifica alcuna installazione errata: semplicemente non vieni aggiornato). L'altro è un attacco congelante trattiene deliberatamente un aggiornamento di sicurezza. Sparkle 2.x rifiuta downgrade e replay controllando ordinamento delle versioni, ma una difesa completa contro il congelamento necessita di un server di aggiornamento dedicato con scadenza. Questo è sulla nostra lista da affrontare.

§11

Cosa deve gestire RoamSwitch

  • Sondaggi e attacchi da parte di un utente malintenzionato sulla stessa LAN o da un dispositivo IoT compromesso. Risponde con la furtività, il controllo delle porte esposte e l'isolamento dall'esterno.
  • Esposizione su una rete di cui non ti fidi. Interrompe automaticamente la condivisione dei servizi e AirDrop.
  • Attacchi man-in-the-middle (spoofing ARP/NDP). A partire dalla versione 1.7.6 la difesa è a più livelli: (1) tunnel VPN + kill-switch (§5, la difesa principale, che non dipende dall'integrità di L2), (2) il blocco preventivo ARP/NDP del gateway sulle reti non attendibili (§5) e (3) il rilevamento dello spoofing con un air-gap di emergenza (a posteriori). Il tunnel VPN e il blocco del gateway sono funzioni Pro, disattivate per impostazione predefinita. Il rilevamento dello spoofing è attivo per impostazione predefinita e l'air-gap di emergenza si attiva per impostazione predefinita all'attivazione di Pro.
  • Trovare server e database di sviluppo (Redis, MongoDB, Elasticsearch e così via) esposti su 0.0.0.0 senza autenticazione e bloccarli dall'esterno.
  • Rilevamento tempestivo di attività di crittografia non autorizzate simili a ransomware e arresto di tutto il traffico (non si basa sulle firme).
  • BadUSB e protezione di approvazione della tastiera fisica (USBKeyboardGuard, CGEventTap + IOKit) che intercetta e rilascia le sequenze di tasti da tastiere/cavi USB non approvati (Rubber Ducky, O.MG Cable, ecc.) per prevenire attacchi di command injection automatizzati.
  • Una richiesta di approvazione per l'archivio USB sconosciuto (un'unità non riconosciuta viene mantenuta di sola lettura anziché espulsa immediatamente) e una scansione ClamAV automatica sull'archivio collegato (opzionale).
  • Rileva chiavi API sensibili (OpenAI, Anthropic, GitHub, AWS, ecc.) negli appunti in modo completamente locale, prevenendo un'incollatura accidentale in un modulo web o in una chat IA prima che avvenga.
  • Rileva e segnala i file di modelli IA in formato Pickle pericolosi (rischio di esecuzione di codice arbitrario) scaricati da Hugging Face o dal web.
  • La firma di test EICAR, standard del settore, e reverse shell da manuale ben documentate all'interno dei file scaricati (StaticSignatureScanner, dalla 1.8.7, funziona anche senza ClamAV installato).
  • Un LaunchAgent/LaunchDaemon appena registrato che invoca direttamente un interprete di script grezzo (PersistenceMonitorGuard, dalla 1.8.7).
  • Attacchi di tipo «ClickFix», in cui una falsa schermata di avviso induce a eseguire da soli un comando nel Terminale — rilevati tramite pattern di comandi noti come malevoli nella cronologia della shell, attivando un air-gap di emergenza (ClickFixGuard, dalla 1.8.7, Pro, disattivato di default).
  • Rileva e notifica l'istante in cui un container viene avviato con una configurazione Docker a rischio di escape dal container, come la modalità --privileged o un mount collegato di /var/run/docker.sock (DockerEventGuard, da 1.8.9, Pro, disattivato per impostazione predefinita, solo notifica).
  • Controllo delle fughe di segreti/chiavi API che ora copre non solo il testo incollato, ma anche la scansione ricorsiva a livello di cartella (SecretLeakAuditor, scansione cartelle aggiunta in 1.8.9, interamente sul dispositivo).

Cosa abbiamo deciso di non fare

  • Non è un sostituto dell'antivirus. ClamAV e XProtect sono usati come ausiliari; RoamSwitch da solo non è un rilevatore di malware generico.
  • Abbiamo deciso di non richiedere l'entitlement EndpointSecurity. I tassi di approvazione per un account Apple Developer individuale sono bassi, e qualsiasi funzionalità che richieda davvero un blocco pre-esecuzione (ad esempio ES_EVENT_TYPE_AUTH_EXEC) viene trattata come un obiettivo chiuso e non perseguito finché ciò resta vero. Al posto di bloccare l'esecuzione stessa, abbiamo implementato, senza tale entitlement, ciò che è praticabile come rilevamento a posteriori: rilevamento tramite firme statiche, monitoraggio dell'avvio automatico e difesa ClickFix.
  • Non è una garanzia. È uno strato di uno stack di difesa approfondita, non qualcosa che "previene completamente il ransomware". Anche il testo di marketing viene esaminato su questa premessa.
  • Non può proteggere un root o un kernel già compromesso. Se un utente malintenzionato ha già root, può rimuovere anche le regole pf dell'helper.
  • Non ripristina l'integrità L2 stessa. Il pin ARP/NDP preventivo è affidabile al primo utilizzo: se un utente malintenzionato è già presente prima della connessione, può bloccare un MAC falsificato. Il tunnel VPN (§5) è la risposta quando non si vuole fare questo presupposto: anche con L2 avvelenato, i contenuti sono crittografati e il kill-switch impedisce la fuoriuscita di testo in chiaro. Non sostituisce lo snooping DHCP aziendale o l'ispezione ARP dinamica.
  • Non fornisce un server VPN. La funzione tunnel utilizza una configurazione WireGuard fornita dall'utente; RoamSwitch non diventa un provider VPN.

Superficie di attacco aggiunta dall'installazione di RoamSwitch

Aggiunta la superficie di attacco e come è contenuta
Superficie d'attaccoCome è contenuto
Un LaunchDaemon che viene eseguito come root e il relativo servizio mach (com.tetsuharu.RoamSwitch.Helper) La superficie operativa è fissata su HelperProtocol (la tabella §3). Non esiste un canale di comando arbitrario. Le connessioni sono autorizzate da un requisito di firma del codice, utilizzando audit_token.
Se lo stesso RoamSwitch.app viene compromesso, tutte le operazioni dell'helper passano all'aggressore Hardened Runtime è attivo e all'app non vengono concessi privilegi non necessari. L'invio in rete è limitato ai percorsi della tabella del §7 (e alle funzioni avviate dall'utente o disattivate per impostazione predefinita descritte sotto la tabella). Non è dichiarato alcun entitlement di invio in rete, ma poiché l'app non è sottoposta a sandbox, ciò di per sé non vieta le comunicazioni (misurazioni alla fine del §7). Prevediamo di farlo revisionare da una terza parte.
I percorsi binari di sistema generati dall'helper I percorsi assoluti come /sbin/pfctl vengono specificati direttamente, senza dipendenza da PATH. Anche gli argomenti sono codificati (a parte i numeri di porta, le stringhe DNS, le coppie IP ARP/MAC e l'IP dell'endpoint VPN, tutti con convalida del formato).
Riscrittura della protezione del collegamento /etc/hosts (setLinkGuardSinkhole) Le scritture sono limitate a una sezione gestita delimitata; le linee esterne ad essa sono preservate alla lettera. I domini vengono normalizzati e convalidati, gli IP e la posta indesiderata vengono eliminati, l'elenco è limitato a 60.000 e il file viene scritto tramite un file temporaneo + sostituzione atomica. Si limita a incanalare i casi chiari (elenco di feed di minacce o omografo di marchi), decisi da un motore di verdetto esclusivamente locale. L'elenco dei blocchi stesso proviene dal feed delle minacce con firma verificata (chiave dedicata al feed).
Recupero del feed delle minacce di Link Guard (un giorno di sola ricezione GET) Recupero di un file statico senza stringa di query e senza identificatore per dispositivo (solo lo User-Agent standard). Il manifest e il corpo del feed sono entrambi verificati secondo Ed25519 e un recupero che non supera la verifica viene scartato (nessun fallback su dati non firmati). "Aggiornamento automatico: disattivato" rimuove completamente il percorso.
Pin ARP/NDP preventivo (lockGatewayARP) Vengono bloccate solo le voci della cache dei vicini per "il gateway, il router IPv6, il DNS sul collegamento". Le coppie IP/MAC passate vengono convalidate in termini di formato e riconciliate con il set di richieste (non aggiunge mai voci da solo). Funziona solo su reti non affidabili; Pro, disattivato per impostazione predefinita. Il limite di fiducia al primo utilizzo è stabilito nel §11 "Cosa abbiamo deciso di non fare".
Tunnel VPN (wireGuardImport/Up/…, Homebrew wireguard-tools) Il .conf viene memorizzato dall'helper in 0600. L'IP dell'endpoint viene risolto dall'app e passato all'helper (l'helper non risolve mai un nome host arbitrario). La destinazione e i contenuti del tunnel sono sotto il controllo dell'utente. La funzionalità è disabilitata se wireguard-tools non è installato. Pro, disattivato per impostazione predefinita. Il kill switch passa attraverso PFRulesetCoordinator (§4).
Il server MCP passa lo stato del sistema a un LLM (un sostituto confuso) È di sola lettura, senza API di scrittura implementata. I controlli degli URL sono offline. Il dominio delle impostazioni è di sola lettura.
Dirottamento del percorso di aggiornamento Verifica della firma EdDSA (SUPublicEDKey), più un ticket di notarile in bundle. L'appcast è su HTTPS.

Appendice A

Tutto quanto affermato in questo documento può essere verificato rispetto all'artefatto distribuito con i seguenti comandi.

Firma e notarile

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

# Se il ticket di notarizzazione è stato allegato (staple)
stapler validate /Applications/RoamSwitch.app
spctl -a -t exec -vvv /Applications/RoamSwitch.app

# Firma dell'helper incluso / server MCP
codesign -dvvv /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchHelper
codesign -dvvv /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchMCPServer

Entitlements (verifica che non sia dichiarato alcun entitlement di invio in rete; poiché l'app non è sottoposta a sandbox, ciò di per sé non vieta le comunicazioni)

codesign -d --entitlements :- /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchHelper
codesign -d --entitlements :- /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchMCPServer
# → Un dizionario di entitlements vuoto. Nessuna chiave app-sandbox / network client.

Misurare il traffico

# Esegui tcpdump in parallelo per confermare che, nell'uso normale, non ci sia traffico al di fuori dei percorsi della tabella
sudo tcpdump -i any -n 'host not 127.0.0.1' and 'not port 53'
# Nessun traffico se non i percorsi della tabella (attivazione della licenza, controlli di aggiornamento, dati di aggiornamento firmati, aggiornamenti di ClamAV e così via)

Per un controllo più rigoroso e automatizzato con attribuzione per processo, vedere rs-zerotel-audit.sh in roamswitch-support/audit/

Architettura di difesa e test di penetrazione (5 confini di difesa)

# Verifica automaticamente in un unico passaggio il confine di autorizzazione XPC, la priorità Air-Gap di pf, il rilevamento dell'esposizione delle porte, la modalità di sola lettura di MCP e il monitoraggio ARP
git clone https://github.com/lafine1211/roamswitch-support
cd roamswitch-support/audit
./rs-defense-audit.sh all

Test di penetrazione in tempo reale e audit di difesa multilivello da una VM macOS: RESULTS-DEFENSE-2026-08-30

Verifica della protezione dalle minacce DNS (malware Quad9 e blocco C2).

# Interroga il dominio di test ufficiale di Quad9 per verificare il blocco delle minacce
nslookup test.dns9.quad9.net
# → Deve restituire ** server can't find test.dns9.quad9.net: NXDOMAIN, cioè la risoluzione dei nomi è bloccata

Verifica di Web & Mail Download Guard (rilevamento in tempo reale con ClamAV)

# Crea la stringa di test antivirus standard e innocua (EICAR) in Downloads
echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > ~/Downloads/eicar_test.com
# → Il rilevamento FSEvents attiva una scansione immediata di ClamAV; non viene mostrata alcuna notifica e "firma di test EICAR rilevata (innocua)"
#   viene registrata nella cronologia delle notifiche. Poiché EICAR è un file di test standard del settore e non una minaccia reale,
#   non viene messo in quarantena né bloccato e il file resta al suo posto (solo un vero campione di malware viene spostato nel gestore della quarantena).

Verifica del rilevamento tramite firme statiche (senza necessità di EndpointSecurity)

## Una reverse shell da manuale tratta da riferimenti pubblici di offensive security (innocua, non viene mai eseguita)
echo 'bash -i >& /dev/tcp/127.0.0.1/4444 0>&1' > ~/Downloads/rs_test.sh
## → Anche senza ClamAV installato, appare una notifica «File scaricato pericoloso messo in quarantena» e il file viene
##   spostato immediatamente nella cartella Quarantine (indipendentemente dal database delle firme di ClamAV)

Verifica del nuovo monitoraggio dell'avvio automatico (PersistenceMonitorGuard)

# Creare un LaunchAgent innocuo che invoca direttamente un interprete di script grezzo (non viene mai realmente eseguito)
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
# → Entro pochi secondi dovrebbe apparire una notifica «Rilevata nuova registrazione di avvio automatico». Rimuoverlo in seguito:
rm ~/Library/LaunchAgents/com.example.selfcheck.plist

Link Guard (blocco delle connessioni di phishing)

# Se la sezione gestita è presente (2 righe BEGIN/END in modalità Pro + predefinita)
sudo grep -c 'RoamSwitch link guard' /etc/hosts

# Il contenuto della sezione e il numero di sinkhole
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 viene effettivamente bloccato (verificato con una voce della sezione; innocuo)
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 (risoluzione dei nomi bloccata)

# Verifica anche che la sezione precedente scompaia tramite Menu → "Protezione link" → "Off"

Il feed delle minacce di sola ricezione (firma verificata)

# Feed e manifest pubblici (chiunque può recuperarli e verificarli)
curl -s https://lafine.net/updates/v1/manifest        # version/generated/threatfeed{...}
curl -sI https://lafine.net/updates/v1/manifest.sig   # → text/plain
# Viene inviata solo una GET senza query string, cookie o identificatore per dispositivo (solo lo User-Agent standard). Verificabile eseguendo tcpdump in parallelo.

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

# Connettersi a una rete non attendibile con il blocco preventivo attivo → verificare le voci permanent
arp -an | grep -i permanent          # Il gateway e le altre IP sono elencati come (permanent)
ndp -an | grep -i 'P '               # Lato IPv6 (P = permanent)
sudo cat "/Library/Application Support/RoamSwitch/gateway_arp_lock.json"  # L'insieme delle mappature IP→MAC attualmente fissate
# Rilascia il blocco dal menu → quanto sopra dovrebbe scomparire

Tunnel VPN + kill switch (§5, 1.7.6+)

# Presuppone wireguard-tools (tramite Homebrew)
brew list wireguard-tools >/dev/null && echo "wireguard-tools: OK"

# Attiva la VPN su una rete non attendibile → prima che il tunnel sia attivo, pf è in stato di kill-switch
sudo pfctl -sr | grep -E 'block drop all|pass .*(utun|udp)'   # block drop all + un insieme limitato di regole pass quick

# Dopo che il tunnel è attivo (wg-quick up già eseguito)
wg show                              # handshake / transfer è attivo
route -n get default | grep interface  # → utunN (la route predefinita è il tunnel)
# Disattiva la VPN → il kill-switch viene rilasciato e pf torna allo stato precedente

L'aiutante privilegiato stesso

# Il LaunchDaemon registrato
sudo launchctl print system/com.tetsuharu.RoamSwitch.Helper

# Le regole pf attualmente caricate (stato reale di Air-Gap / guardia porte)
sudo pfctl -sr

# La directory di stato dell'helper
ls -la "/Library/Application Support/RoamSwitch/"

La risposta del server MCP (controllo 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"
# Restituisce serverInfo e le definizioni degli strumenti. Non si verifica alcuna connessione di rete.

Sorgente e test del server MCP

git clone https://github.com/lafine1211/roamswitch-mcp
cd roamswitch-mcp
swift build -c release        # Lo stesso codice sorgente del binario distribuito
swift test                    # Test unitari, input avversari, stdio e fuzzing a mutazione
# Consulta SECURITY_TESTING.md per il contenuto dei test e i problemi riscontrati

Identificatore del dispositivo

# Il valore grezzo associato al token (viene inviato solo uno SHA-256 con salt)
ioreg -d2 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}'
White paper Architettura / Sicurezza di RoamSwitch v1.6 · Ambito 1.10.0 (build 116) · Lafine Systems Design · 2026-09-23
Segnala eventuali errori o omissioni su lafine.net/contact.html.