§1
RoamSwitch est une application de barre de menus pour Mac. En fonction de votre confiance dans le réseau que vous faites
actuellement connecté, il bascule automatiquement le pare-feu macOS, les services de partage, AirDrop,
et DNS. It also watches for ARP spoofing, ports open to the outside, USB storage, and
ransomware-like encryption activity, and when it detects something dangerous it will go as far as
cutting off traffic with the packet filter (pf) — an emergency air-gap.
En d'autres termes, RoamSwitch installe un démon bénéficiant des privilèges root et, s'il le voulait, il pourrait tout arrêter. trafic réseau sur Mac. It is built by one person, and "please trust me" is not enough to justifier ce privilège. Au lieu de cela, ce document explique la conception sous une forme que vous pouvez vérifier.
Pour qui c'est
- Les ingénieurs décident de l'installer ou non
- Security researchers and journalists who want to understand the internals before a review or an article
- Personnel de sécurité des entreprises partenaires évaluant un déploiement interne ou un regroupement OEM
Ce que ce document ne couvre pas
It does not go into detection-threshold tuning, false-positive statistics, or UI walkthroughs.
Ce qu'il explique, ce sont quatre choses : les privilèges , les limites des processus, le flux de données et
cryptographie. Les spécifications des fonctionnalités elles-mêmes se trouvent dans la ressource MCP fournie
roamswitch://docs/features et dans l'aide intégrée à l'application.
Ce qui est divulgué et dans quelle mesure (politique de divulgation)
This document is written on the assumption that an attacker already has the distributed binary.
Every endpoint URL, identifier, file path, XPC protocol, and embedded public key that appears here
peut être retiré du colis d'expédition RoamSwitch.app en quelques minutes avec
strings, codesign -d ou un proxy de trafic. Les écrire ici donc
ne donne rien de nouveau à un attaquant. La seule chose qu'il fait progresser est la compréhension du critique.
On the other hand, server-side implementation that cannot be seen from the binary — rate-limit thresholds, keys, admin endpoints, the DB schema, the Firebase project layout — is not included. La conception de l'intégration des OEM et des partenaires est également hors de portée ici et est couverte dans un document distinct. document interne. « Ce que vous pouvez apprendre en observant le client » est la ligne de divulgation pour cela. documenter.
Ce document correspond à la source de la version notée en haut. Où les comportements changent
in a later version, the document is revised and the version number and target build are updated.
If you find a discrepancy between the text and the code, please let us know at
lafine.net/contact.html.
§2
RoamSwitch.app est composé de trois exécutables. Un seul d’entre eux est privilégié ; le
deux autres fonctionnent avec des droits d'utilisateur de connexion. All three ship with Hardened Runtime enabled, Developer ID
signé et notarié.
| Exécutable | Privilège | Peut faire | Je ne peux pas faire |
|---|---|---|---|
| RoamSwitch .app |
Utilisateur connecté | Surveillez l'état du réseau, exécutez des diagnostics, dessinez l'interface utilisateur, appelez l'assistant via XPC, modifiez AirDrop via defaults, lancez ClamAV (facultatif) |
Faire fonctionner directement le pare-feu, pf ou les démons système (tout cela passe par l'assistant) |
| RoamSwitch Helper |
racine | Uniquement les opérations répertoriées dans HelperProtocol (tableau §3) : pare-feu/furtivité, chargement/déchargement des démons de partage, application du jeu de règles pf, changement de DNS, envoi de signaux aux processus |
N'importe quoi d'autre. Il n'y a pas d'interface pour exécuter des commandes arbitraires. Il n'a pas non plus de droit d'envoi sur le réseau |
| RoamSwitch MCPServer |
Utilisateur connecté | Lire et formater les valeurs de diagnostic, rechercher dans la base de connaissances locale ; les résultats sont renvoyés au client via stdio | Modifiez les paramètres, activez le verrouillage, isolez un port, éjectez un appareil. Il n'ouvre aucune prise. Il n'envoie rien sur le réseau |
Droits et signature
- Les trois cibles ont
ENABLE_HARDENED_RUNTIME = true. - L'application Sandbox est désactivée (
com.apple.security.app-sandbox = false). Les droits de l'assistant et de MCPServer sont des dictionnaires vides. - Les versions de distribution sont des applications d'ID de développeur signées, notariées par Apple et agrafées (§10).
L’App Sandbox n’est pas utilisé. RoamSwitch doit lire l'UUID matériel d'IOKit, utiliser CoreWLAN et
DiskArbitration, énumérer les sockets d'écoute des autres processus (lsof), ouvrir un XPC
connexion à un LaunchDaemon et générer des binaires système. Rien de tout cela n'est possible à l'intérieur du
sandbox, il reste donc désactivé.
Quatre choses compensent cela. Tout d’abord, le runtime renforcé. Deuxièmement, la signature de l'ID de développeur et notarisation. Troisièmement, un seul exécutable s'exécute en tant que root - l'assistant - et ce que cet assistant peut faire est fixe et énuméré (le tableau §3). Quatrièmement, les connexions à l'assistant sont limitées par le code signature (§3).
§3
Comment il est enregistré
L'assistant est enregistré en tant que LaunchDaemon en utilisant SMAppService.daemon(plistName:). Le
plist intégré à l'application
(Contents/Library/LaunchDaemons/com.tetsuharu.RoamSwitch.Helper.plist) déclare uniquement
Label, BundleProgram, une seule entrée MachServices, et
AssociatedBundleIdentifiers. En raison du fonctionnement de SMAppService,
l'inscription n'est même pas possible à moins que l'application soit en /Applications. Au premier
l'enregistrement, l'assistant ne devient actif que lorsque l'utilisateur l'approuve manuellement dans le système
Paramètres.
Dont les connexions sont acceptées (ClientValidator)
L'assistant inspecte le processus de connexion dans les NSXPCListener
shouldAcceptNewConnection et distribue HelperProtocol uniquement à ceux qui
passer. L'inspection utilise le audit_token plutôt que le PID, pour
éviter la réutilisation du PID et du TOCTOU.
# Exigences de signature de code imposées par le build Release aux clients qui se connectent
identifier "com.tetsuharu.RoamSwitch"
and anchor apple generic
and certificate leaf[subject.OU] = "GV76B6G4YU"
Cette exigence est vérifiée avec SecCodeCopyGuestWithAttributes et
SecStaticCodeCheckValidity, et la connexion est interrompue si elle échoue. Seulement
Les versions DEBUG suppriment le code PIN d'identification de l'équipe, pour faciliter le développement. Ce qui est réellement expédié est toujours un
Version de publication.
La sécurité de l'assistant repose sur cette seule exigence de signature de code. Tout ce qui le satisfait (un
correctement signé RoamSwitch.app) peut appeler toutes les opérations du tableau ci-dessous. Il y a
aucun canal pour lui envoyer des commandes arbitraires, mais les opérations de cette table ne sont pas faibles en
eux-mêmes. Si RoamSwitch.app lui-même est repris, ces opérations passent au
attaquant.
Ce que l'assistant peut faire (la liste complète)
Les opérations privilégiées définies dans Shared/HelperProtocol.swift le sont toutes.
Il n’existe aucune API privilégiée qui ne soit répertoriée ici.
| Méthode | Ce que ça fait | Binaire/API invoquée |
|---|---|---|
| setBlockAll(_:) | Active ou désactive le pare-feu des applications et le mode furtif | /usr/libexec/ApplicationFirewall/ --setblockall / --setstealthmode |
| obtenirBlockAllStatus(...) | Lit les valeurs actuelles de ce qui précède | socketfilterfw --getblockall |
| setSharingServicesEnabled(_:) | déchargement/chargement des démons SSH/SMB/Partage d'écran. A l'arrêt, il enregistre uniquement "ceux qui étaient en cours d'exécution" et ne restitue que ceux | /bin/launchctl list / unload -w / load -w (fixé aux trois : ssh.plist / com.apple.smbd.plist / com.apple.screensharing.plist) |
| activerNetworkAirGap(...) disableNetworkAirGap(...) |
Applique et lève le bloc complet d'urgence (block drop all). Passe par PFRulesetCoordinator (§4) |
/sbin/pfctl -f / -e / -sr |
| setGuardedDevServerPorts(_:) | Utilise pf pour bloquer uniquement les connexions external aux ports du serveur de développement donnés (localhost passe par). Passer un tableau vide les soulève tous | /sbin/pfctl (également via le coordinateur) |
| setSecureDNSServers(_:) restoreOriginalDNSServers(...) getCurrentDNSServers(...) |
Bascule le DNS des services réseau actifs vers un DNS bloquant les logiciels malveillants (Quad9 9.9.9.9 / Cloudflare 1.1.1.2), en sauvegardant les paramètres d'origine et en les restaurant |
/usr/sbin/networksetup -listallnetworkservices / -getdnsservers / -setdnsservers |
| setLinkGuardSinkhole(_:) | Lien Garde (§5). Écrit les domaines de phishing/arnaque donnés dans une section gérée délimitée de /etc/hosts sous la forme 0.0.0.0, puis vide le cache DNS. Un tableau vide supprime la section. Les domaines sont normalisés et dédupliqués ; Les adresses IP et les fichiers indésirables sont supprimés ; plafonné à 60 000 ; écrit via un fichier temporaire + remplacement atomique |
réécrit /etc/hosts (FileManager.replaceItemAt)/usr/bin/dscacheutil -flushcache /usr/bin/killall -HUP mDNSResponder |
| lockGatewayARP(_:) unlockGatewayARP(...) getGatewayARPLockStatus(...) |
Verrouillage préventif ARP/NDP de la passerelle (§5). Épingle les mappages IP → MAC donnés (la passerelle IPv4, le routeur IPv6 par défaut, les résolveurs DNS en liaison) en tant qu'entrées de cache de voisin permanent. Les formats IP et MAC sont validés ; un modèle de réconciliation (les broches non présentes dans la requête sont supprimées). Le jeu de broches est conservé jusqu'à gateway_arp_lock.json |
/usr/sbin/arp -s / -d /usr/sbin/ndp -s / -d |
| wireGuardImport(_:) wireGuardForget(...) |
Tunnel VPN (§5). L'assistant enregistre/supprime le texte WireGuard .conf à 0600 |
fichier en écriture seule |
| wireGuardUp(endpointIPv4:endpointIPv6:port:) wireGuardDown(...) wireGuardStatus(...) |
Amener le tunnel vers le haut/vers le bas/lire l'état. Le nom d'hôte du point de terminaison est résolu par l'application et l'adresse IP est transmise à l'assistant (le DNS de l'assistant peut être coupé par le kill-switch) | Homebrew wg-quick up/down, wg show (wireguard-tools ; désactivé s'il n'est pas installé) |
| terminerProcess (pid: forceKill :) | Suspend (SIGSTOP) ou force la fermeture (SIGKILL) d'un processus. Utilisé pour contenir des processus de type ransomware. pid > 1 seulement |
appel système kill(2) (pas un sous-processus) |
| getHelperVersion(...) | Renvoie la chaîne de version de l'assistant (utilisée pour les vérifications de compatibilité des applications) | — |
terminateProcess peut envoyer SIGKILL à n'importe quel processus, à condition que
pid > 1. setSecureDNSServers accepte n'importe quelle chaîne de serveur DNS. C'est
la largeur dont la fonctionnalité a besoin, mais elle n’est pas étroite. Jugez-le en sachant que le
le contrôle de signature de code devant lui (ClientValidator) est la seule porte.
État à l'intérieur de l'assistant
HelperTool.sharedest une instance unique partagée entre les connexions. Auparavant, il s'agissait d'une instance distincte par connexion, donc un confinement d'urgence qui ouvrait une nouvelle connexion pouvait frapper une course qui perdait la trace des « services à ramener ».- Les sauvegardes du service de partage et DNS sont uniquement mutées sur une file d'attente série (
stateQueue).
§4
Quatre fonctionnalités touchent pf : l'entrefer d'urgence, la protection du port du serveur de développement, le tunnel VPN
Le kill-switch WireGuard (§5, 1.7.6+) et son kill-switch Tailscale (§5, 1.8.0+ — uniquement le backend sélectionné). Tous y vont toujours
via un point d'entrée unique, PFRulesetCoordinator, et ne jamais exécuter
pfctl -f eux-mêmes.
Pourquoi il y a un point d'entrée unique
Auparavant, les deux fonctionnalités chargeaient chacune des règles avec pfctl -f indépendamment, en concurrence
sur l'ensemble de règles principal unique de pf. Si la règle étroite block ... port {…} du garde portuaire était
chargé après le block drop all de l'entrefer, vous pourriez vous retrouver dans un état où l'écran
dit "isolé" mais le Mac était toujours accessible. Ce bug a été trouvé en attaquant réellement le
machine d'un autre hôte, et a été corrigé dans la version 1.4.3 (l'histoire est écrite dans
docs/marketing/zenn/03_lan_side_attack_test.md).
Comment ça marche maintenant
- Reconstruit entièrement à chaque fois. L'ensemble des règles requises est reconstruit à partir de l'état actuel et appliqué en une seule fois. Il n’est jamais appliqué comme différence.
- Une file d'attente série. Chaque modification de pf s'exécute sur le même
DispatchQueue, donc qu'elle provienne d'une connexion XPC, du démarrage de l'assistant ou de la minuterie de sécurité, les modifications sont traitées dans l'ordre. - L'ordre de priorité est le suivant (les éléments les plus haut priment).
- Entrefer de secours →
set skip on lo0etblock drop all(rien d'autre n'est pris en compte) - Kill-switch VPN →
block drop allpluspass quickpour uniquement :lo, l'interface du tunnel (utunN), la prise de contact UDP avec les IP du point de terminaison épinglé, DHCP et ICMP - Garde du serveur de développement →
block drop in quick proto tcp ... port { … } - Aucun → rechargez
/etc/pf.confet remettez pf à son état d'origine
- Entrefer de secours →
- Relisez après l'application.
pfctl -srlit les règles pour confirmer queblock drop all, ou la règle de chaque port, est réellement chargée. Un cas oùpfctl -fa été silencieusement ignoré n'est pas traité comme un succès. - Le fichier temporaire est écrit dans un chemin contenant un
UUIDet supprimé une fois appliqué (la v1.4.5 a abandonné le chemin fixe au profit d'un chemin difficile à deviner). Le répertoire d'état est/Library/Application Support/RoamSwitch.
Les réponses XPC de enableNetworkAirGap et setGuardedDevServerPorts,
(Bool, String?), indiquez si l'opération s'est déroulée jusqu'au bout du
relecture. L'appelant (tel que ARPSpoofContainmentManager) réessaye en cas d'échec, et si
il échoue toujours, il affiche le message directement à l'écran : "Le trafic n'est pas encore arrêté. Éteignez
Wi-Fi maintenant."
§5
Les types de blocs sont différents
| Genre | Portée | Déclencheur | bouclage |
|---|---|---|---|
| Entrefer d'urgence | Arrête tout le trafic, entrant et sortant | Lorsqu'une activité de chiffrement de type ransomware est détectée. Lors de la détection d'usurpation d'identité ARP, il se déclenche immédiatement uniquement en cas de verrouillage ; sur les réseaux équilibrés/de confiance, il notifie à la place (vous le déclenchez manuellement). Sur un motif de commande connu de type « ClickFix » dans l'historique du shell, comme décoder du base64 directement vers un shell ou AppleScript (1.8.7+, Pro, désactivé par défaut). Non utilisé pour la protection quotidienne hors du domicile | Transmis avec set skip on lo0 |
| VPN kill-switch | Tout sauf le tunnel, sa poignée de main, DHCP et ICMP | Lorsque le tunnel VPN (§5, Pro, désactivé par défaut) est activé et que vous rejoignez un réseau non fiable. Tenu jusqu'à ce que le tunnel soit établi (et pendant qu'il est en panne) | Transmis avec set skip on lo0 |
| Garde de port du serveur de développement | Seules les connexions TCP external aux ports donnés | Une isolation manuelle en un clic ou un blocage automatique lorsqu'un port d'écoute inconnu est détecté (Pro) | Depuis localhost, inchangé |
| Protection quotidienne des réseaux non fiables | Pare-feu et furtivité, partage arrêté (§6). pf n'est pas utilisé | Lorsque vous vous connectez à un réseau sur lequel vous n'êtes pas enregistré | — |
| Garde de lien | Uniquement la résolution de noms de domaines de phishing/arnaque (0.0.0.0 via /etc/hosts). pf n'est pas utilisé |
Une destination sur le fil des menaces ou un homographe de marque. Activé par défaut (Pro) | Non affecté |
| Verrouillage préventif ARP/NDP | Uniquement le MAC de la passerelle, le routeur IPv6 et le DNS en liaison (cache voisin). pf n'est pas utilisé | En rejoignant un réseau non fiable (Pro, désactivé par défaut). Ré-épinglé à chaque changement de réseau | Non affecté |
Comment l’entrefer ne persiste pas
- Il se lève après 10 minutes au maximum. L'assistant lui-même continue d'exécuter
releaseAirGapIfExpired()juste après le démarrage et sur une minuterie de 60 secondes, mais en tant que filet de sécurité totalement indépendant, un LaunchDaemon dédié (AirGapFailsafe), sans aucunKeepAlive, se réveille de lui-même toutes les 3 minutes et effectue la même vérification et la même levée. Si l'assistant lui-même plante et entraîne avec lui sa propre minuterie d'auto-levée, ce filet de sécurité n'est pas affecté (à partir de 1.8.6). - Ce démon dédié ne touche jamais à la configuration de démarrage propre de l'assistant interactif. Une tentative antérieure (1.8.5) utilisait le
KeepAlive.PathStatepropre à l'assistant pour cette récupération, mais cela interférait parfois avec une fermeture normale de l'application et provoquait un redémarrage non désiré ; elle a donc été annulée au profit du démon actuel, totalement indépendant. - La vérification de l'horodatage utilise une combinaison hybride du temps de fonctionnement monotone (
ProcessInfo.systemUptime) et de l'heure murale. Elle privilégie normalement le temps de fonctionnement monotone, de sorte que les corrections NTP et les changements manuels d'horloge n'ont pas d'effet. Elle ne revient à l'heure murale que lorsque le temps de fonctionnement ne peut pas être fiable — juste après un redémarrage, détecté lorsque la valeur actuelle est inférieure à celle stockée. - Il se réapplique après un redémarrage ou une réapparition du démon. Au démarrage, l'assistant lit l'état du disque avec
reapplyFromDisk()et le restaure lui-même, dans l'ordre air-gap, port guard, système par défaut (la règle des 10 minutes s'applique ici aussi). - Un ascenseur échoué est traité comme un échec. Si
block drop alln'a pas pu être supprimé, l'horodatage est réécrit afin que le minuteur de sécurité et la nouvelle tentative aient quelque chose sur lequel converger. L'écran n'affiche jamais faussement "levé". - Vous pouvez reprendre le contrôle à tout moment – avec le bouton d'ascenseur dans le modal ou simplement en désactivant le Wi-Fi.
Comment récupérer un faux positif de port-guard
Le blocage automatique des ports inconnus (Pro, activé par défaut une fois Pro activé) peut arrêter un
Récepteur LAN — LocalSend, Syncthing, tout a démarré après l'activation de la garde. Quand ça
se produit, autorisez-le à partir du bouton "Autoriser" sur la bannière de notification ou de la ligne correspondante dans
l'écran "Ports exposés". Un exécutable autorisé une fois est enregistré comme connu et n'est pas bloqué
encore une fois (PortAnomalyGuard.allowPort(_:)). Notez que les interpréteurs de script génériques
(Python, Node.js, Netcat, etc.) sont strictement limités par path:port plutôt que par binaire
seuls pour empêcher les attaques visant à vivre de la terre. Démons du système Apple qui satisfont
anchor apple (rapportd, qui soutient Handoff, etc.) ne sont pas
regardé en premier lieu.
Appât pour canaris et suppression du confinement par ransomware auto-réparateur
Si les fichiers du leurre Canary sont falsifiés ou renommés et déclenchent l'entrefer d'urgence, libérant le confinement après vérification de la sécurité régénère automatiquement les fichiers Canary manquants ou corrompus revenir à leur hachage de base authentique généré à partir de modèles intégrés, rétablissant immédiatement la surveillance ininterrompue de kqueue (empêchant la contamination de base de l'adversaire). De plus, pendant le confinement actif (lorsque le mode d'urgence est affiché), les alertes de notification redondantes et les événements de redéclenchement issus d'interrogations périodiques d'intégrité en arrière-plan sont automatiquement supprimés pour éviter de distraire l'utilisateur lors de la réponse à un incident.
Détection multicouche des téléchargements (signatures statiques + ClamAV) — couche de signatures statiques ajoutée en 1.8.7
Les fichiers nouvellement placés dans les dossiers Téléchargements, Bureau ou Documents passent d'abord par une vérification légère de signatures statiques via StaticSignatureScanner (seulement les 4 premiers Mo, simple correspondance de motifs d'octets AND/OR — aucune expression régulière ni calcul d'entropie). Elle ne nécessite pas l'entitlement EndpointSecurity et continue de fonctionner même là où ClamAV n'est pas installé. La détection est délibérément limitée à ce qui peut être affirmé comme un fait sans corpus de malware réel : la signature de test EICAR, norme du secteur, et cinq reverse shells classiques documentés dans des références publiques d'offensive security (par exemple PayloadsAllTheThings) — /dev/tcp/ de bash/sh, -e de netcat, pty.spawn de Python, Socket de Perl, et fsockopen de PHP. Nous n'écrivons pas de « signatures » au niveau des octets pour des familles de malwares nommées sans disposer d'un échantillon réel dont les extraire — ce ne serait qu'un faux sentiment de sécurité. Les motifs eux-mêmes sont stockés obfusqués par XOR 0x5A, afin que le binaire de RoamSwitch n'intègre jamais littéralement une chaîne EICAR ou un reverse shell que ClamAV pourrait lui-même signaler.
Seuls les fichiers que la couche de signatures statiques juge « propres » passent ensuite à l'analyse ClamAV. Les fichiers nouvellement placés dans les dossiers Téléchargements, Bureau ou Documents sont analysés immédiatement par ClamAV, qu'ils portent ou non une extension .tmp ou un attribut com.apple.quarantine (par exemple une copie faite via le Terminal). EICAR et les autres signatures de test standard du secteur sont traités comme inoffensifs : aucune notification n'est affichée, seule une entrée est ajoutée à l'historique des notifications — et ils ne sont jamais mis en quarantaine ni bloqués (seuls les échantillons de malware réels sont mis en quarantaine). Et si un fichier du même nom avait déjà été mis en quarantaine auparavant, la nouvelle menace est tout de même déplacée vers le dossier de quarantaine sous un nom unique horodaté, afin qu'une menace ne puisse jamais rester à son emplacement d'origine à cause d'une collision de noms.
Surveillance des nouvelles persistances de démarrage (LaunchAgent/LaunchDaemon) — 1.8.7
Les nouveaux fichiers .plist placés dans ~/Library/LaunchAgents, /Library/LaunchAgents ou /Library/LaunchDaemons sont détectés en temps réel via FSEvents (PersistenceMonitorGuard). Sans ES_EVENT_TYPE_AUTH_CREATE d'EndpointSecurity, l'écriture elle-même ne peut pas être bloquée. Ce qui est possible, c'est de la remarquer en quelques secondes et d'en évaluer le contenu.
L'évaluation porte sur ce qui est directement lancé, et non sur la signature de l'exécutable lui-même. Des techniques réelles observées en 2026 — un LaunchAgent déguisé en Google Update dissimulant un script bash décodé en base64, ou un LaunchDaemon root réexécutant à chaque démarrage une charge utile AppleScript décodée en base64 — cachent toutes deux le code malveillant dans le script lui-même tout en appelant un /bin/bash ou /usr/bin/osascript signé et inchangé. Une vérification ne regardant que la signature de l'exécutable laisserait passer les deux. C'est pourquoi tout LaunchAgent/LaunchDaemon nouvellement enregistré qui invoque directement un interpréteur de script brut (bash, sh, zsh, osascript, python3, perl, ruby, php et similaires) est signalé sans condition, indépendamment de la validité de la signature de cet interpréteur. Les arguments qui lui sont passés sont également soumis à StaticSignatureScanner, et tout motif connu comme malveillant est inclus dans la notification. Les entrées enregistrées via BundleProgram (pointant vers un binaire compilé et signé à l'intérieur d'un bundle d'application — y compris l'assistant propre de RoamSwitch) sont simplement vérifiées de manière habituelle, avec codesign --verify --strict.
Détection de ClickFix et confinement d'urgence — 1.8.7, désactivé par défaut
« ClickFix » est une technique d'ingénierie sociale où un faux CAPTCHA ou écran d'erreur vous demande d'ouvrir le Terminal et d'y coller une commande « pour vérification ». Les détections ont augmenté de plus de 500 % entre 2024 et 2025, et d'ici 2026, cette technique est considérée comme l'un des vecteurs d'attaque macOS les plus répandus. Comme il s'agit de votre propre shell légitime exécutant exactement ce que vous avez saisi, elle échappe entièrement à la vérification de signature de Gatekeeper.
Barre de menu → Protection contre les malwares → Défense ClickFix (Pro, désactivée par défaut). Surveille via FSEvents les nouvelles lignes ajoutées à ~/.zsh_history/~/.bash_history (uniquement les lignes ajoutées après le démarrage de la surveillance — elle ne remonte jamais dans l'historique existant), et ne fait correspondre que les reverse shells classiques qu'elle partage avec StaticSignatureScanner, ainsi que la combinaison spécifique du décodage base64 directement vers un shell ou vers osascript. Un simple curl | bash — extrêmement courant dans les installateurs légitimes — est délibérément exclu, car il est indiscernable des instructions d'installation officielles de Homebrew, rustup, nvm et similaires. Dès qu'une correspondance est trouvée, elle déclenche le même Air-Gap d'urgence décrit plus haut. Comme la commande a déjà été exécutée, il s'agit nécessairement d'une réaction a posteriori, mais elle peut néanmoins interrompre un téléchargement de second stade ou une exfiltration d'identifiants en cours. Comme tout autre déclencheur d'Air-Gap, il se lève de lui-même après 10 minutes au maximum (voir le début de cette section).
Une limite connue. Comme la détection repose sur une écriture dans l'historique du shell, elle ne peut pas détecter la variante sans fichier qui contourne entièrement le Terminal en invoquant directement l'Éditeur de script via le schéma d'URL applescript://, sans rien écrire sur le disque. Apple a mis en place une certaine atténuation pour cela (une boîte de dialogue de confirmation pour les scripts non identifiés), mais des variantes la contournant ont été signalées. Nous avons testé ce chemin sur du matériel réel via le journal unifié de macOS (log show/log stream) et n'avons trouvé aucun signal de journal distinctif sur lequel baser une détection. Nous préférons énoncer clairement cette limite plutôt que revendiquer une couverture que nous n'avons pas.
Détection des risques Docker — 1.8.9, Pro, désactivée par défaut
Barre de menus → « Protection contre les logiciels malveillants » → « Détecter les conteneurs Docker privilégiés et les montages docker.sock » (DockerEventGuard). Comme cette base de code n'a aucun précédent de connexion de streaming persistante telle que docker events, elle utilise la même approche d'interrogation par minuteur que PortAnomalyGuard. Toutes les 20 secondes, elle récupère l'ensemble des identifiants de conteneurs via un léger docker ps -q, puis exécute docker inspect --format uniquement sur la différence (conteneurs nouvellement démarrés) pour une inspection détaillée. La chaîne de format utilisée pour la détection est délibérément identique à celle de l'édition Linux (roamswitch_core::health::LinuxHealthChecker::DOCKER_INSPECT_RISK_FORMAT), de sorte que les deux plateformes signalent exactement les mêmes conditions (démarrage en mode --privileged, ou montage lié /var/run/docker.sock).
Aucune action automatique n'est entreprise lors de la détection. Un conteneur privilégié ou un montage docker.sock est une « configuration » risquée pouvant permettre une évasion de conteneur, mais ce n'est pas une compromission confirmée — il existe aussi des usages légitimes, comme l'exécution délibérée d'un agent de surveillance en mode privilégié. Comme la plupart des utilisateurs n'utilisent pas du tout Docker, cette fonction reste désactivée par défaut même avec une licence Pro. Elle n'utilise pas l'entitlement EndpointSecurity ; la CLI docker est recherchée dans l'emplacement d'installation par défaut de Docker Desktop, puis via Homebrew, puis via which, dans cet ordre. Vérifiée sur une installation réelle de Docker Desktop (29.7.2) selon trois scénarios — un conteneur privilégié, un conteneur avec montage docker.sock et un conteneur normal — sans faux positifs ni détections manquées.
Audit des fuites de secrets et clés API — ajouté en 1.8.4, analyse de dossier ajoutée en 1.8.9
Barre de menus → « Protection contre les logiciels malveillants » → « Audit des fuites de secrets/clés API ». Collez du texte et il est instantanément vérifié à la recherche de clés API et de jetons divulgués, affichant le numéro de ligne, une chaîne masquée et une recommandation par résultat (SecretLeakAuditor, entièrement sur l'appareil). La version 1.8.9 a ajouté une option « Choisir un dossier à analyser », permettant au même moteur de détection d'auditer récursivement un répertoire — comme un dépôt de code source — via auditDirectory(at:). .git, node_modules, target, vendor, dist, build, __pycache__ et venv sont automatiquement exclus, tout comme les fichiers de plus de 2 Mo ou détectés comme binaires. Le traitement s'exécute hors du thread principal pour ne pas bloquer l'interface, et rien n'est jamais envoyé à l'extérieur.
Link Guard (blocage des connexions de phishing) — 1.7.2 et versions ultérieures, renforcé dans la version 1.8.0
Barre de menu → « Protection contre les logiciels malveillants » → « Link Guard » (Pro). Bloque les connexions aux sites de phishing/arnaques sur l'appareil, dans tous les navigateurs et applications. Il existe deux points d'application, utilisés ensemble par ordre de priorité.
- ① Extension du système de filtrage de contenu (
RoamSwitchLinkFilter, 1.8.0+, préférée une fois approuvée). Une extension du systèmeNEFilterDataProvider— pas d'examen Apple (les fournisseurs de filtres de contenu sont en libre-service, pas de file d'attente d'approbation). Il inspecte la résolution réelle du nom du flux TCP sortant après. Le nom de destination provient du nom d'hôte résolu par le système d'exploitation ou, à défaut, du TLS SNI analysé à partir des premiers octets du flux. Ainsi, un navigateur effectuant son propre DoH/DoT et se connectant à une adresse IP nue est toujours bloqué. Il ne réécrit jamais/etc/hostset attribue chaque flux à un processus. QUIC (UDP/443) n'a pas de SNI lisible, donc en modeblock, il est supprimé, forçant le navigateur à revenir à HTTP/2 sur TCP. Nécessite une approbation unique dans les paramètres système. - ② Gouffre
/etc/hosts(repli, tandis que l'extension n'est pas approuvée/refusée). L'assistant privilégié écrit les domaines cibles dans une section gérée délimitée de/etc/hostsen tant que0.0.0.0et vide le cache. Plafonné à 60 000. Une fois l'extension active, cette section est supprimée. - Trois modes. "Off" le désactive. "Avertir uniquement" met en pause la connexion correspondante et demande à l'utilisateur (voir Real
warnci-dessous). "Bloquer automatiquement les sites frauduleux évidents (recommandé)" disparaît immédiatement. La valeur par défaut est bloquée à partir de la version 1.7.2. - Real
warn(1.8.0+, extension uniquement). Sur unwarn, appuyez sur l'extension système contient le flux (à la fois le nom du système d'exploitation et le chemin SNI) avec.pause()et l'application génère une notification « Autoriser / Bloquer ». Dès que l'utilisateur atteintallowlist.txt/extra.txt(l'extension interroge les petits fichiers d'état toutes les 1,5 s), les flux suspendus reprennent (autorisent) ou s'arrêtent (bloquent). Aucune réponse dans les 25 s échoue open (un avertissement est consultatif). La décision est mise en cache par hôte (comptez 5 min/bloc 1 h), de sorte que les autres flux de la page et les visites ultérieures sont instantanés et appliqués. La notificationblockest inchangée : « Bloqué… » avec un bouton « Autoriser une fois (5 min) ». - Seuls les cas clairs sont bloqués. Une liste sur le fil de menaces ou un homographe Unicode de marque - tout le reste (TLD à haut risque, usurpation d'identité de sous-domaine, …) est un avertissement. Le moteur de verdict est partagé avec l'édition Linux et n'envoie d'URL nulle part.
- Récupération d'un mauvais blocage. Autoriser un domaine depuis la notification ou le menu (5 minutes ou permanent). La liste verte est soustraite lorsque la section est régénérée.
- Pro-gated. L'application (
applyMode()) n'a lieu que sur une licence Pro valide. Sans Pro, le mode est stocké mais/etc/hostsn'est jamais touché. L’activation ou l’expiration d’une licence prend effet en milieu de session. - Flux et graines groupées. La liste de blocage provient du flux de menaces signé (§7, vérifié avec une clé dédiée au flux). Il fonctionne sur la graine groupée de l'application (~ 60 000 entrées) avant même la première récupération, et la désactivation de la « mise à jour automatique » signifie aucun trafic sortant.
Verrouillage préventif ARP/NDP de la passerelle — 1.7.5 et versions ultérieures
Barre de menu → "Port & Device Monitor" → "Épingler l'ARP/NDP de la passerelle sur des réseaux non fiables (préventif)" (Pro, désactivé par défaut).
- Comment ça marche. Lors de la connexion à un réseau non fiable, le MAC actuel de la passerelle IPv4, du routeur IPv6 par défaut et des résolveurs DNS en liaison est collecté à partir de
route/scutil --dns/arp -n/ndp -an, et l'assistant épingle chacun comme une entréepermanentavecarp -s/ndp -s(confiance à la première utilisation - le premier MAC observé est fiable). Les réponses ARP/NDP usurpées pour ces adresses IP sont ensuite ignorées, de sorte qu'une attaque de l'homme du milieu ne peut pas être mise en place. - Scope. Seuls ces trois types d'entrée sont épinglés. Les réseaux de confiance (ouverts) ne sont jamais épinglés. À chaque changement de réseau, il se déverrouille une fois et se réépingle. Les résolveurs publics hors liaison (8.8.8.8, …) n'ont pas d'entrée ARP en liaison et sont automatiquement exclus.
arp -sest préventif ; l'entrefer est après coup. Cette broche existe donc une usurpation d'identité ne peut pas réussir ; La détection d'usurpation d'identité ARP (ARPSpoofContainmentManager) et l'entrefer d'urgence existent pour "couper plus vite qu'un humain" une fois que l'on en voit un. Ils fonctionnent de manière indépendante.- Persistance. Le jeu de broches est enregistré dans
/Library/Application Support/RoamSwitch/gateway_arp_lock.jsonafin qu'il puisse être déverrouillé lors d'une reconnexion XPC ou d'un redémarrage d'assistance.
Le tunnel VPN (WireGuard / Tailscale) et son kill-switch — 1.7.6 et versions ultérieures, backend sélectionnable en 1.8.0
Barre de menu → "Moniteur de ports et de périphériques" → "Tunnel VPN" (Pro, désactivé par défaut). Il s'agit de la défense anti-MITM primary — elle ne dépend pas de l'intégrité de L2 (ARP/NDP). Le backend est sélectionnable (sous-menu → "Backend") entre "WireGuard (fichier de configuration)" et "Tailscale (Exit Node)". RoamSwitch n'implémente aucune cryptographie elle-même ; seul le backend choisi est armé.
- (A) Backend WireGuard. Pilote le
wireguard-toolsde Homebrew (pas de droit à l'extension réseau Apple). Importez votre propre.conf; l'assistant l'enregistre à0600. Kill-switch : pfblock drop allplus unpass quickuniquement pourlo, l'interface du tunnel, la prise de contact UDP avec les IP du point de terminaison épinglé, DHCP et ICMP. UnAllowedIPsde tunnel non plein déclenche un avertissement de tunnel divisé. - (B) Backend Tailscale (1.8.0+). Pour les utilisateurs qui exécutent déjà Tailscale. RoamSwitch not exécute
tailscale up/ se connecte / installe - il littailscale status --jsonet exécutetailscale set --exit-node=<node>. La CLI autonome (brew install tailscale) est recommandée — la version de l'App Store (GUI) ne peut pas être pilotée depuis l'extérieur de l'application (bac à sable) ; avec lui, vous choisissez le nœud de sortie dans l'application Tailscale et RoamSwitch affiche uniquement l'état. Un nœud de sortie est requis (achemine tout le trafic à travers le tunnel) ; il est automatiquement désarmé s'il est hors ligne ou inaccessible. - Reconfiguration du réseau à la déconnexion. Le macOS autonome
tailscaledne restaure pas proprement le routage + DNS lorsqu'un nœud de sortie est effacé, donc RoamSwitch fait rebondir le(s) service(s) réseau actif (la même chose que basculer le Wi-Fi à la main ; uniquement les services avec une adresse IPv4, sans danger pour les multi-NIC et les IP statiques). Le trafic diminue pendant environ 5 à 10 s et le niveau de protection n'est pas dégradé pendant cette fenêtre. - Le kill-switch Tailscale est désactivé par défaut (opt-in). Le nœud de sortie tunnelise déjà tout ; le pf
block drop all(autorise uniquement CGNAT/MagicDNS/STUN/DERP/DHCP/ICMP/DNS) est destiné aux utilisateurs avancés et est "résistant aux fuites, pas étanche" (le DNS est autorisé donctailscaledsurvit). - Automatique / en cas de perte de licence. Apparaît sur les réseaux non fiables, en panne sur les réseaux fiables ; levée à l'expiration de la licence Pro.
§6
Lorsque vous vous connectez à un réseau que vous n'avez pas enregistré, le commutateur « niveau de protection » n'utilise pas pf. Il modifie simplement les paramètres standard du système d'exploitation d'une manière qui peut être inversée ultérieurement.
| Fonctionnement | Mise en œuvre | Privilège | Comment il est restauré |
|---|---|---|---|
| Pare-feu + mode furtif activé | socketfilterfw --setblockall activé / --setstealthmode activé | racine (assistant) | off lorsque vous revenez à un réseau sécurisé |
| Arrêtez SSH / SMB / Partage d'écran | launchctl décharger -w | racine (assistant) | Enregistre uniquement ceux qui étaient en cours d'exécution à l'arrêt, et load -w au retour (avec SSH également couplé à /usr/sbin/systemsetup -setremotelogin on pour une restauration macOS moderne garantie) |
| Désactiver AirDrop | les valeurs par défaut écrivent com.apple.sharingd DiscoverableMode | Utilisateur (l'application elle-même) | Enregistre la valeur précédente et la réécrit au retour |
Rien de tout cela n’est un nouveau mécanisme de blocage ajouté par RoamSwitch – il s’agit simplement de basculer les paramètres du système d’exploitation. Si vous supprimez l'application, la seule chose qui s'arrête est la commutation dépendante du réseau ; le dernier système d'exploitation les paramètres appliqués restent tels quels. Rien n'est laissé verrouillé, mais si vous voulez vous tromper Côté sécurité, remettez-le sur « Ouvrir » sur un réseau de confiance avant de le désinstaller.
§7
Ce qui reste sur Mac
| Données | Emplacement | Contenu |
|---|---|---|
| Jeton de licence | Porte-clés com.tetsuharu.RoamSwitch.license | Un jeton signé Ed25519. kSecAttrAccessibleAfterFirstUnlock |
| Paramètres de l'application / garde activée/désactivée | Suite UserDefaults com.tetsuharu.RoamSwitch | Inscriptions sur des réseaux de confiance, politique de protection, listes d'exclusion, etc. |
| état pf | /Bibliothèque/Support d'application/ | L'horodatage de l'intervalle d'air, le JSON des ports gardés, l'état du kill-switch VPN, le fichier temporaire de l'ensemble de règles appliqué |
| Flux de menaces Link Guard | ~/Bibliothèque/Application Support/ | La liste des domaines de phishing/arnaque téléchargés (ou la graine fournie par l'application si elle n'a pas encore été récupérée). La version du flux est dans UserDefaults |
| Section gérée Link Guard | /etc/hôtes | Une section délimitée par # BEGIN RoamSwitch link guard… # END, annulant les domaines bloqués à 0.0.0.0. Supprimé lorsque le mode est "Off" (§5) |
| Goupilles de verrouillage ARP/NDP | /Bibliothèque/Support d'application/ | L'ensemble IP → MAC rendu permanent par le verrouillage préventif (§5). Supprimé une fois déverrouillé |
| Configuration WireGuard | /Bibliothèque/Application Support/0600) | Le .conf importé par l'utilisateur. Le nom d'hôte du point de terminaison est également caché dans UserDefaults (l'application le résout) |
| UUID de secours de l'appareil | Paramètres par défaut de l'utilisateur | Une valeur aléatoire, générée uniquement lorsque IOKit ne renvoie pas d'UUID (§9) |
| Journaux | os.Logger / NSLog | Journalisation unifiée. Rien n'est envoyé à l'extérieur |
Trafic qui quitte la machine (la liste complète)
Il n'existe aucun code nulle part qui collecte et envoie des résultats de diagnostic, des informations sur les ports, des URL ou journaux. Aucun SDK d'analyse ni aucun SDK de rapport de crash ne sont inclus. La seule bibliothèque externe est Sparkle (mises à jour). Ce qui sort sur le réseau, ce sont ces huit, et c'est tout (le sixième uniquement si l'utilisateur configure un VPN, le septième uniquement si l'appairage RoamSwitch Sensor est activé).
| Connexion | Destination | Quand ça arrive | Ce qui est envoyé |
|---|---|---|---|
| Activation/désactivation de licence | lafine.net /api/v1/license/* |
Uniquement lorsque l'utilisateur saisit une clé de licence ou désactive Pro | Clé de licence, hachage de l'appareil, nom d'hôte, version de l'application. Les informations personnelles sont traitées par Stripe lors de l'achat ; l'application ne le gère pas |
| Vérification des mises à jour | lafine.net /updates/appcast.xml |
Sparkle, toutes les 24 heures et au lancement | Une requête HTTP (un UA et une version standard). L'élément téléchargé est vérifié par la signature EdDSA (§10) |
| Flux de menaces Link Guard | lafine.net /updates/v1/{manifest, feed/ |
Lorsque Link Guard (§5) est activé et que la « Mise à jour automatique » est activée, toutes les 24 heures (et au lancement). La désactivation de la "Mise à jour automatique" supprime ce chemin | Un GET seulement. Aucune chaîne de requête, pas de cookies, rien qui identifie la machine. Un fichier statique signé receive-only ; le manifeste et le corps de l'aliment sont tous deux vérifiés avec Ed25519. La clé de signature est dédiée au flux — une clé séparée de la mise à jour de l'application SUPublicEDKey (une fuite se limite donc à "une mauvaise liste de blocage") |
| Données de mise à jour de la vérification CVE des paquets / de la vérification active des vulnérabilités | lafine.net /updates/v1/manifest |
Toutes les 24 heures dès le lancement de l'application (toujours — les données de mise à jour de la vérification CVE des paquets n'ont pas d'interrupteur, car il s'agit de données purement reçues utilisées uniquement pour une comparaison locale d'inventaire ; la carte CVE de la vérification active des vulnérabilités n'est récupérée que si « Vérification active des vulnérabilités (vérification active de l'accessibilité) » est activée) | Un simple GET. Aucune chaîne de requête, aucun cookie, rien qui identifie la machine. Un fichier statique signé, reçu uniquement ; le manifeste et le contenu du flux sont tous deux vérifiés avec Ed25519. Réutilise la même clé et le même manifeste que le flux de menaces de Link Guard, mais les flux effectivement récupérés (cartes CVE connues pour Homebrew/npm/PyPI/crates.io/RubyGems/Packagist/Go/Maven, la carte CVE de la vérification active des vulnérabilités, et la liste des noms de paquets npm populaires utilisée pour la détection de typosquatting) sont des fichiers distincts |
| Mise à jour de la définition du virus ClamAV | Miroirs officiels ClamAV | Uniquement lorsque l'utilisateur a installé ClamAV et utilise la fonction d'analyse. Il lance freshclam |
Une récupération de définition ClamAV standard. Il ne contient aucune information dérivée de RoamSwitch |
| Tunnel VPN (§5, WireGuard / Tailscale) | Le point de terminaison WireGuard configuré par l'utilisateur, ou le plan de contrôle Tailscale (Tailscale, Inc.) | Uniquement lorsque l'utilisateur a configuré le tunnel VPN (Pro, désactivé par défaut) et rejoint un réseau non fiable. Avec WireGuard, le nom d'hôte du point de terminaison est résolu via DNS une fois avant l'ouverture du tunnel. Avec Tailscale, il s'agit du trafic du plan de contrôle géré par le tailscaled déjà installé et connecté par l'utilisateur lui-même — RoamSwitch se contente de lire tailscale status --json et d'exécuter tailscale set --exit-node= | Avec WireGuard : la poignée de main WireGuard (UDP) et le trafic à l'intérieur du tunnel. La destination est le propre serveur VPN de l'utilisateur et le contenu est le propre trafic de l'utilisateur. Avec Tailscale : le trafic entre le propre compte Tailscale de l'utilisateur et le plan de contrôle de Tailscale, Inc. (enregistrement de l'appareil, relais DERP, etc., régi par la politique de confidentialité propre de Tailscale). Dans les deux cas, RoamSwitch n'ajoute aucun identifiant ni aucune donnée de diagnostic |
| Appairage et liaison d'audit RoamSwitch Sensor | RoamSwitch Sensor sur le même réseau local (matériel installé par l'utilisateur lui-même, fonctionnant à une IP fixe) | Uniquement lorsque « Appairage RoamSwitch Sensor » (Pro, désactivé par défaut) est activé et que vous saisissez l'adresse IP et le code d'appairage du Sensor pour vous appairer (un échange ponctuel). Après l'appairage, il se reconnecte à cette même IP fixe uniquement lorsque vous appuyez sur « Demander un audit au Sensor », et lors de la récupération automatique du résultat (à partir de 5 minutes après la demande, puis toutes les 5 minutes, jusqu'à 5 tentatives) | L'appairage envoie le nom d'hôte et la clé publique Ed25519 de ce terminal, et reçoit la clé publique émise par Sensor. Les demandes d'audit et la récupération des résultats sont authentifiées par une signature Ed25519 — seules des demandes signées sont envoyées. Les résultats de diagnostic et les informations de port ne sont reçus que comme réponse du Sensor ; aucun contenu de fichier n'est jamais envoyé. La seule destination est l'adresse IP fixe du Sensor installé par l'utilisateur lui-même — elle n'atteint jamais aucun serveur externe, y compris lafine.net. La confiance mutuelle n'est établie que par l'échange explicite du code d'appairage |
| Page de paiement | Paiement à rayures | Uniquement lorsque l'utilisateur appuie sur le bouton d'achat (il s'ouvre dans le navigateur) | — (une navigation dans un navigateur) |
"Zéro télémétrie" signifie ici qu'il n'y a pas de télémétrie qui collecte et envoie des données d'utilisation ou résultats diagnostiques. Cela ne signifie pas qu’il n’y a aucun trafic réseau. Les huit chemins du le tableau ci-dessus existe. Mais chacun d'eux est soit quelque chose que l'utilisateur initie, soit un signature vérifiée, récupération receive-only et résultats de diagnostic, ports, URL, et le contenu des fichiers sur Mac ne le quitte jamais.
Le flux de menaces Link Guard (ligne 3) ajoute « il extrait les mises à jour » en plus de « il n'envoie rien ». défense. Les deux restent séparés ; le livre blanc Linux §1.1 divise également "Zéro Télémétrie" de "recevoir uniquement des mises à jour". Désactivez la « Mise à jour automatique » et Link Guard s'exécute sur le package données (~ 60 000 domaines de phishing/arnaque) plus détection d'homographes hors ligne, et ce chemin ne se produit pas.
Le tunnel VPN (ligne 6) ne se produit que si l'utilisateur configure son propre serveur WireGuard, ou choisit le backend Tailscale et est déjà connecté à tailscaled avec son propre compte Tailscale ; la destination et le contenu sont sous le contrôle de l'utilisateur. RoamSwitch se contente d'ouvrir le tunnel, de maintenir le kill-switch, ou de lire l'état de Tailscale pour changer de nœud de sortie — il n’ajoute aucun identifiant ni aucune donnée d’utilisation. Sans VPN configuré, ce chemin n'existe pas.
L'appairage RoamSwitch Sensor (ligne 7) est une fonctionnalité réservée à Pro, désactivée par défaut ; ce chemin n'existe pas tant que vous ne l'activez pas. L'appairage lui-même n'est établi que par une action explicite — la saisie du code d'appairage émis par l'opérateur du Sensor, avec l'adresse IP fixe du Sensor. Toute communication ultérieure (demandes d'audit, récupération des résultats) est également adressée à cette même IP fixe appartenant à l'utilisateur et authentifiée par une signature Ed25519. Elle n'atteint jamais aucun serveur externe, y compris lafine.net. Les résultats de diagnostic ou le contenu des fichiers ne sont jamais envoyés, si ce n'est en tant que réponse du Sensor lui-même.
La feuille de « contrôle de sécurité des liens » intégrée à l'application envoie une requête HEAD à l'URL cible pour voir
où atterrit une URL raccourcie (les redirections suivantes vers des adresses privées ou locales sont arrêtées par le
v1.4.5 Atténuation SSRF). Le MCP audit_url_safety, en revanche, est une analyse hors ligne
qui se termine sur place et n'envoie l'URL nulle part (§8).
Cela n’est pas simplement affirmé. Le 2026-08-29, une installation 1.4.7 en cours d'exécution a été auditée avec
tcpdump + attribution par processus (nettop / lsof / a filtré
pktap capture) + LuLu, sur une fenêtre d'environ 2 heures, avec le niveau de sécurité épinglé sur
Verrouillage maximum et vérification de l'appcast forcées.
Résultat : aucun flux sortant attribué à RoamSwitch, RoamSwitchHelper,
ou RoamSwitchMCPServer autre que la vérification appcast à lafine.net ; le MCP
les seules sockets du serveur étaient vers localhost ; les vidages des droits sont vides.
Rédaction complète et script que tout le monde peut exécuter pour le reproduire : audit/RESULTATS-2026-08-29.md
Cette mesure date de la version 1.4.7, avant le flux de menaces Link Guard (1.7.2, ligne 3) et le VPN
tunnel (1.7.6, rangée 5). Audité sur 1.7.2 ou version ultérieure, vous verrez two en réception uniquement
GETs à lafine.net (l'appcast et le flux de menace) ; si un VPN
est configuré, vous verrez également UDP vers le propre serveur WireGuard de l'utilisateur (destination et contenu
sous le contrôle de l'utilisateur). Tout cela disparaît avec « Mise à jour automatique : désactivée » et aucun VPN configuré.
§8
RoamSwitchMCPServer est un outil de ligne de commande autonome fourni sur
RoamSwitch.app/Contents/MacOS/. Un client MCP tel que Claude Desktop ou Claude Code
le lance en tant que sous-processus et lui parle via stdio (JSON-RPC délimité par une nouvelle ligne
2.0). Le SDK officiel ne s'appuierait pas sur le SDK macOS de cette machine, il est donc
implémenté à la main sur le JSONSerialization de la Fondation.
Ce que la conception contraint
- Il est en lecture seule. Il n'existe tout simplement aucune API pour modifier le niveau de sécurité, isoler un port ou éjecter un périphérique. Ce n’est pas quelque chose d’oublié dans la v1 – c’est délibérément laissé de côté. Laisser du code externe (ici, un LLM) réécrire l'état de protection d'un outil de sécurité briserait la confiance de chaque utilisateur.
- Il n'ouvre aucun socket. Il n'enregistre pas de service ni n'écoute. Il lit une ligne depuis stdin, renvoie une ligne sur stdout, puis le client termine le processus.
- Il n'envoie rien. Tous les diagnostics sont terminés à l'intérieur du Mac.
- Il lit les paramètres d'un domaine différent.
UserDefaults(suiteName: "com.tetsuharu.RoamSwitch")lit explicitement le domaine de l'application (son propre domaine d'ID de bundle est vide). Il ne fait que lire ; ça n'écrit pas.
Les outils qu'il expose
| Outil | Ce qu'il retourne | Trafic |
|---|---|---|
| get_security_report | Une vérification en 18 éléments (FileVault / SIP / Gatekeeper / mise à jour automatique / XProtect / pare-feu / cryptage Wi-Fi / ARP / verrouillage ARP de la passerelle / audit de configuration SSH / audit sudo NOPASSWD / ports exposés / configuration de garde, et plus) avec un score et des conseils de remédiation par élément | Local uniquement |
| get_exposed_ports | Une liste des ports TCP d'écoute. Pour tout exposé au-delà de localhost, il référence une base de données de service dangereuse connue et vérifie les CORS/en-têtes avec une sonde HTTP vers 127.0.0.1:port (local, fermé) | Seule la sonde vers 127.0.0.1 |
| get_guard_status | L'état activé/désactivé des protections de réponse automatique Pro (anomalie de port/ARP/USB/Bluetooth/téléchargement Web+Mail/protection contre les menaces DNS), le niveau de protection actuel et l'état du réseau de confiance. | Local uniquement |
| audit_url_sécurité | Un jugement d'une URL pour le phishing / l'homographe (usurpation d'identité Unicode) / l'usurpation de sous-domaine de marque / TLD à haut risque / HTTP en texte brut. Il est synchrone et entièrement hors ligne (analyzeURL ; il ne suit pas les redirections) | Aucun |
| get_app_help | Une recherche en texte intégral dans la base de connaissances fournie (spécifications des fonctionnalités/paramètres/dépannage/explications des messages de notification) | Aucun |
| audit_secrets | Détecte les clés d'API et les clés privées exposées dans un texte, un fichier ou une arborescence (les valeurs trouvées sont masquées en sortie). | Aucun |
| audit_security_logs | Agrège les journaux de sécurité récents (Mac : échecs d'authentification sudo, tentatives de force brute SSH, blocages Gatekeeper, détections XProtect, etc. / Linux : échecs d'authentification sudo, force brute SSH, blocages pare-feu, refus AppArmor, détections ClamAV, etc.), masque automatiquement les informations sensibles telles que les clés API et jetons, et renvoie également les résultats de la détection de nouveaux motifs (par gabarits de logs) et des anomalies de fréquence (pics statistiques). | Local uniquement |
| run_active_vuln_scan | Vérification active de vulnérabilités, non destructive et limitée à 127.0.0.1. Le seul outil qui utilise le réseau : désactivé par défaut, il exige un consentement explicite dans les réglages. | 127.0.0.1 uniquement (désactivé par défaut) |
| run_package_cve_scan | Compare les paquets installés (Mac : Homebrew / Linux : dpkg, dnf, zypper, pacman) à une carte CVE locale. Aucune communication réseau n'a lieu. | Aucun |
| run_package_cve_scan_languages | Compare les fichiers de verrouillage de dépendances npm, PyPI, crates.io, RubyGems, Packagist, Go et Maven à la même carte CVE locale. Aucune communication réseau n'a lieu. | Aucun |
| get_quarantine_status | Renvoie le contenu du coffre de quarantaine : chemin d'origine, nom de la menace détectée, date de mise en quarantaine et taille. | Local uniquement |
| get_canary_status | Renvoie l'état des fichiers leurres anti-rançongiciel (canary) ainsi que les 50 incidents détectés les plus récents. | Local uniquement |
| get_notification_history | Renvoie l'historique des notifications envoyées par RoamSwitch (anomalies d'audit des journaux de sécurité, détections ClickFix, etc.) des 7 derniers jours, les plus récentes en premier. | Local uniquement |
| get_port_anomaly_incidents | Renvoie l'état de référence de la protection contre les ports anormaux, les ports actuellement bloqués automatiquement et les 50 incidents les plus récents. La réponse précise explicitement que les ports actuellement bloqués sont un instantané de l'état présent, sans horodatage, distinct de l'historique des incidents qui, lui, est horodaté. | Local uniquement |
| get_runtime_threat_status | Indique si ce Mac est isolé (Air-Gap) à la suite d'une détection de logiciel malveillant par XProtect, et l'incident déclencheur. À consulter en premier lors d'un Air-Gap actif. | Local uniquement |
Le champ instructions dans la réponse initialize indique également clairement :
"Impossible de modifier le niveau de sécurité, d'isoler les ports ou d'éjecter des périphériques", communiquant la fonctionnalité
limite au LLM côté client. Les ressources MCP (roamswitch://docs/*) sont en lecture seule
Documents markdown également.
En septembre 2026, lors d'un exercice (fire drill) où un LLM local (Qwen 3.8 27B) devait enquêter sur un incident via ce serveur MCP, nous avons découvert un cas où le modèle avait mal interprété les données renvoyées par get_port_anomaly_incidents et intégré à tort un ancien blocage de port, sans rapport avec l'incident, dans le récit d'un incident en cours. La cause : un instantané sans horodatage des ports actuellement bloqués était renvoyé dans la même réponse que l'historique réel et horodaté des incidents, sans que rien ne distingue les deux. Le correctif ajoute des champs de note explicites indiquant lequel des deux champs constitue une chronologie et lequel n'est qu'un instantané. En relançant le même scénario avec le même modèle après correction, celui-ci a cité le texte de la note et a explicitement refusé d'attribuer l'ancien port à l'incident en cours. L'efficacité du correctif a été vérifiée à la fois par un test de non-régression automatisé et par une nouvelle exécution avec le véritable LLM local.
La source de ce serveur et de la logique de détection qu'il utilise (surveillance ARP, analyse des ports,
audit des ports, le bilan de santé en 18 points, analyse de sécurité des URL), est publiée sur
github.com/lafine1211/roamswitch-mcp (MIT, un miroir du code livré, étiqueté à chaque
publication). Vous pouvez vérifier directement dans le code qu'il est en lecture seule, ce qu'il transmet au LLM, et
qu'il n'envoie rien. Il n'inclut pas l'assistant privilégié, le contrôle pf, les gardes qui
agissent, ni la licence — ceux-ci restent dans le dépôt de l'application.
Les tests sont également livrés avec lui : tests unitaires en miroir, tests d'entrée contradictoire et fuzzing de mutation,
géré par swift test et vérifié dans CI. Le fuzzing a révélé un crash non surveillé
(JSONSerialization débordements de pile sur un objet JSON profondément imbriqué) ; c'est réglé avec
une vérification de la profondeur d'imbrication avant l'analyseur et enregistrée dans SECURITY_TESTING.md.
§9
Le jeton
- Il utilise Ed25519 (signatures Curve25519). La clé publique est intégrée dans l'application (
LicenseVerifier.embeddedPublicKeyBase64). La clé privée correspondante existe uniquement dans le backend de la licence (une variable d'environnement Firebase Functions) et ne se trouve pas dans le référentiel. - Les données signées sont JSON canonique. La signature est créée et vérifiée sur les octets exacts produits par le codage
LicensePayload(clé de licence, niveau, hachage de l'appareil, émis à, expiration, nombre de sièges) avec.sortedKeyset.withoutEscapingSlashesdeJSONEncoder. - Il est conçu pour échouer fermé. Si la clé intégrée est manquante ou mal formée, ou si la signature ou le JSON canonique ne peut pas être produit, le résultat n'est pas "vérifié" - il renvoie
invalidSignature.
Liaison de périphérique
device_hash = SHA-256( "RoamSwitch-LifetimeSalt-v1" : lowercase(IOPlatformUUID) )
L'UUID matériel brut n'est pas envoyé au serveur. Dans les rares cas où IOKit ne renvoie pas de
UUID, il revient à un UUID aléatoire conservé dans UserDefaults. Au moment de la vérification, si le
le device_hash du jeton ne correspond pas au hachage actuel de l'appareil, le résultat est
deviceMismatch.
Cela fonctionne hors ligne
Tout ce que validateSavedLicense() fait au démarrage est de lire le jeton depuis le trousseau et
vérifiez-le localement avec la clé publique intégrée. Il ne se connecte pas au réseau. Si le
Le serveur de licences est toujours arrêté, les fonctionnalités Pro continuent de fonctionner sur un Mac déjà activé.
Le serveur est contacté uniquement pour une nouvelle activation et pour une désactivation explicite. Le
l'avis de désactivation au serveur est le meilleur effort - même en cas d'échec, la désactivation locale
se termine toujours.
La valeur par défaut est un achat unique (à vie) ; expires_at est coché uniquement lorsque
is_lifetime est faux. Le nombre de sièges est exprimé par niveau : 2 pour Personal Pro, 5 pour
Équipe.
§10
Signature et légalisation (scripts/release.sh)
- Exécutez
xcodebuild archive(version, signature manuelle, application d'ID de développeur). - Exportez avec
-exportArchivesousmethod: developer-id. - Après
notarytool submit --wait, exécutezstapler staplesur le .app. - Vérifiez avec
spctl -a -t exec -vv. - Recompressez après avoir agrafé pour produire l'artefact de mise à jour Sparkle (le ticket de notarisation est donc inclus et il s'exécute hors ligne sans avertissement Gatekeeper).
- Construisez le DMG, légalisez et agrafez également le DMG, et vérifiez avec
stapler validate.
Mises à jour (Sparkle 2.9.6)
| Clé | Valeur |
|---|---|
| SUFeedURL | https://lafine.net/updates/appcast.xml |
| Clé SUPublicED | CNxzwijMzMCJzliId76Yl88S/9np6t/xg/zQ9YbYzHs= |
| SUEnableAutomaticChecks | vrai |
| SUScheduledCheckInterval | 86400 |
Avant qu'une mise à jour ne soit appliquée, la signature EdDSA répertoriée dans l'appcast est vérifiée
contre le SUPublicEDKey intégré dans l'application. La clé de signature privée existe uniquement dans
l’environnement de construction. L'appcast est diffusé via HTTPS. Les mises à jour Delta sont vérifiées par signature
de la même manière.
Le fait que l’URL appcast.xml soit publique n’est pas une faiblesse en soi. Son contenu
ne sont que les numéros de version, les notes de version, les URL de téléchargement, la taille des fichiers et la signature EdDSA de
chaque construction – rien de secret. L'ancre de confiance n'est pas "l'appcast est-il authentique en transit" mais
vérifier la signature de l'artefact avec la clé publique intégrée à l'application. Un attaquant qui peut
remplacer complètement l'appcast (MITM, détournement DNS, compromission de l'hébergeur Web) ne peut toujours pas pousser
une mise à jour malveillante sans la clé de signature. Gatekeeper (ID de développeur et notarisation) est un
deuxième porte.
Deux risques subsistent. La première est que les mises à jour n'arrivent pas, car l'hôte est en panne ou l'appcast est en panne. (aucune mauvaise installation ne se produit – vous n’êtes tout simplement pas mis à jour). L'autre est une attaque par gel qui retient délibérément une mise à jour de sécurité. Sparkle 2.x rejette les rétrogradations et les replays en vérifiant l'ordre des versions, mais une défense complète contre le gel nécessite un serveur de mise à jour dédié avec expiration. C'est sur notre liste de choses à aborder.
§11
Ce que RoamSwitch est censé gérer
- Sondages et attaques d'un attaquant sur le même réseau local ou d'un appareil IoT compromis. Il répond avec furtivité, audit des ports exposés et isolation de l'extérieur.
- Exposition sur un réseau auquel vous ne faites pas confiance. Il arrête automatiquement le partage des services et AirDrop.
- Attaques de l'homme du milieu (usurpation d'identité ARP/NDP). Depuis la version 1.7.6, ceci est en couches : (1) le tunnel VPN + kill-switch (§5, la défense principale — ne dépend pas de l'intégrité L2), (2) une broche ARP/NDP de passerelle préventive sur les réseaux non fiables (§5), et (3) la détection d'usurpation d'identité avec un espace d'air d'urgence (après coup). All Pro, désactivé par défaut (la détection est activée par défaut).
- Rechercher des serveurs de développement et des bases de données (Redis, MongoDB, Elasticsearch, etc.) exposés sur
0.0.0.0sans authentification et les bloquer de l'extérieur. - Détecter rapidement les activités de chiffrement non autorisées de type ransomware et arrêter tout le trafic (cela ne repose pas sur les signatures).
- BadUSB et protection d'approbation de clavier physique (
USBKeyboardGuard, CGEventTap + IOKit) qui intercepte et supprime les frappes des claviers/câbles USB non approuvés (Rubber Ducky, O.MG Cable, etc.) pour empêcher les attaques automatisées par injection de commandes. - Une invite d'approbation pour le stockage USB inconnu (un lecteur non reconnu est conservé en lecture seule plutôt que éjecté immédiatement) et une analyse ClamAV automatique sur le stockage connecté (facultatif).
- Détecte les clés API sensibles (OpenAI, Anthropic, GitHub, AWS, etc.) dans le presse-papiers, entièrement en local, et empêche un collage accidentel dans un formulaire web ou un chat IA avant qu'il ne se produise.
- Détecte et signale les fichiers de modèles d'IA au format Pickle dangereux (risque d'exécution de code arbitraire) téléchargés depuis Hugging Face ou le web.
- La signature de test EICAR, norme du secteur, et des reverse shells classiques bien documentés à l'intérieur des fichiers téléchargés (
StaticSignatureScanner, à partir de 1.8.7, fonctionne même sans ClamAV installé). - Un LaunchAgent/LaunchDaemon nouvellement enregistré qui invoque directement un interpréteur de script brut (
PersistenceMonitorGuard, à partir de 1.8.7). - Les attaques de type « ClickFix », où un faux écran d'avertissement vous incite à exécuter vous-même une commande dans le Terminal — détectées via des motifs de commandes connus comme malveillants dans l'historique du shell, déclenchant un Air-Gap d'urgence (
ClickFixGuard, à partir de 1.8.7, Pro, désactivé par défaut). - Détecte et notifie l'instant où un conteneur démarre avec une configuration Docker à risque d'évasion de conteneur, comme le mode
--privilegedou un montage lié/var/run/docker.sock(DockerEventGuard, à partir de 1.8.9, Pro, désactivé par défaut, notification uniquement). - Audit des fuites de secrets et clés API qui couvre désormais non seulement le texte collé, mais aussi l'analyse récursive de dossiers entiers (
SecretLeakAuditor, analyse de dossier ajoutée en 1.8.9, entièrement sur l'appareil).
Ce que nous avons décidé de ne pas faire
- Il ne remplace pas l'antivirus. ClamAV et XProtect sont utilisés comme auxiliaires ; RoamSwitch à lui seul n'est pas un détecteur de malware à usage général.
- Nous avons décidé de ne pas demander l'entitlement EndpointSecurity. Les taux d'approbation pour un compte Apple Developer individuel sont faibles, et toute fonctionnalité nécessitant réellement un blocage pré-exécution (par exemple
ES_EVENT_TYPE_AUTH_EXEC) est traitée comme un non-objectif fermé tant que cela reste vrai. Plutôt que de bloquer l'exécution elle-même, nous avons mis en œuvre, sans cet entitlement, ce qui est réalisable en pratique en matière de détection a posteriori : détection par signatures statiques, surveillance du démarrage automatique, et défense ClickFix. - Ce n'est pas une garantie. Il s'agit d'une couche dans une pile de défense en profondeur, et non de quelque chose qui « empêche complètement les ransomwares ». La copie marketing est également examinée sur cette prémisse.
- Il ne peut pas protéger une racine ou un noyau déjà compromis. Si un attaquant possède déjà root, il peut également supprimer les règles pf de l'assistant.
- Il ne restaure pas lui-même l'intégrité L2. La broche préventive ARP/NDP est de confiance lors de la première utilisation : si un attaquant est déjà en place avant votre connexion, il peut épingler un MAC usurpé. Le tunnel VPN (§5) est la réponse lorsque vous ne voulez pas faire cette hypothèse : même avec L2 empoisonné, le contenu est crypté et le kill-switch empêche le texte en clair de fuir. Il ne remplace pas la surveillance DHCP d'entreprise ou l'inspection ARP dynamique.
- Il ne fournit pas de serveur VPN. La fonctionnalité de tunnel utilise une configuration WireGuard fournie par l'utilisateur ; RoamSwitch ne devient pas un fournisseur VPN.
Surface d'attaque ajoutée par l'installation de RoamSwitch
| Surface d'attaque | Comment il est contenu |
|---|---|
Un LaunchDaemon qui s'exécute en tant que root et son service mach (com.tetsuharu.RoamSwitch.Helper) |
La surface d'opération est fixée à HelperProtocol (le tableau §3). Il n’existe pas de canal de commande arbitraire. Les connexions sont autorisées par une exigence de signature de code, en utilisant audit_token. |
Si RoamSwitch.app lui-même est compromis, toutes les opérations de l'assistant sont transférées à l'attaquant |
Hardened Runtime est activé et l'application ne bénéficie d'aucun privilège inutile. Le trafic sortant est limité aux huit chemins ci-dessus (le sixième uniquement si l'utilisateur configure un VPN, le septième uniquement si l'appairage RoamSwitch Sensor est activé). Nous prévoyons de faire examiner cela par un tiers. |
| Les chemins binaires du système générés par l'assistant | Les chemins absolus comme /sbin/pfctl sont spécifiés directement, sans dépendance sur PATH. Les arguments sont également codés en dur (à l'exception des numéros de port, des chaînes DNS, des paires ARP IP/MAC et de l'adresse IP du point de terminaison VPN, qui sont tous validés au format). |
Réécriture de Link Guard /etc/hosts (setLinkGuardSinkhole) |
Les écritures sont confinées à une section gérée délimitée ; les lignes à l’extérieur sont conservées textuellement. Les domaines sont normalisés et validés, les adresses IP et les fichiers indésirables sont supprimés, la liste est plafonnée à 60 000 et le fichier est écrit via un fichier temporaire + remplacement atomique. Il ne résout que les cas clairs (liste de menaces ou homographe de marque), décidés par un moteur de verdict uniquement local. La liste de blocage elle-même provient du flux de menaces vérifié par signature (clé dédiée au flux). |
Récupération du flux de menaces de Link Guard (un GET quotidien en réception uniquement) |
Une récupération de fichier statique sans chaîne de requête ni identifiant. Le manifeste et le corps du flux sont tous deux vérifiés par Ed25519, et une récupération qui échoue à la vérification est rejetée (pas de repli sur les données non signées). « Mise à jour automatique : désactivée » supprime entièrement le chemin. |
Broche ARP/NDP préventive (lockGatewayARP) |
Seules les entrées du cache voisin pour « la passerelle, le routeur IPv6, le DNS en liaison » sont épinglées. Les paires IP/MAC transmises sont validées au format et réconciliées avec l'ensemble de requêtes (elles n'ajoutent jamais d'entrées seules). Il fonctionne uniquement sur des réseaux non fiables ; Pro, désactivé par défaut. La limite de confiance lors de la première utilisation est indiquée au §11 « Ce que nous avons décidé de ne pas faire ». |
Tunnel VPN (wireGuardImport/Up/…, Homebrew wireguard-tools) |
Le .conf est stocké par l'assistant à 0600. L'adresse IP du point de terminaison est résolue par l'application et transmise à l'assistant (l'assistant ne résout jamais un nom d'hôte arbitraire). La destination et le contenu du tunnel sont sous le contrôle de l'utilisateur. La fonctionnalité est désactivée si wireguard-tools n'est pas installé. Pro, désactivé par défaut. Le kill-switch passe par PFRulesetCoordinator (§4). |
| Le serveur MCP transmet l'état du système à un LLM (un adjoint confus) | Il est en lecture seule, sans API d'écriture implémentée. Les vérifications d'URL sont hors ligne. Le domaine des paramètres est référencé en lecture seule. |
| Détournement du chemin de mise à jour | Vérification de la signature EdDSA (SUPublicEDKey), plus un ticket de notarisation groupé. L'application est diffusée via HTTPS. |
Annexe A
Tout ce qui est indiqué dans ce document peut être vérifié par rapport à l'artefact distribué avec les commandes suivantes.
Signature et légalisation
# Signature Developer ID et identifiant d'équipe (Team ID)
codesign -dvvv /Applications/RoamSwitch.app 2>&1 | grep -E 'Authority|TeamIdentifier|flags'
# Si le ticket de notarisation est agrafé (staple)
stapler validate /Applications/RoamSwitch.app
spctl -a -t exec -vvv /Applications/RoamSwitch.app
# Signature du helper embarqué / serveur MCP
codesign -dvvv /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchHelper
codesign -dvvv /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchMCPServer
Droits (pas d'autorisation d'envoi réseau)
codesign -d --entitlements :- /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchHelper
codesign -d --entitlements :- /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchMCPServer
# → Un dictionnaire d'entitlements vide. Aucune clé app-sandbox / network client.
Mesurer le trafic
# Exécuter tcpdump en parallèle pour confirmer qu'aucun trafic ne se produit en usage normal
sudo tcpdump -i any -n 'host not 127.0.0.1' and 'not port 53'
# Aucun trafic autre que l'activation de licence, les vérifications de mise à jour et les mises à jour ClamAV
Pour une vérification plus stricte et automatisée avec attribution par processus, voir
rs-zerotel-audit.sh dans roamswitch-support/audit/
Architecture de défense et tests de pénétration (5 limites de défense)
# Vérifie automatiquement en une fois la frontière d'autorisation XPC, la priorité Air-Gap de pf, la détection d'exposition de port, le mode lecture seule MCP et la surveillance ARP
git clone https://github.com/lafine1211/roamswitch-support
cd roamswitch-support/audit
./rs-defense-audit.sh all
Tests d'intrusion en direct et audit de défense multicouche à partir d'une VM macOS : RESULTS-DEFENSE-2026-08-30
Vérification de la protection contre les menaces DNS (logiciels malveillants Quad9 et blocage C2)
# Interroger le domaine de test officiel de Quad9 pour vérifier le blocage des menaces
nslookup test.dns9.quad9.net
# → Doit renvoyer ** server can't find test.dns9.quad9.net: NXDOMAIN, c'est-à-dire que la résolution de noms est bloquée
Vérification Web & Mail Download Guard (détection en temps réel ClamAV)
# Créer la chaîne de test antivirus standard et inoffensive (EICAR) dans Downloads
echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > ~/Downloads/eicar_test.com
# → La détection FSEvents déclenche une analyse ClamAV immédiate ; aucune notification n'est affichée, et « signature de test EICAR détectée (inoffensive) »
# est enregistrée dans l'historique des notifications. EICAR étant un fichier de test standard de l'industrie et non une menace réelle,
# il n'est ni mis en quarantaine ni bloqué et le fichier reste en place (seul un véritable échantillon de malware est déplacé vers le gestionnaire de quarantaine).
Vérification de la détection par signatures statiques (sans EndpointSecurity)
## Un reverse shell classique tiré de références publiques d'offensive security (inoffensif, jamais exécuté)
echo 'bash -i >& /dev/tcp/127.0.0.1/4444 0>&1' > ~/Downloads/rs_test.sh
## → Même sans ClamAV installé, une notification « Fichier téléchargé dangereux mis en quarantaine » apparaît, et le fichier est
## déplacé immédiatement vers le dossier Quarantine (indépendamment de la base de signatures de ClamAV)
Vérification de la nouvelle surveillance du démarrage automatique (PersistenceMonitorGuard)
# Créer un LaunchAgent inoffensif qui invoque directement un interpréteur de script brut (jamais réellement exécuté)
cat > ~/Library/LaunchAgents/com.example.selfcheck.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.example.selfcheck</string>
<key>ProgramArguments</key><array><string>/bin/bash</string><string>-c</string><string>echo hi</string></array>
</dict></plist>
EOF
# → En quelques secondes, une notification « Nouvel enregistrement de démarrage automatique détecté » devrait apparaître. Retirez-le ensuite :
rm ~/Library/LaunchAgents/com.example.selfcheck.plist
Link Guard (bloquant les connexions de phishing)
# Si la section gérée est présente (2 lignes BEGIN/END en mode Pro + par défaut)
sudo grep -c 'RoamSwitch link guard' /etc/hosts
# Le contenu de la section et le nombre de sinkholes
sudo sed -n '/BEGIN RoamSwitch link guard/,/END RoamSwitch link guard/p' /etc/hosts | head -4
sudo sed -n '/BEGIN RoamSwitch link guard/,/END RoamSwitch link guard/p' /etc/hosts | grep -c '^0\.0\.0\.0'
# Si le blocage est effectif (vérifié avec une entrée de la section ; inoffensif)
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 (résolution de noms bloquée)
# Confirmer également que la section ci-dessus disparaît via Menu → « Protection des liens » → « Désactivé »
Le flux de menaces en réception uniquement (signature vérifiée)
# Flux public et manifeste (n'importe qui peut les récupérer et les vérifier)
curl -s https://lafine.net/updates/v1/manifest # version/generated/threatfeed{...}
curl -sI https://lafine.net/updates/v1/manifest.sig # → text/plain
# Seule une requête GET sans chaîne de requête, cookies ni identifiants est envoyée. Vérifiable avec tcpdump en parallèle.
Verrouillage préventif ARP/NDP (§5, 1.7.5+)
# Se connecter à un réseau non fiable avec le verrouillage préventif activé → vérifier les entrées permanent
arp -an | grep -i permanent # La passerelle et les autres IP sont listées comme (permanent)
ndp -an | grep -i 'P ' # Côté IPv6 (P = permanent)
sudo cat "/Library/Application Support/RoamSwitch/gateway_arp_lock.json" # L'ensemble des correspondances IP→MAC actuellement fixées
# Libérer le verrou via le menu → ce qui précède doit disparaître
Tunnel VPN + kill-switch (§5, 1.7.6+)
# Suppose que wireguard-tools est installé (via Homebrew)
brew list wireguard-tools >/dev/null && echo "wireguard-tools: OK"
# Activer le VPN sur un réseau non fiable → avant l'établissement du tunnel, pf est en état de coupe-circuit
sudo pfctl -sr | grep -E 'block drop all|pass .*(utun|udp)' # block drop all + un ensemble limité de règles pass quick
# Une fois le tunnel établi (wg-quick up déjà exécuté)
wg show # handshake / transfer est actif
route -n get default | grep interface # → utunN (la route par défaut est le tunnel)
# Désactiver le VPN → le coupe-circuit est levé et pf revient à son état antérieur
L'assistant privilégié lui-même
# Le LaunchDaemon enregistré
sudo launchctl print system/com.tetsuharu.RoamSwitch.Helper
# Les règles pf actuellement chargées (état réel de l'Air-Gap / de la garde de port)
sudo pfctl -sr
# Le répertoire d'état du helper
ls -la "/Library/Application Support/RoamSwitch/"
La réponse du serveur MCP (vérification hors ligne)
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"
# Renvoie serverInfo et les définitions des 5 outils. Aucune connexion réseau ne se produit.
Les sources et les tests du serveur MCP
git clone https://github.com/lafine1211/roamswitch-mcp
cd roamswitch-mcp
swift build -c release # La même source que le binaire livré
swift test # Tests unitaires, entrées adverses, stdio et fuzzing par mutation
# Voir SECURITY_TESTING.md pour le contenu des tests et les problèmes trouvés
Identifiant de l'appareil
# La valeur brute liée au jeton (seul un SHA-256 salé est jamais envoyé)
ioreg -d2 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}'