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.5 Ambito RoamSwitch 1.9.47 (build 104) Requisiti macOS 13.0+ / Apple Silicon Pubblicato 2026-09-15 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 Qualsiasi altra cosa. Non esiste un'interfaccia per l'esecuzione di comandi arbitrari. Non ha nemmeno il diritto di invio in rete
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 Modifica le impostazioni, attiva il blocco, isola una porta, espelli un dispositivo. Non apre alcuna presa. Non invia nulla sulla rete

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

§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 si impedisce al traferro di persistere

  • Si disattiva dopo al massimo 10 minuti. L'helper stesso continua a eseguire releaseAirGapIfExpired() subito dopo l'avvio e su un timer di 60 secondi, ma come rete di sicurezza completamente indipendente, un LaunchDaemon dedicato (AirGapFailsafe) privo di qualsiasi KeepAlive si risveglia da solo ogni 3 minuti ed esegue lo stesso controllo e rilascio. Se l'helper stesso va in crash trascinando con sé il proprio timer di autorilascio, questa rete di sicurezza non ne risente (dalla 1.8.6).
  • 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 una rigenerazione del daemon. All'avvio, l'helper legge lo stato su disco con reapplyFromDisk() e lo ripristina da solo, nell'ordine air-gap, protezione delle porte, impostazione predefinita di sistema (anche qui si applica la regola dei 10 minuti).
  • 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 il pulsante dell'ascensore in modalità o semplicemente disattivando il Wi-Fi.

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

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 CLI autonoma (brew install 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.
  • 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 sulla porta, URL o registri. Non sono inclusi alcun SDK di analisi e alcun SDK di crash reporter. L'unica libreria esterna è Sparkle (aggiornamenti). Ciò che esce in rete sono questi otto e basta (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 host, versione dell'app. Le informazioni personali vengono gestite da Stripe al momento dell'acquisto; l'app non lo gestisce
Controllo degli aggiornamenti lafine.net
/updates/appcast.xml
Sparkle, ogni 24 ore e al momento del lancio 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}
Quando Link Guard (§5) è attivo e "Aggiornamento automatico" è abilitato, ogni 24 ore (e all'avvio). La disattivazione dell'"Aggiornamento automatico" rimuove questo percorso Solo GET. Nessuna stringa di query, nessun cookie, niente che identifichi la macchina. Un file statico firmato receive-only; il manifest e il corpo del feed sono entrambi verificati con Ed25519. La chiave di firma è dedicata al feed — una chiave separata dall'aggiornamento dell'app SUPublicEDKey (quindi una fuga di notizie è limitata a "una blocklist errata")
Dati di aggiornamento della verifica CVE dei pacchetti / della verifica attiva delle vulnerabilità lafine.net
/updates/v1/manifest
Ogni 24 ore a partire dall'avvio dell'app (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; 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. 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 Solo quando l'utente ha installato ClamAV e utilizza la funzione di scansione. Si avvia freshclam Un recupero della definizione ClamAV standard. Non contiene informazioni derivate da RoamSwitch
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"

"Zero Telemetry" qui significa che non esiste alcuna telemetria che raccolga e invii dati di utilizzo o risultati diagnostici. Ciò non significa che non ci sia traffico di rete. Gli otto percorsi nel la tabella sopra esiste. Ma ognuno di essi è qualcosa che l'utente avvia oppure a recupero con firma verificata, receive-only e risultati diagnostici, porte, URL, e il contenuto dei file sul Mac non lo lascia mai.

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.

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

Questo non è solo affermato. Il 29-08-2026 è stata controllata un'installazione 1.4.7 in esecuzione tcpdump + attribuzione per processo (nettop / lsof / a filtrato pktap capture) + LuLu, in una finestra di circa 2 ore, con il livello di sicurezza bloccato Blocco massimo e controllo appcast forzato. Risultato: nessun flusso in uscita attribuito a RoamSwitch, RoamSwitchHelper, o RoamSwitchMCPServer diverso da appcast controlla su lafine.net; il MCP gli unici socket del server erano su localhost; i dump dei diritti sono vuoti.

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, prima del feed delle minacce Link Guard (1.7.2, riga 3) e della VPN tunnel (1.7.6, riga 5). Controllato sulla versione 1.7.2 o successiva, vedrai due di sola ricezione da GETs a lafine.net (l'appcast e il feed delle minacce); se una VPN è configurato vedrai anche UDP sul server WireGuard dell'utente (destinazione e contenuto sotto il controllo dell'utente). Tutto scompare con "Aggiornamento automatico: disattivato" e nessuna VPN configurata.

§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. Non registra un servizio né ascolta. Legge una riga da stdin, restituisce una riga su stdout e quindi il client termina il processo.
  • Non invia nulla. Tutta la diagnostica viene completata all'interno del Mac.
  • 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

Gli strumenti/l'elenco degli strumenti restituiscono e i dati restituiti
StrumentoCosa restituisceTraffico
get_security_reportUn controllo in 18 punti (FileVault / SIP / Gatekeeper / aggiornamento automatico / XProtect / firewall / crittografia Wi-Fi / ARP / blocco ARP del gateway / verifica della configurazione SSH / verifica sudo NOPASSWD / porte esposte / configurazione delle protezioni, e altro) con un punteggio e consigli di correzione per ciascun puntoSolo locale
get_porte_esposteUn elenco di porte TCP in ascolto. Per qualsiasi esposizione oltre localhost, fa riferimento incrociato a un DB di servizio noto come pericoloso e controlla CORS/intestazioni con un proxy HTTP su 127.0.0.1:port (locale, chiuso)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).Solo locale
run_active_vuln_scanVerifica attiva delle vulnerabilità, non distruttiva e limitata a 127.0.0.1. L'unico strumento che usa la rete: disattivato per impostazione predefinita, richiede l'attivazione 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.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_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_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

Anche il campo instructions nella risposta initialize afferma chiaramente: "Impossibile modificare il livello di sicurezza, isolare le porte o espellere dispositivi", comunicando la funzionalità confine al LLM lato client. Le risorse MCP (roamswitch://docs/*) sono di sola lettura Anche documenti Markdown.

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 questo è stratificato: (1) tunnel VPN + kill-switch (§5, la difesa primaria — non dipende dall'integrità L2), (2) un pin ARP/NDP del gateway preventivo su reti non attendibili (§5) e (3) rilevamento dello spoofing con un air-gap di emergenza (a posteriori). Tutte le versioni Pro, disattivate per impostazione predefinita (il rilevamento è attivo per impostazione predefinita).
  • 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 Il runtime rafforzato è abilitato e all'app non vengono concessi privilegi non necessari. Il traffico in uscita è limitato agli otto percorsi sopra indicati (il sesto solo se l'utente configura una VPN, il settimo solo se è attivata l'associazione RoamSwitch Sensor). 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 identificatori. 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

Diritti (nessuna autorizzazione di invio in rete)

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 non ci sia traffico durante l'uso normale
sudo tcpdump -i any -n 'host not 127.0.0.1' and 'not port 53'
# Nessun traffico se non l'attivazione della licenza, i controlli di aggiornamento e gli aggiornamenti di ClamAV

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 identificatori. 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 dei 5 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.5 · Ambito 1.9.28 (build 85) · Lafine Systems Design · 2026-09-15
Segnala eventuali errori o omissioni su lafine.net/contact.html.