Lafine Systems Conception · Document technique

RoamSwitch pour Linux
Livre blanc sur l'architecture et la sécurité

Un document technique expliquant ce que RoamSwitch pour Linux fait au niveau de sa limite de privilèges. Cela évite le langage marketing ; les réclamations peuvent être vérifiées par rapport au comportement réel du package distribué et à l'auto-vérification en annexe.

Version v1.11.1 Champ d'application RoamSwitch for Linux 1.11.1 (durcissement LPE du noyau / protection runtime eBPF / gel SIGSTOP des processus attaquants / optimisation à faible surcharge de Falco / détection de tunnellisation DNS / capture de preuves forensiques / honeytokens / suivi du pass_age de la vérification des vulnérabilités / cartographie NIST CSF/CIS Controls / réévaluation des diagnostics / audit de la configuration MCP) Prérequis systemd + nftables Publié 2026-10-08

§1

RoamSwitch pour Linux est une application résidente de sécurité réseau et de diagnostic système qui apporte le modèle de défense réseau sans confiance de macOS RoamSwitch à Linux (environnements systemd + nftables).

Cette édition cible machines client (postes de travail, ordinateurs portables, ordinateurs monocarte) et suppose un comportement autonome qui suit la machine lorsqu'elle se déplace entre les réseaux. Une édition serveur/infrastructure sans tête renforcée (RoamSwitch Server Edition — refus par défaut permanent, intégrité FIM du chemin critique, webhooks à distance et application de politiques statiques) est fournie sous la forme d'une version de serveur dédiée.

Il identifie le réseau Wi-Fi/filaire connecté par l'adresse MAC de sa passerelle et commute de manière autonome le profil de pare-feu nftables actif. En plus de cela, il fournit trois couches de défense contre les attaques de l'homme du milieu (MITM) sur les réseaux non fiables : un tunnel VPN + kill-switch (WireGuard ou Tailscale, la défense principale), un verrouillage ARP/NDP préventif et la détection de l'usurpation ARP. S'y ajoutent la détection comportementale des ransomwares (fanotify + entropie de Shannon + canaris), une protection de lien passive qui avertit ou bloque les connexions de phishing (NFQUEUE + SNI), une protection contre les anomalies de port, une atténuation BadUSB, des contre-mesures contre l'élévation de privilèges locaux (LPE) du noyau (le refus des espaces de noms utilisateur non privilégiés sur les réseaux non fiables, en atténuation de Frag Gap CVE-2026-53362, peut être activé en option ; désactivé par défaut), l'intégration du capteur de menaces d'exécution eBPF (Falco / Tetragon) avec gel des processus attaquants en quelques millisecondes (SIGSTOP) et isolement Air-Gap automatique, une évaluation de l'état de sécurité en 27 éléments et un serveur MCP (Model Context Protocol) intégré en standard.

Ce livre blanc décrit l'architecture, le modèle de menace et la conception de chaque mécanisme de défense – et, en particulier, comment le principe « traitement entièrement local ; par défaut, la seule chose qui quitte la machine est une demande de données de détection signées, qui ne contient rien sur vous » est garanti au niveau du code. Les fonctions optionnelles qu'un opérateur doit activer explicitement (comme le transfert d'événements) sont des exceptions à ce comportement par défaut ; elles sont toutes énumérées dans le tableau ci-dessous et au §9.4. Il évite le langage marketing ; les affirmations peuvent être vérifiées par rapport au comportement réel du paquet distribué (binaire) et à l'auto-vérification en annexe. RoamSwitch pour Linux est un logiciel gratuit propriétaire dont le code source n'est pas publié (§11).

Principes de conception
PrincipeDétail
Zéro télémétriePar défaut, le produit n'envoie aucune information sur vous ou sur votre machine. Il n'y a ni télémétrie, ni analyse, ni rapport de plantage, ni statistiques d'utilisation, ni configuration à distance, ni serveur d'activation de licence. Seules peuvent communiquer les fonctions optionnelles qu'un opérateur active explicitement, énumérées à la ligne suivante (« Exceptions au comportement par défaut ») et en 9.4.
Exceptions au comportement par défaut (toutes optionnelles)Les fonctions suivantes ne communiquent avec l'extérieur que si un opérateur les active explicitement. Leurs destinations sont uniquement celles que l'opérateur configure ; il n'y a aucune destination intégrée. (1) Transfert d'événements (forward_enabled, désactivé par défaut) – envoie les événements de détection vers un syslog / fichier JSON Lines / webhook signé indiqué par l'opérateur. (2) Fleet (niveau Business, code côté appareil uniquement) – un appareil non enrôlé n'envoie rien et ne résout même pas le nom d'hôte de Fleet ; le service de gestion n'est ni déployé ni publié. Tout le reste, y compris les vérifications de mise à jour déclenchées par l'utilisateur, figure au 9.4.
Recevoir uniquement les mises à jourLa seule requête sortante que le produit effectue par défaut est un GET quotidien, une fois par jour, d'un manifeste signé à URL fixe (lafine.net/updates/v1). Lorsque ClamAV est installé, chaque exécution lance aussi l'outil de mise à jour propre à ClamAV, freshclam ; ce trafic relève de ClamAV et figure séparément au §9.4. Le manifeste permet de récupérer la liste des sites frauduleux pour la protection de lien, la dernière version de l'application, les cartes CVE du noyau, des paquets de l'OS (Debian / Ubuntu / Arch / famille RHEL / SUSE) et des paquets de langages (npm / PyPI / crates.io / RubyGems / Packagist / Go / Maven), la carte CVE du diagnostic de vulnérabilités par démonstration, ainsi que la liste des noms de paquets npm populaires utilisée pour la détection de typosquatting. Chaque jour, seuls le manifeste et sa signature sont récupérés (deux GET) ; la liste de la protection de lien, la carte CVE du diagnostic par démonstration et la liste npm ne le sont que si le manifeste indique une version plus récente, et les cartes CVE du noyau et des paquets seulement si environ 30 jours se sont écoulés depuis la dernière mise à jour. La carte CVE d'Ubuntu n'est récupérée que par les appareils sous une version d'Ubuntu prise en charge ; un appareil qui ne l'a pas encore la récupère lors de sa prochaine exécution quotidienne, sans attendre les 30 jours. Elle ne comporte ni chaîne de requête ni cookie, le seul en-tête qui nomme le logiciel est un User-Agent: RoamSwitch-Updater fixe, et aucune information vous concernant n'est incluse. updates.enabled = false la désactive entièrement ; le produit fonctionne alors avec les données fournies et, sauf activation d'une fonction optionnelle, n'a aucun contact externe.
Traitement entièrement localChaque évaluation, détection et audit s'exécute jusqu'à son terme sur la machine locale. L'analyse de la sécurité des URL et l'analyse secrète évaluent leurs entrées de manière heuristique sans les transmettre nulle part.
Moin de privilègeLes opérations privilégiées sont confinées à un démon root ; l'interface, la CLI et le serveur MCP les demandent via une IPC par socket de domaine Unix. Par défaut, le démon n'ouvre aucun socket TCP/UDP (les fonctions optionnelles activées par un opérateur font exception, §9.4) ; le garde des liens n'utilise que le canal de verdict NFQUEUE sur AF_NETLINK (noyau ↔ espace utilisateur, aucun paquet émis). Les appels IPC qui modifient l'état ne sont acceptés que de root ou d'un UID disposant d'une session de connexion locale (depuis la 1.10.4 ; les appels en lecture seule sont inchangés).
Échec ferméAir-Gap (isolation d'urgence) survit à un redémarrage du démon et est réaffirmé en quelques secondes si ses règles sont supprimées de l'extérieur.
Licence hors ligneLes licences Pro/Business sont vérifiées uniquement par la vérification de signature Ed25519. Aucun appel à un serveur d'activation n'a lieu.

§2

RoamSwitch pour Linux cible principalement les ordinateurs portables/stations de travail mobiles qui se déplacent entre des réseaux non fiables.

Attaquants visés

  • Un attaquant sur le même LAN — analyse de port sur le Wi-Fi partagé, usurpation d'identité ARP/NDP (y compris les publicités de routeur malveillantes) à la recherche d'un homme du milieu, DHCP malveillant, détection passive après une inondation MAC, accès non autorisé à un serveur de développement ou à une API LLM locale accidentellement liée à 0.0.0.0.
  • Périphériques USB malveillants (BadUSB) — usurpant l'identité d'un clavier et injectant des frappes avant le verrouillage de l'écran ; stockage de masse USB non enregistré pour l'exfiltration de données ou pour transporter des logiciels malveillants.
  • Ransomware / malware sans fichier — entrant via un fichier téléchargé et cryptant en masse les documents de l'utilisateur.
  • C2 basé sur DNS et phishing — malware utilisant la résolution de nom pour atteindre un serveur C2 ou un site de phishing.

Hors de portée

  • Un attaquant qui détient déjà racine ; rootkits au niveau du noyau ou du micrologiciel.
  • Attaques qui suppriment physiquement le disque (le domaine du chiffrement complet du disque ; RoamSwitch diagnostique si le chiffrement est présent mais ne chiffre pas).
  • Contrôle de partage sans fil de proximité de type AirDrop (pas de mécanisme natif du système d'exploitation sous Linux ; non pris en charge).

§3

RoamSwitch pour Linux est implémenté en Rust, et le paquet fournit huit exécutables (roamswitch-daemon, roamswitch-app, roamswitch (CLI), roamswitch-mcp, roamswitch-updater, roamswitch-honeytokens, roamswitch-incident-capture et roamswitch-xdp-gate, expérimental et désactivé par défaut). La logique commune se trouve dans la bibliothèque roamswitch-core, et roamswitchkit est le SDK client. Toutes les communications avec le démon passent par /run/roamswitch/roamswitch.sock (un socket de domaine Unix). Il n'y a pas de socket TCP/UDP d'écoute dans le démon, l'application ou le serveur MCP. La seule chose que l'application bind est un unique socket Unix destiné à empêcher une deuxième instance.

# Espace utilisateur (non privilégié)
roamswitch-app      Icône GTK3 + AppIndicator
roamswitch (CLI)    Frontend shell léger
roamswitch-mcp      stdio JSON-RPC (lancé par les clients IA)
      │
      │  AF_UNIX  /run/roamswitch/roamswitch.sock  (IPC local uniquement)
      ▼
roamswitch-daemon   root / systemd Type=notify + WatchdogSec=30
  · Contrôle des profils nftables (table inet roamswitch)
  · Boucle sentinelle (détection réseau et application des profils)
  · Tunnel VPN + coupe-circuit (WireGuard : wg-quick / roamswitch_vpn, Tailscale : roamswitch_ts)
  · Verrouillage préventif ARP/NDP (fixation de la table neigh) / détection
  · Garde d'anomalie de port (roamswitch_dev_guard)
  · Garde fanotify (ransomware / malware)
  · Garde de lien (NFQUEUE — inet roamswitch_linkguard)
  · Moteur de canari / Vault de quarantaine
  · Contre-mesures LPE du noyau (le refus des userns non privilégiés est optionnel, désactivé par défaut)
  · Intégration runtime eBPF (/var/run/roamswitch/events.sock)
  · Gel SIGSTOP des processus attaquants à la milliseconde & couplage Air-Gap
  · Surveillance USB (evdev EVIOCGRAB — BadUSB) / file d'approbation
  · Vérification de licence Ed25519

roamswitch-updater  Un assistant de minuterie systemd autonome. Le seul crate
                    avec une pile HTTP/TLS (GET quotidien d'un manifeste signé)
roamswitch-honeytokens          Processus auxiliaire lancé par le démon (place et surveille des identifiants leurres)
roamswitch-incident-capture     Processus auxiliaire lancé par le démon (collecte un paquet de preuves lors d'un confinement)
roamswitch-core     Bibliothèque de logique pure (partagée par tous les crates)
roamswitchkit       SDK client MCP
Séparation privilégiée
ComposantPrivilègeRôle
démon roamswitchracine (systemd, Type=notify + WatchdogSec=30)Toutes opérations privilégiées. En tant qu'écrivain unique, contrôle les unités nftables, DNS et systemd.
application roamswitchutilisateur de connexionInterface graphique GTK3 + plateau AppIndicator. Édition et visualisation de la configuration uniquement ; chaque action privilégiée passe par IPC.
commutateur itinérant (CLI)utilisateur de connexionFront-end à coque fine utilisant le même socket IPC.
roamswitch-mcpprocessus généré par le client IAstdio JSON-RPC. Fournit des informations de diagnostic en lecture seule.

Toutes les communications avec le démon passent par /run/roamswitch/roamswitch.sock (le répertoire est en 0755 et le socket en 0666). Le contrôle d'accès repose non pas sur les permissions du fichier, mais sur la vérification de l'appelant : le démon lit l'UID du pair avec SO_PEERCRED, refuse les connexions des comptes système (UID 1 à 999 et nobody) et n'accepte les appels qui modifient l'état que de root ou d'un UID disposant d'une session de connexion locale (§1). La configuration utilisateur est stockée en JSON en texte brut dans ~/.config/roamswitch/config.json. Le démon lit /home/*/.config/roamswitch/config.json avec les privilèges root (il s'exécute avec HOME=/root), en n'acceptant que le fichier qui appartient au compte lui-même, qui n'est pas un lien symbolique et dans lequel les autres utilisateurs ne peuvent pas écrire. La configuration ne quitte jamais la machine ; il n'y a ni enregistrement de compte ni synchronisation cloud.

§4

La boucle sentinelle (cycle de 3 secondes) lit le MAC de la passerelle par défaut à partir de /proc/net/arp et ip neigh et le compare aux MAC enregistrés dans config.json. On utilise le MAC plutôt que le SSID car un SSID est usurpé de manière triviale. La notification sur le bureau affichée lorsque le profil change indique la raison du changement (« un réseau non enregistré » / « votre paramètre pour le réseau enregistré X » / « aucune connexion réseau »).

Profils
ProfilSituationcomportement des nftables
ouvert (de confiance)Un réseau domestique ou d'entreprise enregistrépolicy accept. Circulation locale autorisée.
équilibréNon enregistré mais configuré comme relativement sûrRefus par défaut du trafic entrant. Seuls les connexions établies, lo et ICMP / ICMPv6 sont autorisés ; l'exposition des ports publics est surveillée.
verrouillage (protection contre les absences)Wi-Fi public/réseau non enregistréTous les paquets entrants sont rejetés en mode furtif. Les services de partage sont arrêtés automatiquement et restaurés au retour sur un réseau de confiance (activé par défaut, désactivable à tout moment).

Toutes les règles sont isolées dans une table nftables dédiée nommée inet roamswitch et n'entrent jamais en collision avec une autre configuration de pare-feu (ufw / pare-feu). L'expiration du remplacement manuel (1 heure/2 heures/4 heures/jusqu'à minuit/jusqu'à effacement/jusqu'à la prochaine déconnexion) est appliquée par le démon en tant qu'écrivain unique. L'interface graphique n'écrit jamais d'expiration ; il relit le config.json que le démon a réécrit au tick suivant et le reflète dans l'interface utilisateur (en évitant un conflit).

Air-Gap (isolation d'urgence du réseau)

Un mode d'isolation totale qui définit policy drop (priorité -100) sur les hooks d'entrée et de sortie de la table inet roamswitch, autorisant uniquement lo.

  • Fail fermé — au redémarrage du démon, la détection du fichier marqueur (/run/roamswitch/airgap.active) réaffirme l'isolement.
  • Auto-guérison — si un processus externe supprime les règles avec nft delete table, la boucle sentinelle s'applique à nouveau en quelques secondes.
  • Les déclencheurs à faible confiance se lèvent après une courte minuterie — un Air-Gap manuel simple, sans contexte d'alerte (un bouton de l'interface ou roamswitch airgap), est un déclencheur à faible confiance et est entièrement rétabli après une courte minuterie (airgap_low_confidence_secs, 600 secondes par défaut), afin qu'une fausse manœuvre ou un faux positif ne coupe pas la machine du réseau longtemps. Une anomalie ARP hors lockdown, une anomalie de port et un événement eBPF de niveau Error ou inférieur sont également classés à faible confiance dans la machine à états commune, mais le client n'active jamais l'Air-Gap automatiquement pour eux ; ils se limitent à produire une notification ou une entrée de journal, ou à bloquer le seul port concerné.
  • Les déclencheurs à forte confiance ne rétablissent jamais en silence — altération d'un canari, événement eBPF Emergency/Alert (un événement Critical seulement lorsque le processus en cause n'a pas pu être confiné), usurpation ARP concernant la passerelle sous lockdown et Air-Gap activé en réponse à une alerte ARP (ainsi que, seulement en cas d'activation, une règle de gravité élevée de l'enregistreur d'exécutions ; §5) ne sont pas levés automatiquement, quel que soit le temps écoulé. Après la courte minuterie, chaque cycle revérifie si la cause persiste (PID gelé toujours vivant, canari réécrit, anomalie ARP qui continue, etc.) et ne lève l'isolement, avec notification, que lorsque la disparition de la cause est vérifiée. Une vérification non concluante compte comme « non résolu ». Une rafale d'entropie de ransomware active aussi l'Air-Gap, en plus du gel du processus concerné avec SIGSTOP et d'une notification, et compte de même comme déclencheur à haute confiance.
  • Mode DEGRADED — si un isolement à forte confiance atteint le plafond (airgap_hard_cap_secs, 3600 secondes par défaut ; 0 = pas de plafond) sans levée par l'utilisateur ni disparition vérifiée de la cause, il passe en DEGRADED. Côté client, DEGRADED est aussi strict que FULL – tout sauf le loopback est rejeté, connexions établies comprises ; la différence est qu'il ne se rouvre jamais sur minuterie. La levée se fait par l'utilisateur local (bouton de la GUI ou roamswitch emergency-restore) ou par une revérification qui atteste la disparition de la cause. Air-Gap coupe aussi par défaut la radio Wi-Fi via rfkill (airgapAutoRfkill) ; DEGRADED la laisse coupée sauf si airgap_degraded_lift_rfkill vaut true.
  • Transitions consignées et notifiées — le passage de FULL à DEGRADED, chaque type de levée automatique et une levée par l'utilisateur sont inscrits dans la chronologie des incidents et déclenchent une notification de bureau. L'état d'isolement est stocké dans /run/roamswitch/airgap_isolation.json et survit à un redémarrage du démon (un redémarrage remet à zéro le compteur de vérifications ARP consécutives, ce qui penche vers le maintien de l'isolement – fail-closed).
table inet roamswitch {
    chain input {
        type filter hook input priority -100; policy <accept|drop>;
        iif "lo" accept
        ct state established,related accept
        # open est en policy accept sans règle ; balanced / lockdown sont en policy drop (ICMP / ICMPv6 ne sont autorisés qu'en balanced)
    }
    chain output {
        type filter hook output priority -100; policy <accept|drop>;
        # Pendant l'Air-Gap (isolement), la même table est recréée : policy drop en input et en output, seul lo est autorisé (les connexions établies ne passent pas non plus)
    }
}

Protection de lien passive (détection de connexion par phishing)

Le démon surveille où vont les connexions sortantes et signale – ou, pour le groupe restreint de « clairement phishing », bloque – les connexions vers des hôtes dangereux. Il remplace l'ancien vérificateur coller une URL, qui obligeait l'utilisateur à agir.

  • Interception — une table nftables dédiée inet roamswitch_linkguard connecte le chemin output et met en file d'attente les questions DNS et les nouvelles/premières connexions Web à un travailleur de l'espace utilisateur via NFQUEUE. NFQUEUE est un canal de verdict noyau↔espace utilisateur sur AF_NETLINK ; le démon ne lie aucune socket et ne génère aucun paquet. Les règles de file d'attente portent bypass, donc si le travailleur ne court pas, le trafic passe sans entrave (Air-Gap reste l'arrêt dur séparé).
  • Hostname sources — le nom de la question DNS, le TLS ClientHello SNI (donc un navigateur utilisant DoH est toujours couvert par le nom d'hôte) et l'en-tête HTTP Host en texte brut. Encrypted ClientHello (ECH), autrefois largement déployé, est un angle mort connu.
  • Verdict (roamswitch-core, logique pure) — un hôte est comparé au flux local de phishing/menace et à un ensemble d'heuristiques hors ligne : IDN/homographe prêt à confusion d'une marque protégée, jeton de marque dans un domaine non officiel, hôte IP brut, TLD à haut risque.
Mode (linkGuard.mode)Coup de fil ou homographe de marqueAutres signaux suspects
offnon inspecténon inspecté
warnconnexion maintenue jusqu'à 8 s + une invite d'approbation d'autorisation/blocageni notification ni blocage
block (par défaut)connexion abandonnée + une boîte de dialogue d'autorisation (une fois par hôte)ni notification ni blocage

Seul un accès à un flux ou un hôte d'homographe de marque est bloqué en dur ; tout ce que l'heuristique signale à elle seule reste un verdict de niveau avertissement, sans blocage ni notification. Le mode warn maintient la connexion pendant 8 secondes au maximum à un hôte dangereux pendant qu'il interroge l'utilisateur ; si la mise en attente expire sans réponse, c'est fail-closed (les paquets sont abandonnés et le verdict n'est pas mis en cache). Ne pas répondre à la question « ce site est-il fiable ? » ne doit pas signifier « oui » : la tentative suivante pose à nouveau la question. Le mode block affiche une seule notification et une seule boîte de dialogue d'autorisation par hôte (un navigateur ouvrant de nombreuses connexions vers le même hôte bloqué n'inonde plus les notifications). Dans une boîte de dialogue d'approbation pour une cible dangereuse, le bouton par défaut est « Continuer le blocage » et « Autoriser » est présenté de façon discrète, comme le choix autre que celui par défaut. Choisir « Autoriser » dans la boîte de dialogue d'approbation ajoute définitivement l'hôte bloqué à linkGuard.allowlist, jusqu'à ce qu'il soit retiré dans les paramètres.

Le DNS est inspecté, jamais contrefait : une question DNS figurant sur liste de blocage produit uniquement une notification ; l'application se fait lors de la connexion TCP suivante, donc RoamSwitch n'injecte jamais de réponse DNS synthétique. Le feed est une liste compilée de domaines de phishing/arnaque, livrée dans le paquet et actualisée par le programme de mise à jour quotidien (§11). En l'absence de feed, l'heuristique hors ligne s'exécute toujours. La signature Ed25519 et le SHA-256 du feed sont vérifiés par le programme de mise à jour quotidien avant toute installation, et les données qui ne correspondent pas ne sont pas adoptées. La protection de lien charge le feed installé (ou, à défaut, celui livré dans le paquet) ; si aucun des deux n'est présent, elle fonctionne en « heuristique uniquement », jamais en « tout autoriser ».

Tunnel VPN + kill-switch (la principale défense MITM)

Protection anti-MITM qui ne dépend pas de l'intégrité de la couche 2. Sur un réseau non fiable, un tunnel chiffré permanent vers un point de terminaison approuvé rend l'usurpation d'identité ARP/NDP locale, le DHCP malveillant et le reniflage passif inutiles : l'attaquant ne voit que le texte chiffré. Le verrouillage ARP/NDP et la détection ci-dessous sont secondaires à cette fonctionnalité.

Deux backends parmi lesquels choisir — « WireGuard (fichier de configuration) » ou « Tailscale (nœud de sortie) ». RoamSwitch n'implémente aucune cryptographie propre et n'est pas un fournisseur VPN. Les deux backends sont des dépendances facultatives ; en cas d'absence, l'interface graphique affiche la commande d'installation.

(A) Back-end WireGuard

  • Aucun serveur VPN n'est fourni — utilise une configuration WireGuard fournie par l'utilisateur : un .conf d'un fournisseur tel que Mullvad, IVPN ou Proton VPN, ou le propre serveur WireGuard de l'utilisateur (un VPS ou un serveur domestique). Aucune configuration par défaut n'est livrée.
  • Config — le démon valide le .conf et le stocke dans /etc/wireguard/roamswitch.conf (0600, interface roamswitch). Il ne peut pas vivre ailleurs : le profil AppArmor wg-quick sur Ubuntu 24.04+ / Debian 13 refuse à wg-quick de lire un .conf en dehors de /etc/wireguard/ — même en tant que root. Une configuration de tunnel divisé (AllowedIPs et non 0.0.0.0/0 / ::/0) est signalée comme « tout le trafic n'est pas protégé ».
  • Kill-switch — une table nftables dédiée inet roamswitch_vpn (policy drop, les deux hooks) supprime tout sauf le bouclage, l'interface du tunnel (roamswitch, correspondant à oifname), UDP vers l'IP du point de terminaison épinglé, DHCP et ICMP. Il est installé avant que le tunnel soit activé (le point de terminaison est résolu en adresses IP fixes via getent ahosts en premier), il n'y a donc pas de fenêtre de fuite. Si wg-quick up échoue, le kill-switch reste actif.

(B) Backend Tailscale

  • Pour les utilisateurs qui exécutent déjà Tailscale. RoamSwitch not exécute tailscale up / se connecte / installe tailscaled — il lit tailscale status --json et exécute tailscale set --exit-node=<node> / --exit-node= (clair).
  • Un nœud de sortie est requis — la protection anti-MITM n'est valable qu'avec un nœud de sortie qui achemine tout le trafic. Si aucune n'est configurée, la protection n'est pas armée et l'interface utilisateur avertit « non protégé ». Si le nœud choisi se déconnecte, la protection est automatiquement désarmée et l'utilisateur averti (tout router via un nœud mort entraînerait un trou noir dans la connexion).
  • Kill-switch (inet roamswitch_ts, policy drop) — Le transport de Tailscale ne peut pas être épinglé à une seule adresse IP de point final (relais DERP + homologues itinérants), il est donc plus lâche que celui de WireGuard. Il permet : le bouclage, l'interface tunnel (tailscale0), les gammes CGNAT 100.64.0.0/10 et fd7a:115c:a1e0::/48, STUN (udp/3478), DERP (tcp/443), udp/41641, DHCP, ICMP, MagicDNS (100.100.100.100:53), les connexions établies. Tout le reste (UDP arbitraire, DNS vers un résolveur local, SMB, mDNS, HTTP en texte brut, TCP arbitraire) est supprimé.
  • Kill-switch mises en garde — (1) liste blanche plus large que le backend WireGuard (udp/3478, tcp/443, l'ensemble du CGNAT /10). (2) Un attaquant sur le chemin voit toujours les métadonnées (que Tailscale est utilisé, la région DERP, le timing) et peut les supprimer/retarder – mais ne peut pas lire la charge utile tunnelisée. (3) Lorsque l'UDP direct est bloqué, la confidentialité repose alors sur le DERP TLS à certificat épinglé de Tailscale. (4) Avec MagicDNS désactivé + un résolveur local, le DNS s'interrompt lorsqu'il est armé (avertissement de l'interface utilisateur). (5) Les portails captifs doivent être connectés avant d'être activés.

Commun — armé uniquement lorsque vpn_on_untrusted_enabled est activé et que le profil est lockdown (protection en déplacement). Sur open et balanced, le tunnel n'est pas établi et, avec Tailscale, le nœud de sortie est effacé et le kill-switch supprimé (jamais tailscale down). Désactivé par défaut, sur activation volontaire (opt-in). Le tunnel WireGuard / transport Tailscale va vers le propre point de terminaison VPN / tailnet de l'utilisateur et n'est pas lié à l'infrastructure de lafine (§9).

Verrouillage préventif ARP/NDP

Au moment où un réseau non fiable est rejoint, les adresses MAC de l'infrastructure de liaison qu'un attaquant MITM devrait usurper l'identité sont épinglées dans la table voisine du noyau sous la forme d'entrées PERMANENT (statiques). Une fois épinglé, le noyau ignore les réponses ARP/publicités voisines falsifiées.

  • Qu'est-ce qui est épinglé — (1) la passerelle IPv4 par défaut, (2) le routeur IPv6 par défaut (souvent une adresse fe80:: de lien local), (3) tout résolveur DNS en liaison (un résolveur public est atteint via la passerelle et est ignoré). Les voisins IPv6 sont épinglés avec ip -6 neigh replace ... nud permanent.
  • Les réseaux de confiance ne sont jamais épinglés — donc un redémarrage du routeur domestique ne peut pas mettre l'utilisateur dans un trou noir. Ré-épinglé sur chaque commutateur réseau (TOFU) ; un basculement légitime est détecté lors de la prochaine reconnexion. Les entrées épinglées sont enregistrées dans /var/lib/roamswitch/arp_lock.json.
  • Activé par défaut (politique de l'édition communautaire ; même répartition que la version macOS Pro). Basculer : gateway_arp_lock_enabled.

Détection d'usurpation d'identité ARP (notifier d'abord)

Surveille /proc/net/arp pour détecter une même MAC correspondant à plusieurs IP (MAC en double) et un changement soudain suspect du MAC de la passerelle. En lockdown : lorsque la passerelle est concernée, couper tout le trafic (Air-Gap) + notifier. Sur les réseaux balanced / de confiance : notifier sans couper ; l'utilisateur déclenche l'Air-Gap d'urgence depuis RoamSwitch. Cela (a) empêche un attaquant de transformer un seul paquet ARP usurpé en une panne auto-infligée par un DoS, et (b) empêche le redémarrage d'un routeur ou l'itinérance d'un point d'accès de le déclencher par erreur. Paramètre : arp_spoof_guard_enabled.

Vérification de la cause avant la levée d'un Air-Gap (revérification ARP)

Un Air-Gap causé par une usurpation ARP (détectée sous lockdown ou activé en réponse à une alerte ARP) n'est levé comme « cause disparue » que si tout ce qui suit est vrai. Le démon lit la table de voisinage du noyau (ip -j neigh show, à défaut /proc/net/arp) et la route par défaut ; la MAC de la passerelle par défaut doit égaler la MAC de confiance enregistrée à l'activation (état REACHABLE, STALE, DELAY, PROBE ou PERMANENT, sans autre hôte IPv4 du même périphérique revendiquant cette MAC), pendant arp_recheck_consecutive vérifications consécutives (3 par défaut) couvrant au moins arp_recheck_min_secs secondes (60 par défaut). Seulement si l'entrée de voisinage est absente ou en échec et que le lien est actif (carrier présent, operstate up, Wi-Fi non bloqué par rfkill), il envoie un unique ARP who-has à la passerelle (à au moins 20 secondes d'intervalle, via AF_PACKET) pour vérifier (arp_recheck_active_probe, true par défaut). Si une réponse porte une MAC autre que celle de confiance, c'est une discordance. Discordance, entrée absente, MAC en double, échec de lecture ou absence de MAC de confiance enregistrée comptent toujours comme « non résolu » (fail-closed) et remettent le compteur à zéro. Limite connue : un attaquant qui clone la MAC de la vraie passerelle ne peut être détecté par aucune comparaison de MAC, en cache ou active.

La détection d'une usurpation ARP ne déclenche l'Air-Gap automatique que si la passerelle est concernée : la MAC de la passerelle a changé, ou l'IP de la passerelle partage une MAC avec une autre IP. Lorsque la MAC d'un autre hôte change, ou qu'une machine répond pour plusieurs IP avec une seule MAC (un PC qui exécute des machines virtuelles, un NAS avec une adresse alias), on se contente de notifier, sans couper le réseau. La même règle s'applique à la vérification de levée : seul un doublon qui concerne la passerelle compte comme « la cause persiste ». Lors de la correction de la version 1.10.7 (2026-09-26), sur un vrai réseau local, un faux ARP de passerelle envoyé depuis une autre machine a déclenché l'Air-Gap en environ 1 seconde, et il a été levé automatiquement environ 11 minutes après l'arrêt de l'attaque (10 minutes par défaut plus 60 secondes de confirmation). Il a pu être levé même lorsque deux IP sans rapport continuaient de répondre avec une même MAC (avant cette règle, une coupure totale se déclenchait et n'était jamais levée).

Garde d'anomalie de port

Contient automatiquement un port d'écoute nouvellement exposé au réseau local externe (une porte dérobée, C2 ou un serveur de développement/API LLM locale lié accidentellement à 0.0.0.0). Le verdict est baseline-relative, et non « numéro de port dangereux ». Lors de l'activation, les exécutables actuellement exposés sont appris comme « connus » ; par la suite, seul un exécutable exposé nouvellement apparu est signalé (identité = le chemin de l'exécutable, snaps normalisés en snap:<name>). Les démons système sont hors de portée. Le confinement s'effectue via la table inet roamswitch_dev_guard : déposez TCP sur ce port à partir de tout sauf de lo – la machine elle-même continue de l'utiliser. Lors du confinement automatique, une boîte de dialogue immédiate de notification et de confirmation sur le bureau (« Autoriser ce port » / « Garder bloqué ») s'affiche, permettant une mise sur liste blanche permanente en un clic. N'importe quel port peut également être autorisé individuellement et la ligne de base récupérée à partir de l'écran de diagnostic des ports. Activé par défaut. Basculer : port_anomaly_guard_enabled. Porté depuis macOS.

Détection de tunnellisation DNS / d'exfiltration

Fonctionne en permanence dans le cadre du chemin DNS existant de la protection de lien passive (il n'existe pas de commutateur dédié). Une requête correspondant à l'une des formes caractéristiques des outils de tunnellisation DNS tels qu'iodine ou dnscat2 (étiquette de plus de 40 octets ET entropie de Shannon supérieure à 3,8, ou type d'enregistrement TXT/NULL) est comptée comme « de forme suspecte », et une notification n'est envoyée que lorsque 10 requêtes de ce type sont atteintes en 60 secondes (avec un délai de 300 secondes avant une nouvelle notification pour le même apex domain, qui passe à 600 puis 1200 secondes, jusqu'à 1800 secondes au plus, tant que les notifications continuent ; il revient à 300 secondes s'il n'y a pas eu de notification pendant au moins deux fois le délai). Seul le DNS (port 53 UDP/TCP) est inspecté ; aucun autre trafic n'est examiné. La fonction se contente de notifier et ne bloque jamais automatiquement. Seules les requêtes TXT vers clamav.net ne sont pas comptées, car freshclam les utilise pour vérifier la fraîcheur des signatures. La règle NFQUEUE sous-jacente porte le drapeau bypass : si le processus de détection est arrêté ou ne répond pas, le trafic DNS continue de passer sans être affecté. La résolution de noms n'est jamais bloquée par une défaillance de cette fonction (même conception fail-open que la protection de lien passive elle-même).

Limite connue — il a été constaté par mesure que les noms d'hôte longs séparés par des tirets (par exemple prod-us-east-1-web-server-node-0042) ainsi qu'un compteur global unique ne distinguant pas les domaines de destination pour les rafales TXT/NULL provoquaient ici des faux positifs. Les deux ont été corrigés le 23/09/2026. La correction consiste en une liste blanche d'entropie configurable et en une séparation des clés du compteur de rafales par domaine apex (une approximation simple basée sur les deux derniers labels du QNAME, et non une implémentation stricte de la Public Suffix List). La liste blanche intègre par défaut 16 suffixes de CDN/cloud et de PaaS courants (cloudfront.net, akamaiedge.net, akamaized.net, edgekey.net, edgesuite.net, fastly.net, azureedge.net, googleusercontent.com, amazonaws.com, r2.dev, workers.dev, vercel.app, netlify.app, supabase.co, firebaseapp.com, web.app), et les administrateurs peuvent y ajouter les domaines internes de leur organisation via LinkGuardCfg.dns_tunnel_entropy_allowlist (les paramètres de la protection de lien de roamswitch-app comportent un champ dédié, à remplir avec des valeurs séparées par des virgules). La liste blanche ne dispense que du contrôle combiné longueur d'étiquette + entropie ; la détection par type d'enregistrement TXT/NULL s'applique toujours, avec ou sans liste blanche, ce qui empêche un attaquant de dissimuler un tunnel sous un domaine autorisé (seule exception : les requêtes TXT sous clamav.net, que freshclam utilise pour vérifier la fraîcheur des signatures, ne sont pas comptées, grâce à une petite liste d'exclusion distincte). Le compteur de rafales a ses clés séparées par domaine apex, ce qui élimine les faux positifs dus à un compteur global unique ignorant le domaine de destination (la logique sous-jacente de fenêtre glissante de 60 secondes n'a pas changé ; le délai avant une nouvelle notification commence à 300 secondes et peut aller jusqu'à 1800 secondes tant que les notifications continuent). Le trafic malveillant réel d'outils comme iodine ou dnscat2 fait simplement tourner ses étiquettes et types d'enregistrement sous un seul domaine contrôlé par l'attaquant, si bien que restreindre le comptage par domaine apex ne réduit pas l'efficacité de la détection.

Détection des risques Docker & protection contre le contournement du pare-feu

Les ports publiés par Docker (-p) sont connus pour contourner le pare-feu standard de l'hôte ; des règles d'inspection sont donc insérées en permanence dans la chaîne DOCKER-USER, bloquant tout trafic externe non explicitement autorisé (aucune configuration requise, actif par défaut). De plus, docker events est surveillé en temps réel afin de détecter instantanément le démarrage d'un conteneur --privileged ou un montage /var/run/docker.sock — un risque d'évasion de conteneur — et de vous avertir immédiatement. Comme cela signale une configuration à risque et non une compromission confirmée, il s'agit uniquement d'une notification, sans blocage automatique.

Couverture — cette inspection de la chaîne DOCKER-USER couvre le backend iptables (IPv4), IPv6 et le backend nftables natif de Docker, et se vérifie et se répare toutes les 60 secondes (y compris dans l'édition bureau, depuis la 1.10.4 ; les versions antérieures ne couvraient que le backend iptables (IPv4)).

Scanner de fuites de secrets et de clés API

Détecte les clés API (OpenAI, Anthropic, AWS, GitHub, Slack, Stripe, etc.) et les clés privées SSH grâce à l'entropie de Shannon combinée à une correspondance de motifs. roamswitch audit-secrets <text|fichier|répertoire> analyse du texte et des fichiers, ainsi qu'un répertoire entier de façon récursive (en ignorant .git, node_modules, etc. ; les fichiers de plus de 2 Mo ou ressemblant à du binaire sont ignorés). La même détection est également disponible via l'outil MCP audit_secrets (en passant text ou path) et via le client Rust roamswitch-linux-kit du SDK public (audit_secrets() pour du texte, audit_secrets_path() pour un fichier ou un répertoire). Le contenu n'est jamais transmis nulle part.

Détection et blocage de la force brute SSH (seulement tant que sshd tourne)

Le démon de l'édition Client fait tourner le même moteur que la Server Edition (ssh_guard_enabled, true par défaut). Il y a trois différences. D'abord, il ne travaille que tant que sshd tourne : chaque minute, il vérifie si quelque chose écoute sur les ports SSH et s'arrête après trois absences de suite. Sur une machine sans sshd, il ne suit pas le journal et ne touche à aucune table nftables. Ensuite, les réglages sont les mêmes 10 clés (ssh_guard_*) avec les mêmes valeurs par défaut, conservées dans config.json et prises en compte en environ une minute. Enfin, les notifications sont des notifications de bureau et une entrée dans le journal d'incidents du client ; il ne déclenche jamais d'Air-Gap. Une source dont les échecs atteignent le seuil (par défaut 6 en 600 secondes) perd ses nouvelles connexions, et les connexions SSH qu'elle avait déjà ouvertes sont coupées en même temps (une session authentifiée et une source exemptée ne sont jamais coupées). Lorsque fail2ban (avec une jail sshd activée) ou CrowdSec protège déjà sshd, il se contente de détecter et de notifier. Les bannissements se listent et se lèvent avec sudo roamswitch bans et sudo roamswitch unban <IP> ; il n'y a pas d'écran GTK pour cela, seulement config.json et les commandes. Aux niveaux de protection par défaut du client, le trafic entrant est fermé, de sorte que les connexions peuvent ne pas atteindre SSH du tout.

Assistant de configuration de première exécution et interface utilisateur de confirmation

Le package est livré /etc/xdg/autostart/roamswitch.desktop, donc l'assistant d'installation se lance lors de la première connexion après l'installation (il réside ensuite uniquement dans la barre d'état). L'activation/la désactivation d'un agent de sécurité affiche une boîte de dialogue de confirmation pour les mêmes gardes que la version macOS (anomalie de port, confinement automatique ARP, stockage USB, clavier BadUSB, Bluetooth). Les décisions au moment de l'événement (un clavier USB inconnu, une clé USB non enregistrée, un port inconnu bloqué automatiquement, un Air-Gap d'urgence) sont présentées sous forme de dialogues modaux.

§5

Détection de rafales d'entropie de Shannon · Balayage YARA

Les événements d'écriture sont suivis par PID. Si un seul processus crée 20 fichiers distincts ou plus avec une entropie ≥ 7,92 en moins de 5 secondes, il est traité comme un chiffrement de masse par ransomware : le processus est gelé avec SIGSTOP. Une notification sur le bureau, un enregistrement dans la chronologie de l'incident et la collecte d'un paquet de preuves suivent. Cette détection déclenche aussi l'Air-Gap (la coupure de l'ensemble du réseau), comme déclencheur à haute confiance au même titre que l'altération d'un canari ou les événements eBPF de gravité élevée.

Mesuré sur une vraie machine (Ubuntu 24.04, noyau 7.0) avec un programme de reproduction qui réécrit 40 fichiers (mesure du 2026-09-26, contre le démon de la version 1.10.7, qui inclut la détection du chiffrement partiel). Un chiffrement complet a été détecté après le 20e fichier et le processus gelé au 31e. Il est toujours détecté lorsque le processus se renomme en python3 ou git : la liste d'autorisation vérifie désormais que l'exécutable et le nom du script concordent avec le nom, et non plus le nom seul (auparavant, les 40 fichiers passaient inaperçus). cp -r, rsync -a, tar -x et les scripts Python sont identifiés au moment où l'écriture est vue ; ils ne déclenchent donc plus de fausse détection même s'ils se terminent vite (auparavant, ils avaient déjà quitté à l'évaluation de la rafale et étaient signalés comme « processus inconnu »). Le chiffrement partiel (16 octets sur chaque 32, à la manière des ransomwares de type LockBit) restait sous la moyenne d'entropie de 7,92 du fichier entier et n'était donc pas détecté ; il l'est depuis la version 1.10.7. La règle recherche des blocs de 16 octets qui alternent entre un aspect aléatoire et un aspect de texte ; elle a détecté 94 % des fichiers simulés et produit 2 faux positifs (0,01 %) sur 19 997 fichiers ordinaires. Un programme de reproduction qui écrit à pleine vitesse, sans pause entre les fichiers, termine l'écriture des 40 fichiers avant que le seuil de 20 fichiers soit évalué : le processus avait donc déjà quitté lorsqu'il a été arrêté ; avec 0,2 seconde par fichier, il a été arrêté au 20e. Le chiffrement de l'en-tête seul, le chiffrement avec zones sautées et les données chiffrées écrites en Base64 ne sont pas comptés comme des détections : ils sont seulement consignés dans le journal et rien n'est gelé, car cela risquerait de produire de faux positifs sur des fichiers ordinaires. Un rançongiciel écrit avec un vrai programme de la liste d'autorisation (comme python3) ne se distingue pas non plus par son nom.

  • Séparation de l'analyse YARA et du suivi de l'entropie — L'inspection des logiciels malveillants YARA est appliquée aux fichiers écrits sans aucune exclusion par extension, y compris ~/.cache/, node_modules/ et les archives (à l'exception du paramètre utilisateur scan_exclusions, du Vault de quarantaine, de /dev/shm et des fichiers en mémoire ou supprimés, d'emplacements tels que /proc, /sys et /dev, et de ce qu'écrivent des processus système comme le démon lui-même, ClamAV et la base du bureau ; seuls les 256 premiers Ko d'un fichier sont lus). En revanche, l'analyse des rafales d'entropie des ransomwares exclut les formats naturellement compressés (.zip, .tgz, .tar.gz, .gz, .png, .jpg, .mp4, .wasm, etc.), les fichiers dont le début est resté celui d'un format légitime de compression ou de média (depuis la version 1.10.25) et le travail de cache des gestionnaires de paquets légitimes (npm, npx, cargo, rustc, dpkg, apt, tar, etc.), afin d'éviter les gels SIGSTOP dus à des faux positifs lors de l'extraction par des outils légitimes.
  • Les écrasements répétés du même fichier (par exemple les trois passes de shred) comptent comme un événement.
  • Protection des processus critiques — Les processus sont comparés à une liste de processus protégés codée en dur (PROTECTED_PROCESS_NAMES : systemd / dockerd / NetworkManager / sshd / gnome-shell, etc.) ; un processus dont le nom correspond et dont l'exécutable se trouve dans une zone système (/usr, /opt, etc.) n'est jamais gelé (ce qui évite un blocage de l'ensemble de la machine). Un programme situé dans /tmp ou /home qui ne fait qu'imiter le nom n'est pas protégé.

Règles de la couche applicative Web (web shells et code injecté)

Des règles dédiées détectent les web shells PHP, JavaScript, JSP et ASP, le code obfusqué injecté, le PHP caché dans des images et le détournement de l'exécution via .htaccess. Elles sont distinctes de la détection intégrée des ELF et des shells et s'écrivent dans un petit langage de littéraux, de suites de littéraux à écart borné et de deux statistiques (une ligne extrêmement longue, un énorme bloc Base64). Sans expressions régulières, l'évaluation ne peut ni exploser ni se bloquer ; même un fichier de règles hostile ne peut provoquer que des faux positifs. Aucune signature n'est tirée d'un échantillon réel ni liée à une famille de malwares nommée : les 18 règles décrivent uniquement des techniques publiquement documentées (exécution de la sortie d'un décodeur, valeurs de requête passées directement à l'exécution, PHP exécutable dans les répertoires de téléversement, PHP dans une image, associations de gestionnaires .htaccess, scripts injectés construits avec unescape, bibliothèques de minage dans le navigateur).

Les règles se répartissent en trois couches : intégrées, installées par la mise à jour quotidienne (11.3) après vérification de la signature, et vos propres fichiers dans /etc/roamswitch/rules.d/*.json (seuls sont lus les fichiers appartenant à root et non modifiables par le groupe ou les autres). Les niveaux de gravité sont critical, high, medium et low ; critical et high passent par l'approbation ou la quarantaine, medium et en dessous sont seulement signalés. La quarantaine automatique se limite à critical par défaut (quarantine dans /etc/roamswitch/web_rules.conf) ; dans l'édition Client, une détection à l'écriture ou à l'exécution reste décidée par l'utilisateur dans la boîte d'approbation. Tout ce qui se trouve sous tests/, fixtures/ et .git/ est ignoré par défaut, car le code de test cite les mêmes constructions.

Après une détection, un exécutable placé dans /etc/roamswitch/hooks/post-detection reçoit les résultats en JSON sur l'entrée standard (seul un fichier appartenant à root, non modifiable et placé dans un répertoire sûr est exécuté ; pas de shell, environnement minimal, arrêt après 30 secondes). La réponse optionnelle sur les comptes (désactivée par défaut) révoque le shell de connexion du propriétaire d'un fichier mis en quarantaine. Elle ne fait rien pour root, les comptes dont l'UID est inférieur à account_min_uid (1000 par défaut), les utilisateurs connectés et l'utilisateur du démon ; l'ancien shell est enregistré et roamswitch web-rules unlock-account le rétablit. Faux positifs mesurés : 0 sur le cœur de WordPress (2 409 fichiers), phpMyAdmin (3 356 fichiers) et des dépendances npm (14 743 fichiers). Limites : seuls les 256 premiers Ko (à l'écriture/exécution) et 4 Mo (analyse) sont lus, les noms fragmentés comme 'ev'.'al' ne sont pas suivis, et aucune signature de malware connu n'est fournie faute d'échantillons réels.

Canaris (fichiers leurres)

Les fichiers leurres sont placés dans les dossiers Documents, Bureau, Téléchargements et Images de l'utilisateur et dans un répertoire dédié, et comparés à une référence SHA-256 sur un cycle de 3 secondes. Tout changement de nom, suppression, troncature ou changement de contenu déclenche immédiatement Air-Gap. Étant donné que le démon s'exécute en tant que root, il énumère /home/* et déploie des canaris dans les répertoires de chaque utilisateur réel.

Paquets de preuves forensiques

Seulement lorsque la détection de rafale d'entropie liée aux rançongiciels se déclenche, ou qu'un événement de sévérité Critical du capteur de menaces eBPF en temps réel aboutit à un confinement ciblé réussi du processus (SIGSTOP + isolation réseau), roamswitch-incident-capture est lancé et enregistre un paquet de preuves dans <répertoire d'état>/incident-evidence/<horodatage>-<identifiant court>/. Ce qui est collecté : une capture de paquets bornée à 30 secondes via tshark sur l'interface de routage par défaut (consignée comme ignorée si tshark n'est pas installé) et, lorsqu'un PID est transmis, un instantané /proc/<pid> du processus visé (status, cmdline, maps, un environ dont les valeurs ressemblant à des secrets sont remplacées par [REDACTED], une liste des cibles des descripteurs de fichiers ouverts — leur contenu n'est pas collecté — et la chaîne des processus ancêtres). L'échec de collecte d'un artefact isolé n'est pas traité comme fatal ; il est consigné, et le reste de la collecte se poursuit. Pour les événements eBPF, le PID du processus visé est transmis. Pour la détection de rafale d'entropie, le PID est transmis lorsque le processus qui a écrit a pu être identifié ; si ce processus avait déjà quitté au moment où la rafale a été évaluée, l'outil est lancé sans PID et ne collecte que la capture de paquets. La collecte de preuves ne s'exécute pas lorsque le confinement ciblé est impossible ou échoue et bascule vers un isolement Air-Gap à l'échelle de l'hôte, ni pour les événements de niveau Emergency/Alert qui déclenchent directement un isolement à l'échelle de l'hôte. Les paquets distribués jusqu'à la 1.10.4 ne contenaient pas roamswitch-incident-capture, nécessaire à cette fonction, qui ne s'exécutait donc pas sur les installations depuis les paquets. Il est inclus depuis la 1.10.5.

Les résultats sont consignés dans manifest.json, qui intègre également l'empreinte SHA-256 de chacun des autres fichiers du paquet (afin de pouvoir détecter une altération ultérieure). Les paquets ne sont jamais supprimés automatiquement — les anciens paquets ne sont retirés que lorsque roamswitch-incident-capture prune (rétention par défaut : 30 jours) est exécuté explicitement.

Les preuves collectées ne sont pas vérifiées au moment de la capture. Pour détecter une altération après coup, exécutez roamswitch-incident-capture verify <répertoire du paquet> [--json]. La commande recalcule le hachage de chaque fichier actuellement présent dans le paquet et le compare aux valeurs enregistrées dans manifest.json, en signalant individuellement chaque écart comme modifié, manquant ou non répertorié (un fichier présent que le manifeste n'a jamais enregistré).

Limite connue — cette vérification compare le SHA-256 de manifest.json à des enregistrements conservés hors du lot, à deux endroits : une chaîne de hachages hors du lot (evidence_anchor.log, chaque ligne s'appuie sur la précédente, en ajout seul lorsque le système de fichiers le permet) et le journal système (étiquette roamswitch-evidence). Réécrire manifest.json après la capture fait échouer verify ; modifier ou supprimer une ligne de la chaîne aussi, car la chaîne se rompt. Cela n'arrête pas un attaquant root capable de réécrire à la fois la chaîne et la destination de transfert du journal système ; il est recommandé de transférer le journal système vers une autre machine. La signature n'est pas implémentée. Un lot sans enregistrement (capturé par une version plus ancienne) est signalé « aucun enregistrement » : il n'a pas de confirmation extérieure.

Honeytokens (leurres d'identifiants)

Au démarrage du démon, RoamSwitch déploie de façon idempotente des fichiers d'identifiants leurres à des emplacements réels connus, réellement explorés par les attaquants et les outils automatisés de vol d'identifiants — ~/.aws/credentials, ~/.ssh/id_rsa et ~/.docker/config.json, ~/.pypirc, ~/.env.backup (sous le répertoire personnel de l'utilisateur appelant) —, chacun contenant un marqueur ROAMSWITCH_HONEYTOKEN. Un fichier réel déjà présent à l'un de ces emplacements n'est jamais écrasé (le déploiement vérifie toujours d'abord son existence). Un accès réel à un honeytoken peut également déclencher le déploiement en cascade de leurres supplémentaires associés. Les paquets jusqu'à la 1.10.4 ne contenaient pas roamswitch-honeytokens, nécessaire à cette fonction, qui ne s'exécutait donc pas sur les installations depuis les paquets. Il est inclus depuis la 1.10.5. Depuis la 1.11.1, chaque commande docker que RoamSwitch exécute lui-même est lancée avec un répertoire privé vide comme DOCKER_CONFIG ; elle ne déclenche donc pas le leurre ~/.docker/config.json. Une lecture par tout autre programme est toujours détectée comme avant.

Un observateur en arrière-plan utilise fanotify de classe notification (surveillance par fichier de FAN_OPEN/FAN_ACCESS sur chaque leurre) pour surveiller la lecture et l'ouverture de chaque leurre ; lorsque fanotify n'est pas disponible (par exemple en cas d'exécution sans root), il bascule sur inotify. Aucun processus légitime n'ayant de raison de lire ces fichiers, tout accès est considéré comme un signal fort de reconnaissance post-compromission par un intrus (MITRE ATT&CK T1552) ; RoamSwitch tente d'identifier le processus à l'origine de l'accès et consigne l'événement dans la chronologie des incidents avec la sévérité Critical (aucune notification contextuelle n'est affichée sur le bureau ; l'événement est transmis si le transfert d'événements est activé). Les accès par les exécutables de RoamSwitch lui-même, et par la famille des clients OpenSSH (ssh, scp, sftp, ssh-add, ssh-agent) sur les leurres SSH, ne sont pas consignés, afin d'éviter les fausses alertes ; un accès dont l'exécutable ne peut pas être identifié est consigné. Cette fonction se contente de consigner : elle ne fige ni ne bloque elle-même le processus concerné.

Limite connue — le contenu du leurre ~/.ssh/id_rsa comprend du texte lisible et reste, en tant que corps base64, un format invalide pour une véritable clé privée OpenSSH. Cependant, la plupart des scanners de secrets signalent les fichiers uniquement par correspondance de motif sur l'en-tête -----BEGIN ... PRIVATE KEY-----, ce qui limite selon nous l'impact réel sur le taux de détection. Par ailleurs, le processus accédant au fichier est normalement identifié à partir du PID porté par l'événement fanotify ; ce n'est que lorsque fanotify n'est pas disponible et que l'observateur bascule sur inotify qu'un appel à fuser est utilisé, ce qui peut échouer à obtenir un PID ou un nom de processus pour un processus extrêmement éphémère qui se termine immédiatement après la lecture. Dans ce cas, il est simplement traité comme inconnu, ce qui n'affecte pas la détection de l'accès lui-même (l'événement se déclenche et est consigné).

surveillance fanotify · Coffre-fort de quarantaine · ClamAV

  • Le démon surveille /home, /tmp, /var/tmp et /dev/shm avec des marques au niveau du système de fichiers (depuis la 1.10.18 ; une marque au niveau du montage ne sert que de repli lorsqu'une marque au niveau du système de fichiers est refusée) et surveille aussi la fin des écritures sur l'ensemble de / au niveau du système de fichiers, via FAN_CLASS_CONTENT (mode de permission ; repli FAN_CLASS_NOTIF). Depuis la 1.10.4, les événements de permission ne concernent que l'exécution (FAN_OPEN_EXEC_PERM) et open est traité comme une notification (fanotify_open_perm rétablit l'ancien comportement). Les exécutables déjà analysés comme sains sont mis en cache 60 secondes, et les processus enfants du démon sont exclus de la surveillance.
  • Notifier d'abord, pas de quarantaine avant confirmation (garde fanotify). Dans la garde fanotify, un simple hit YARA / ClamAV n'entraîne jamais de quarantaine sans confirmation ni de FAN_DENY. Une vraie signature déclenche une requête modale dans /run/roamswitch/approvals.json (Quarantaine / Autoriser / Plus tard) ; rien n'entre dans le Vault jusqu'à ce que l'utilisateur confirme. Seul un execve réel est refusé par FAN_DENY, et seulement lorsque pre_exec_blocking_enabled est activé. Le délai d'approbation expire en mode fail-open. La surveillance des téléchargements (par défaut ~/Downloads et ~/Desktop, plus watched_folders) suit un autre chemin : un fichier signalé par YARA ou ClamAV (EICAR excepté) est déplacé aussitôt dans le Vault de quarantaine, sans confirmation, puis une notification est émise.
  • Les signatures de test telles que EICAR sont uniquement journalisées — la chaîne de test standard de l'industrie ne déclenche aucune notification, seulement une entrée dans l'historique des notifications, et n'est jamais mise en quarantaine ou bloquée, à la fois dans la garde fanotify et dans la garde de téléchargement.
  • Une liste de chemins scan_exclusions configurée par l'utilisateur (chemins absolus, appliqués de manière récursive ; elle englobe l'ancien clamav_exclusions) est honorée par les scanners YARA et ClamAV. « Autoriser » à l'invite et « Restaurer » dans l'onglet Quarantaine s'y ajoutent tous deux, de sorte que le même fichier n'est pas re-marqué.
  • Les logiciels malveillants réels détectés (dans la garde fanotify, seulement ceux que l'utilisateur a confirmés) sont déplacés vers /var/lib/roamswitch/quarantine/, réservé à root (c'était auparavant ~/.local/share/roamswitch/quarantine/ de chaque utilisateur ; l'ancien emplacement est migré automatiquement au démarrage). Le répertoire du Vault appartient à root en 0700 ; chaque échantillon appartient à root en 0400 (lecture seule, non exécutable). Les utilisateurs ne peuvent agir sur le Vault que par l'IPC du démon, et ne peuvent lister, restaurer et supprimer que les éléments qu'ils ont eux-mêmes mis en quarantaine. Une restauration est écrite, sous la forme d'un fichier 0644, par un processus enfant qui a abaissé ses privilèges à ceux de l'utilisateur appelant (un fichier existant n'est jamais écrasé).
  • La boucle d'événements de permission ne lit jamais le fichier de configuration — la configuration est lue par un thread d'arrière-plan dédié dans un instantané Mutex<GuardConfig> ; les accesseurs de configuration du hot-path se contentent de verrouiller et de copier (la lecture du début du fichier sur le point d'être exécuté, pour le juger, est distincte). Il s'agit d'un invariant de conception : bloquer sur FAN_OPEN_EXEC_PERM gèlerait la machine.
  • Lorsque ClamAV est présent (clamav_enabled, activé par défaut), les nouveaux fichiers qui apparaissent dans les dossiers surveillés par la surveillance des téléchargements (par défaut ~/Downloads et ~/Desktop) sont analysés par rapport à la base de données locale, après l'analyse YARA intégrée. Les exécutables de type Windows comme .exe déclenchent une notification, les formats de distribution Linux (.sh, .deb, .elf, …) sont seulement consignés dans l'historique, et les modèles d'IA dangereux (.pkl, .pt) déclenchent une notification indiquant leur provenance. ClamAV est une dépendance facultative ; les autres défenses fonctionnent sans cela. Lorsqu'il est installé, le programme de mise à jour quotidien (§11) appelle freshclam pour que la base de données de signatures reste à jour.

Enregistreur d'exécutions de processus (local uniquement)

Un enregistreur strictement local qui consigne les événements exec et les changements d'uid via le connecteur de processus netlink (proc connector). Il enregistre le chemin de l'exécutable, les arguments de la ligne de commande (limités à 64 arguments, 512 caractères chacun et 4096 au total, caractères de contrôle échappés), la filiation des processus parents, l'uid, le répertoire courant, l'ID du conteneur, le SHA-256 de l'exécutable, etc. Il est activé par défaut, mais désactivé par défaut sur les machines de moins de 1 GiB de RAM (un réglage explicite de exec_recorder_enabled l'emporte). Sa propre consommation CPU est plafonnée à 3 % d'un cœur (exec_recorder_cpu_budget_pct) et il s'exécute en nice 10. Si le connecteur de processus ne peut pas être ouvert, il se replie sur un balayage peu fréquent de /proc (qui manque les processus de courte durée).

  • Stockage — les enregistrements sont stockés sous /var/lib/roamswitch/exec_log en JSON Lines découpé en segments de 8 MiB (0600). La rétention par défaut est de 200 Mo / 14 jours (exec_recorder_retention_mb / exec_recorder_retention_days). Une chaîne de hachage rend l'altération détectable mais non empêchable (un attaquant ayant obtenu root peut réécrire toute la chaîne). Recherche avec roamswitch events search|tree|export|verify ; les outils MCP en lecture seule pour les clients IA sont search_exec_events et get_process_tree.
  • Règles de corrélation à notification seule (EXR-001 à EXR-010) — dix règles : shell lancé par un service web, exécution depuis /tmp, un fichier en mémoire (memfd) ou un fichier supprimé, exécution juste après un téléchargement, one-liner base64/eval, obtention de l'uid 0 sans chemin setuid, exécution d'un binaire déposé juste après une modification cron/systemd, exécution après modification de /etc/ld.so.preload, suppression d'un instantané local du système de fichiers (btrfs, LVM) par autre chose qu'un outil de gestion connu comme snapper ou timeshift (EXR-008, 1.10.11), un même acteur qui recherche 8 outils d'attaque ou plus avec which et équivalents en 30 secondes (EXR-009, 1.11.0), et un interpréteur de scripts comme python ou node qui lance 3 outils d'attaque ou plus en 2 minutes (EXR-010, 1.11.0 ; une saisie humaine sous sshd, login ou un terminal est exclue). Chaque règle porte un identifiant de technique MITRE ATT&CK. Les règles se bornent à notifier et n'isolent jamais seules ; seulement si un opérateur active explicitement exec_rules_isolation_optin (false par défaut), une règle de gravité élevée est traitée comme un déclencheur d'Air-Gap à forte confiance (§4).
  • Rien ne quitte la machine — le journal brut n'est jamais transféré. Seulement si le transfert d'événements (§9.4) est activé, les notifications de règles (gravité, ID de règle, nom du processus, résumé) et les empreintes de scellement des segments sont envoyées, et uniquement vers la destination configurée par l'opérateur.

Surveillance de l'accès aux fichiers d'identifiants (activée par défaut, notification seule)

Activée par défaut (credential_access_watch_enabled vaut true ; false l'arrête, tout comme l'interrupteur de l'écran de protection contre les malwares). Elle notifie lorsqu'un programme inattendu ouvre une clé privée SSH (~/.ssh/id_*), ~/.aws/credentials, les fichiers d'identifiants sous ~/.config/gcloud/ (quatre types, dont credentials.db) ou le magasin d'identifiants/cookies d'un navigateur courant (notification seule). Elle se contente de poser individuellement l'événement fanotify de classe notification FAN_OPEN sur un petit nombre de fichiers, ne bloque, ne gèle et ne refuse jamais rien, et le noyau n'attend aucune réponse. Le nom de l'exécutable du programme qui a ouvert le fichier est comparé à des listes d'autorisation par catégorie (ssh / git / gpg-agent, navigateurs, aws / terraform / kubectl, etc.), et les notifications répétées pour la même paire (programme, fichier) sont supprimées pendant 10 minutes. Les outils de sauvegarde (restic, borg, etc.) et les indexeurs (tracker, baloo, etc.) sont autorisés d'emblée. Ajoutez les autres programmes pour lesquels vous ne voulez pas de notification à credential_access_watch_allow dans config.json. Pour les identifiants AWS, les outils qui s'exécutent en tant que python, python3 ou ansible, comme les scripts boto3 et Ansible, sont autorisés (pour gcloud, python et python3) ; un one-liner comme python -c déclenche néanmoins une notification. La contrepartie de cette autorisation est qu'un programme de vol d'identifiants écrit sous forme de fichier de script python ne déclenche pas non plus de notification. Les autres interpréteurs comme node ou ruby, et les outils généraux de recherche et de synchronisation comme grep ou rclone, ne sont volontairement pas autorisés, car un programme qui vole des identifiants a exactement la même apparence ; ils peuvent donc provoquer des notifications. Une lecture très brève, comme cat ou head, où le programme se termine avant que RoamSwitch puisse identifier son exécutable, peut ne pas être notifiée (une lecture par un programme qui continue de s'exécuter un certain temps est notifiée). RoamSwitch n'utilise pas le fanotify de classe permission pour éviter cette limite, car cela pourrait bloquer le système. Les changements prennent effet en 30 secondes environ, sans redémarrage.

Le chemin d'intrusion dans les notifications d'altération de fichiers

Les notifications de la surveillance des fichiers critiques (FIM) du client et de la surveillance des fichiers de verrouillage de dépendances indiquent aussi le processus à l'origine de la modification (compte, exécutable, commande) et la connexion SSH à laquelle il appartient (source, heure, méthode d'authentification). Il s'agit de fim_intrusion_context_enabled dans config.json, true par défaut ; rien n'est envoyé vers l'extérieur. La notification d'un accès à un fichier d'identifiants n'ajoute une ligne que si le processus appartient à une connexion SSH. Le fonctionnement, les valeurs auxquelles on se fie et les trois réponses distinguées (trouvée / aucune connexion SSH trouvée / chemin de connexion indéterminé) sont les mêmes que dans la Server Edition. Ce qui n'a pas pu être vérifié n'est jamais présenté comme « pas une connexion ». L'enregistrement est conservé dans /var/lib/roamswitch/fim_intrusions.json et dans le champ recentIntrusionContext de l'outil MCP verify_fim.

Préemption et renforcement de l'élévation de privilèges locaux (LPE) du noyau

Lorsqu'un attaquant ou un logiciel malveillant prend pied sur un utilisateur non privilégié, la défense en profondeur empêche d'exploiter les vulnérabilités du noyau Linux pour obtenir les privilèges root.

  • Traitement des espaces de noms d'utilisateurs non privilégiés (atténuation Frag Gap CVE-2026-53362) — De nombreuses vulnérabilités LPE du noyau Linux moderne reposent sur des utilisateurs non privilégiés appelant unshare(CLONE_NEWUSER) pour créer des espaces de noms d'utilisateurs isolés, accédant ensuite à des structures de noyau non initialisées ou à des sous-systèmes réseau anciens. Le diagnostic vérifie et affiche l'état de user.max_user_namespaces et de kernel.unprivileged_userns_clone, mais comme leur désactivation peut casser les navigateurs, Flatpak, Electron et les conteneurs rootless, l'édition Client ne la recommande pas et ne l'inclut pas dans le renforcement en un clic (si user.max_user_namespaces = 0 est défini, le rapport conseille de rétablir la valeur par défaut). La désactivation est un palliatif qui ne ferme que les chemins d'attaque nécessitant un espace de noms utilisateur ; elle n'arrête pas les autres voies. Le passage de kernel.unprivileged_userns_clone à 0 uniquement pendant la connexion à un réseau non fiable n'est appliqué que si vous activez explicitement adaptive_security.untrusted_network_actions.auto_enable_userns_restriction dans guard.yaml (désactivé par défaut ; sans effet sur les noyaux qui n'ont pas ce paramètre). La Server Edition l'applique de façon permanente par défaut (harden_userns_enabled). La vraie solution est de mettre à jour vers un noyau corrigé et de redémarrer.
  • Yama LSM Memory Protection — L'application de kernel.yama.ptrace_scope = 1 (ou 2) empêche les processus non autorisés de se connecter via ptrace ou de lire la mémoire via /proc/$pid/mem, protégeant ainsi les sessions du navigateur et les clés privées GPG/SSH.
  • Core Dump Suppression — Le paramètre fs.suid_dumpable = 0 empêche les processus sensibles de planter d'écrire des vidages de mémoire en texte brut sur le disque.
  • /tmp & /dev/shm noexec Mount Defense — Pour empêcher les attaques par suppression et exécution de charge utile, RoamSwitch remonte /tmp et /dev/shm avec noexec,nosuid,nodev lors de la connexion à des réseaux non fiables.

Capteur de menace d'exécution eBPF et gel des processus autonomes (Desktop eBPF Guard)

Le démon client RoamSwitch inclut une intégration native avec les capteurs eBPF modernes du noyau (Falco / Tetragon / BTF).

# Détection des menaces runtime eBPF et flux d'interception autonome
[Processus malveillant (LPE/malware)]
    │ Exécution d'appel système illégal (tentative d'exploitation Frag Gap)
    ▼
[Noyau Linux (sonde eBPF)]
    │ Tampon circulaire eBPF (conception compacte de 2 Mo)
    ▼
[Falco (RoamSwitch Optimized)]
    │ Socket UNIX direct en mémoire (/var/run/roamswitch/events.sock)
    ▼
[roamswitch-daemon (root)]
    ├─► ① Gel du processus attaquant à la milliseconde : kill(pid, SIGSTOP) [état : Ts]
    ├─► ② Coupure réseau : Critical ne touche que l'utilisateur de ce processus (roamswitch_isolate), Emergency/Alert : nftables input/output drop [Air-Gap activé]
    └─► ③ Alerte d'urgence de bureau : modal GTK3 & notification libnotify
  • Direct UNIX Domain Socket IPC (/var/run/roamswitch/events.sock) — Les alertes de Falco ou d'autres capteurs eBPF sont reçues directement sur un socket UNIX en mémoire sans écrire dans les journaux de disque, éliminant ainsi la latence d'E/S disque et permettant des transferts de réponse en moins d'une seconde.
  • Réponse autonome en quelques millisecondes (gel du processus et coupure réseau) — Lorsqu'un événement de priorité Critical (tel que le LPE du noyau, l'élévation de privilèges ou le chiffrement par ransomware) est reçu, le démon envoie SIGSTOP au PID incriminé pour l'arrêter complètement (état : Ts), bloque uniquement le trafic de l'utilisateur réel de ce processus (ou de son cgroup, dans le cas d'un conteneur) dans la table roamswitch_isolate (« isolement ciblé ») et déclenche une notification sur le bureau. Si aucun PID n'est identifié, si le gel est refusé parce que le processus est protégé, si l'isolement échoue (par exemple pour un processus root), ou si la priorité est Emergency ou Alert, le démon déploie policy drop sur la table inet roamswitch (Air-Gap) et envoie aussi SIGSTOP lorsqu'un PID est connu. Les événements de priorité inférieure à Critical ne déclenchent aucun blocage et sont seulement consignés dans l'historique.
  • Réglage à faible surcharge pour ordinateurs de bureau et prévention de la croissance excessive des journaux (RoamSwitch Optimized) Pour limiter la consommation de batterie, la charge CPU et l'épuisement du disque sur les ordinateurs portables et les stations de travail, un profil Falco optimisé (99-roamswitch-optimized.yaml) et une configuration logrotate sont fournis pour les environnements où Falco est installé. Les paquets de l'édition Client et le mode bureau de install.sh n'installent pas Falco et ne placent pas ce profil (les paquets de la Server Edition et install.sh --server le font). Si vous utilisez Falco avec l'édition Client, vous installez Falco vous-même et, si vous le souhaitez, placez vous-même ce profil. Les principaux réglages sont les suivants.
    • Severity Filtering (priority: warning) — Filtre 95 % des événements d'avis/informations de routine.
    • Kernel Early Drop (drop_failed_exit: true) — Supprime les sorties d'appel système ayant échoué directement dans le pilote du noyau, réduisant de moitié les changements de contexte et la charge du processeur.
    • Compact Ring Buffer (cpus_for_each_buffer: 2) — Réduit la taille du tampon en anneau à 2 Mo, en maintenant la RAM résidente entre 30 et 50 Mo et l'utilisation du processeur <1 %.
    • Capuchon obligatoire de rotation des journaux (/etc/logrotate.d/roamswitch-falco) — Applique un plafond strict de 50 Mo avec une rotation compressée sur 7 générations.
  • Résilience hybride (renforcement autonome ou extension eBPF) — Même sans Falco, RoamSwitch se défend de manière autonome grâce au sysctl du noyau (restriction ptrace de Yama, suppression des core dumps et protections réseau, appliqués à chaque changement de profil), aux profils nftables, à fanotify et à la surveillance des canaris. Avec Falco, l'interception des zero-day et le gel des processus par événements eBPF en temps réel s'y ajoutent, pour une défense de la plus haute intensité.

§6

Les événements USB sont surveillés ; Les appareils de classe clavier (HID) et le stockage de masse USB sont suivis. Désactivé par défaut (expédition isEnabled = false, correspondant au macOS USBKeyboardGuard / USBStorageGuard). Inscrivez-vous à partir de l'assistant de première exécution ou de l'onglet USB ; l'activation affiche une boîte de dialogue de confirmation.

Clavier USB malveillant (injection de frappe)

  • Aucune désautorisation de port physique au niveau du noyau (important). L'ancienne implémentation (≤1.0.6) écrivait 0 en /sys/bus/usb/devices/*/authorized pour un clavier non enregistré, en le déconnectant physiquement. Sur un récepteur combiné clavier + souris (commun sur un Raspberry Pi ou un mini PC), cela déconnecte également la souris, verrouillant ainsi une machine sans entrée intégrée. RoamSwitch refuse désormais catégoriquement d'écrire authorized=0 sur tout périphérique exposant une interface HID (bInterfaceClass 03), et le démon réautorise tout périphérique HID authorized==0 au démarrage pour auto-réparer les machines dont la version précédente est maçonnée.
  • Les frappes sont supprimées dans le logiciel. Lors de la détection d'un clavier non enregistré, un evdev exclusif EVIOCGRAB est pris sur le /dev/input/eventN de cet appareil (uniquement les nœuds avec un gestionnaire kbd dans /proc/bus/input/devices, correspondant à VID:PID). Le nœud de la souris est intact. Il s'agit de l'équivalent Linux de macOS CGEventTap ; l'appareil reste alimenté.
  • Safety — la saisie n'est effectuée que s'il existe un autre clavier sur lequel taper (un clavier intégré ou un clavier USB approuvé et connecté), de sorte qu'une machine avec un seul nouveau clavier n'est jamais verrouillée.
  • Boîte de dialogue d'approbation — en même temps que la capture, une demande d'approbation est placée dans /run/roamswitch/approvals.json et l'interface affiche une fenêtre modale (Autoriser / Refuser). Autoriser ajoute l'appareil à la liste blanche et relâche la capture. Sans approbation au bout de 3 minutes, le clavier n'est plus relâché par défaut (depuis la 1.10.4 ; les versions antérieures s'ouvraient en fail-open après 3 minutes, et activer usb_keyboard_timeout_release rétablit ce comportement).

Stockage de masse USB malveillant

Lorsqu'un périphérique de stockage de masse USB non enregistré (sans interface HID) est inséré et que la protection est activée, il est maintenu avec authorized=0 (sans danger pour un périphérique non HID) et une boîte de dialogue d'approbation s'affiche, que le réseau soit fiable ou non. Un lecteur monté ou utilisé comme swap (un disque externe en cours d'utilisation, une clé USB live) n'est pas maintenu, car cela équivaudrait à un retrait brutal pouvant l'endommager ; l'événement est seulement consigné dans le journal. Autoriser réautorise et ajoute à la liste blanche ; Refuser le maintient en attente. La version macOS a été alignée sur ce modèle « conserver en lecture seule + invite d'approbation » dans la version 1.7.4.

Identification et droits acquis

HID est déterminé à partir de bInterfaceClass == "03" et de la ligne Handlers= dans /proc/bus/input/devices. Un récepteur combo (un VID:PID avec à la fois une section kbd et une section souris) est fusionné par VID:PID. L'enregistrement des claviers existants dans la liste d'autorisation (grandfathering) est effectué par l'interface graphique, et non par le démon. Les claviers connectés à ce moment-là sont enregistrés dans la liste d'autorisation lorsque vous activez la protection dans l'assistant de première configuration (ainsi que lors d'une migration unique pour une installation existante dont la protection est activée, dont la configuration initiale est terminée et dont aucun clavier connecté ne figure dans la liste d'autorisation). Une activation ultérieure dans l'onglet USB n'enregistre rien. Un clavier connecté au démarrage du démon mais non enregistré est traité comme non enregistré, car il a pu être remplacé avant le démarrage, et il est récupéré (grab) si un autre clavier est disponible pour saisir.

§7

Le LinuxHealthChecker dans roamswitch-core évalue 27 éléments et produit un score de 0 à 100, une note S/A/B/C (S pour 100, A pour 85 à 99, B pour 70 à 84, C en dessous) et des conseils par élément. À partir du tableau de bord GTK, de la CLI (roamswitch status) et du MCP (get_security_report), le titre, les détails et la recommandation des 27 éléments apparaissent dans la langue de l'utilisateur (10 langues).

Catégories d'évaluation
CatégorieExemples d'éléments
Disque/démarrageCryptage LUKS/dm-crypt, démarrage sécurisé UEFI
Contrôle d'accèsLSM (AppArmor / SELinux), audit de configuration SSH / sudo
Durcissement du noyauRenforcement de sysctl, vérification de l'état des espaces de noms d'utilisateurs non privilégiés (atténuation Frag Gap ; l'édition Client ne recommande pas de les désactiver), restriction ptrace de Yama, contrôle du core-dump, noexec sur /tmp et /dev/shm
Mises à jourconfiguration automatique des mises à jour de sécurité
RéseauSurveillance de l'usurpation d'identité ARP, état de verrouillage ARP/NDP de la passerelle, audit des ports exposés, force de cryptage Wi-Fi
Logiciel malveillantClamAV (y compris un test de détection en conditions réelles avec EICAR) / fanotify (vérifie également que le garde est réellement en cours d'exécution et qu'aucun événement n'a été manqué par débordement de file) / surveillance de l'entropie / noexec / vérification de l'installation des programmes auxiliaires (honeytokens et collecte de preuves)
NavigateurParamètres de navigation sécurisée Firefox / Chrome / Chromium
DNS/USBDNS de protection contre les menaces, état zéro confiance du bus USB
Conteneurs et vulnérabilitésExposition par montage du socket Docker, audit des conteneurs privilégiés (--privileged), niveau d'isolation du runtime de conteneurs (runc / gVisor / Kata), exposition aux CVE connues du noyau en cours d'exécution

Chaque vérification est basée sur la lecture des fichiers locaux et du résultat de la commande (getenforce, mokutil, resolvectl, ss,…). Il n'y a aucune requête externe d'aucune sorte.

Classement conforme à la confiance. Le renforcement que RoamSwitch n'applique délibérément pas sur un réseau (domestique) de confiance - en épinglant le MAC de la passerelle comme PERMANENT (ce qui perturberait le réseau local lors d'un redémarrage du routeur), en remontant /tmp et /dev/shm noexec (ce qui interrompt les builds de packages et certains installateurs) et le DNS de protection contre les menaces dans la portée par défaut - est affiché en vert avec une explication plutôt qu’un rouge « non durci », et note qu’il s’applique automatiquement sur un réseau non fiable. La vérité d'exécution vient de /run/roamswitch/state.json (active_level / network_trusted / fanotify_ready), écrite à chaque cycle par le démon. L'élément fanotify vérifie que le garde est réellement marqué et exécuté (/run/roamswitch/fanotify.ready), pas seulement que le noyau le prend en charge. L'élément de ports exposés lit le profil en direct du démon (le code dur MCP a disparu).

Renforcement par élément. Les éléments défaillants qui ont un correctif automatisé (noyau sysctl / Yama / core dumps, /tmp noexec, USB zero-trust, épinglage ARP de passerelle, DNS de protection contre les menaces, redémarrage du démon lorsque la garde fanotify est en panne) comportent un bouton « 🔧 Harden » dans le tableau de bord GTK. Le démon s'exécute en tant que root, donc aucun pkexec n'est nécessaire, sauf pour le redémarrage du démon.

Cartographie NIST CSF 2.0 / CIS Controls v8

Chacun des 27 éléments est associé à un identifiant de fonction/catégorie NIST CSF 2.0 (par ex. PR.DS-01, DE.CM-09) et, uniquement pour les éléments où une correspondance fiable existe, à un numéro de contrôle CIS Controls v8 (par ex. 3.11, 4.1). Les éléments dont la correspondance est ambiguë ne sont pas forcés et restent non associés. Cette cartographie (nistCsf / cisControl) figure dans la sortie du MCP (get_security_report) et dans la sortie JSON de la CLI (roamswitch status --json). Elle n'apparaît ni dans l'interface graphique ni dans l'affichage normal de la CLI de l'édition Client (l'affichage de roamswitch status --server de la Server Edition, lui, la comporte). Elle permet aux opérateurs et auditeurs soumis à des exigences de conformité de rattacher mécaniquement les contrôles de RoamSwitch aux référentiels existants.

§8

roamswitch-mcp parle JSON-RPC sur stdio et est lancé par des clients IA tels que Claude Desktop, Cursor et Antigravity. Chaque outil est en lecture seule, et presque tous s'exécutent entièrement en local. Le seul outil susceptible de se connecter à un hôte externe est run_npm_audit_signatures, désactivé par défaut. L'évaluation de get_security_report et de verify_security_findings n'envoie rien au réseau local, et la passerelle est seulement lue dans le cache ARP et d'autres informations locales. Un ping ne peut partir que dans d'autres traitements qui résolvent à nouveau la MAC de la passerelle (surveillance ARP du démon, restauration d'urgence, appairage Sensor, get_guard_status, etc.).

Principaux outils exposés (liste complète : Configuration MCP)
OutilObjectif
get_security_reportÉvaluation en 27 éléments, score et recommandations
verify_security_findingsRéévalue des éléments (checkId) de get_security_report selon l'état actuel et renvoie un verdict par élément, stillPresent (toujours d'actualité), resolved (corrigé, ou sans objet par conception) ou inconclusive (indéterminable), avec un code de motif indépendant de la langue. Il sert à vérifier, après une correction, qu'elle a bien pris effet. Un élément qui n'a pas pu être mesuré, par exemple faute de privilèges, d'assistant privilégié connecté ou d'accès à Docker, n'est jamais déclaré sûr et ressort comme inconclusive. Sans checkIds, tous les éléments sont vérifiés (jusqu'à 50 identifiants sur Mac et 64 sous Linux ; sous Linux, isServer est aussi accepté). Sur Mac, host_firewall, network_stealth_mode, gateway_arp_lock, malware_scanning, dns_threat_guard et usb_zero_trust sont déterminés par les réglages de l'app RoamSwitch. Ils reflètent donc ces réglages et ne constituent pas une nouvelle mesure de l'état réel du système. L'outil est en lecture seule, ne se connecte à aucun hôte externe, ne répare rien et ne modifie aucun réglage. Sous Linux, il n'envoie rien non plus au réseau local et lit seulement la passerelle dans les caches locaux. Sur Mac, pour retrouver l'adresse MAC de la passerelle, il peut envoyer un ping ICMP, normalement un seul (quelques-uns si la MAC reste introuvable), à la passerelle du réseau local.
audit_mcp_configsLit les fichiers de configuration MCP des clients IA (Claude Desktop, Claude Code, Cursor, Windsurf, VS Code, Cline, Gemini CLI, ainsi que .mcp.json et fichiers similaires dans les dossiers de projet enregistrés) et signale les réglages risqués. Il détecte les outils approuvés automatiquement sans confirmation à chaque appel (auto_approve), les serveurs http:// en clair sur un hôte autre que loopback, que l'adresse soit dans le champ url ou passée comme argument de lancement, par exemple --server http://… (remote_plain_http), les lancements par des commandes shell ou interpréteur d'une ligne (shell_wrapper), les lanceurs de type npx / uvx / docker run qui récupèrent un paquet ou une image non épinglés (unpinned_package_launcher), docker run avec --privileged ou avec le socket Docker ou le système de fichiers racine montés (container_host_access), les variables d'environnement qui font charger du code supplémentaire au démarrage, comme LD_PRELOAD ou --require dans NODE_OPTIONS (noms insensibles à la casse, valeurs jamais renvoyées ; code_loading_env), les entrées qui pointent vers HexStrike AI, un backend d'outils d'attaque sans authentification (hexstrike), les fichiers de configuration modifiables par le groupe ou les autres (world_writable_config), l'option enableAllProjectMcpServers de Claude Code (auto_trust_project_servers) et un fichier de configuration qui contient à la fois un serveur lisant des textes écrits par d'autres (pages web, mails, discussions, issues) et un serveur pouvant exécuter des commandes ou modifier des fichiers, la combinaison dont l'injection de prompt indirecte a besoin (reconnue par des noms de paquets connus, sans voir le contenu des outils des serveurs ; injection_exposure). L'outil n'a aucun argument, est en lecture seule et n'envoie rien au réseau. Il ne renvoie jamais de valeurs d'environnement ou d'en-tête, d'arguments ni de secrets, seulement des noms de serveurs, le nom de base d'une commande, un hôte d'URL et, pour un simple nom de registre, le nom du paquet ou de l'image. Les fichiers impossibles à analyser sont listés dans unreadableFiles et n'ont pas été audités.
audit_agent_setupLit les fichiers d'instructions et les skills donnés aux agents IA (CLAUDE.md, commands, agents et skills sous ~/.claude, ainsi que ~/.codex, ~/.cursor/rules, ~/.gemini, ~/.agents, ~/.openclaw, et CLAUDE.md, AGENTS.md, .cursorrules et équivalents dans les dossiers de projet enregistrés) et le openclaw.json d'OpenClaw, et signale les contenus et réglages risqués. Dans les fichiers d'instructions et de skills, il signale des phrases cachées dans des caractères de balise Unicode invisibles (hidden_tag_characters), des caractères de largeur nulle et de contrôle de direction (invisible_characters), des formulations qui écrasent des instructions antérieures ou cachent quelque chose à l'utilisateur (override_instruction), le téléchargement-exécution comme curl … | sh (remote_script_exec), le décodage-exécution (encoded_payload_exec), des références à des stockages de clés et d'identifiants (credential_access) à côté d'un envoi ou d'un service de dépôt (credential_exfil_shape, exfil_endpoint), la mise en place de mécanismes persistants (persistence_instruction), du code compilé dans un skill (skill_precompiled_code) et des fichiers modifiables par d'autres (agent_file_writable). Tout ce qui se trouve dans un commentaire HTML est évalué plus haut, car une page rendue le masque. Pour OpenClaw, il signale l'authentification désactivée (openclaw_auth_disabled), une écoute joignable depuis le réseau (openclaw_wide_bind), un jeton de moins de 24 caractères (openclaw_weak_token), une origine générique (openclaw_origin_wildcard), des interrupteurs dangerously… activés (openclaw_dangerous_flag), une liste de proxys qui fait confiance à tout le monde (openclaw_broad_trusted_proxy, openclaw_proxy_trust_loosened), une politique de messages privés ou de groupe ouverte à tous (openclaw_open_dm), mDNS en full (openclaw_mdns_full), des constats d'autodiagnostic masqués (openclaw_audit_suppressed) et des permissions trop larges (openclaw_loose_permissions). Chaque règle est déterministe : une correspondance signifie « une personne devrait lire cette ligne », pas « malveillant ». Il ne prend aucun argument, est en lecture seule et n'envoie rien au réseau. Il ne renvoie jamais le contenu des fichiers ni des secrets (un jeton n'est signalé que par sa longueur), seulement un nom de règle, un numéro de ligne, un hôte d'URL et un chemin de clé de configuration. Les fichiers illisibles figurent dans unreadableFiles et n'ont pas été audités.
get_exposed_portsports exposés et leur état de blocage du pare-feu
get_guard_statusniveau de protection actuel et état du module de défense
audit_url_sécuritéanalyse heuristique de la sécurité des URL (ne récupère pas la cible)
audit_secretsdétection des clés API/secrets dans le texte (ne transmet pas le texte)
audit_security_logsagrégation des journaux journald/auth locaux
get_quarantine_status / get_canary_statusquarantaine et état des Canaries
get_app_helprecherche dans la base de connaissances intégrée
run_active_vuln_scanVérification active de vulnérabilités, non destructive et limitée à 127.0.0.1. Aucun autre hôte n'est contacté, mais l'outil envoie des requêtes réseau ; il est donc désactivé par défaut et doit être activé (opt-in) dans les réglages.
run_package_cve_scanCompare les paquets installés (Mac : Homebrew / Linux : dpkg, dnf, zypper, pacman) à une carte CVE locale. Aucune communication réseau n'a lieu. Sous Linux, si la liste des paquets n'a pas pu être lue ou a expiré, l'outil ne répond pas « aucun résultat » ; complete passe à false et incompleteReason donne la raison (un résultat inachevé n'est pas un résultat sain). Sous Ubuntu (20.04, 22.04, 24.04, 26.04), l'outil utilise les données propres à Ubuntu, compare par nom et version du paquet source, et ne vérifie que les CVE de CVSS 7,0 ou plus pour lesquelles Ubuntu a déjà publié un correctif ; « aucun résultat » ne signifie donc pas qu'il n'y a pas de CVE connue. Le résultat gagne ecosystem et release, chaque détection porte binary (le nom du paquet binaire installé), et les correctifs disponibles seulement via Ubuntu Pro sont marqués proOnly. Les dérivées d'Ubuntu (Linux Mint, Pop!_OS, etc.) et les versions non prises en charge renvoient complete = false avec la raison dans incompleteReason, sans bascule vers les données Debian.
run_package_cve_scan_languagesCompare 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.
get_vulnerability_scan_historyRenvoie, pour chaque sonde consignée par scan-vulns --confirm, le nombre de jours écoulés depuis sa dernière exécution et confirmation effective (pass_age_days).
verify_fimRecalcule les empreintes de 23 fichiers système critiques fixes, de tous les fichiers situés directement dans 12 répertoires (authentification, cron, systemd, configuration SSH, etc.), des binaires de RoamSwitch et du authorized_keys de chaque utilisateur, puis les compare à la référence pour vérifier les altérations et les nouveaux fichiers. Lancé en root, le résultat contient aussi un champ ajouté, recentIntrusionContext : pour chaque modification récente, le processus qui l'a faite (compte, exécutable, commande) et la connexion SSH à laquelle il appartient (source, heure de connexion, méthode d'authentification), ou la raison pour laquelle cela n'a pas pu être trouvé.
get_file_scan_guard_statusRenvoie la configuration du File Scan Guard (ClamAV) et l'état du Vault de quarantaine qu'il alimente (outil de la Server Edition ; il ne concerne pas les paramètres de la garde anti-malware de l'édition Client).
get_port_anomaly_incidentsRenvoie 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é.
get_ebpf_incidentsRenvoie l'état d'isolation actuel du garde d'exécution eBPF et l'historique des incidents à son origine. Sur la Server Edition, chaque enregistrement porte aussi le temps entre la détection et la réponse en millisecondes (detect_to_receive_ms, receive_to_action_ms, detect_to_action_ms), et le résultat inclut l'état du heartbeat de Falco (heartbeat).
get_ssh_guard_incidentsRenvoie l'état et l'historique de la garde contre la force brute SSH : si elle bannit ou ne fait que détecter (par exemple parce qu'un autre outil protège déjà sshd), les sources actuellement bannies avec la durée restante, et jusqu'à 200 enregistrements récents (une source a été bannie, simplement détectée, ou une connexion a réussi après une série d'échecs de la même source). Les noms d'utilisateur essayés ne sont pas conservés sous Linux (seulement leur nombre) et sont renvoyés assainis, jusqu'à 5, sous Mac. Le Mac renvoie aussi les connexions SSH récentes (source, utilisateur, méthode, empreinte de clé). Lecture seule : aucun outil MCP ne lève un bannissement. Sous Linux, la liste des bannissements n'est lisible que par root ; un appel sans root ne renvoie que le nombre. Linux : la Server Edition, et l'édition Client tant que sshd tourne. Mac : Pro uniquement.
get_resource_guard_incidentsRenvoie l'historique du garde contre l'épuisement des ressources et les anomalies de processus (fuites mémoire, boucles de plantage), avec un niveau de confiance (ce garde appartient à la Server Edition ; l'édition Client ne l'exécute pas, le résultat y est donc toujours vide).
get_notification_historyRenvoie 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.
get_incident_timelineRegroupe les détections de tous les gardes dans une chronologie unique, avec la filiation des processus et des étiquettes MITRE ATT&CK (expérimental).

La vérification active des vulnérabilités consigne également, pour chaque sonde confirmée, le nombre de jours écoulés depuis sa dernière exécution et confirmation effective (pass_age) — distinguant ainsi « ce contrôle existe » de « ce contrôle a réellement été vérifié récemment ». Aucun exécuteur planifié n'existant encore, cette valeur ne reflète que la fréquence à laquelle l'analyse a effectivement été lancée. Ce même nombre de jours écoulés est disponible via la CLI (roamswitch vuln-status), l'interface graphique et MCP (get_vulnerability_scan_history), ce qui permet à un opérateur ou à un auditeur de savoir quand une vérification a réellement eu lieu pour la dernière fois, plutôt que de le supposer du simple fait que la fonctionnalité existe.

Le serveur MCP n'ouvre lui-même aucune connexion réseau. Seul get_sensor_audit_results se connecte directement au socket Unix du démon (/run/roamswitch/roamswitch.sock) ; tous les autres outils appellent directement la logique de roamswitch-core ou lisent seulement les fichiers d'état écrits par le démon (roamswitchkit fonctionne en sens inverse : c'est le SDK qu'utilisent la CLI et d'autres pour lancer roamswitch-mcp comme processus enfant). Les exceptions sont deux outils désactivés par défaut : run_active_vuln_scan envoie des sondes non destructives vers 127.0.0.1, c'est-à-dire l'hôte lui-même, et run_npm_audit_signatures lance la commande npm, qui interroge le registre npm (le binaire MCP lui-même ne contient aucune pile HTTP / TLS). L'évaluation du diagnostic (get_security_report et verify_security_findings) n'envoie rien au réseau local, et la passerelle est seulement consultée dans le cache ARP et d'autres informations locales. Un ping ne peut partir que dans d'autres traitements qui résolvent à nouveau la MAC de la passerelle (surveillance ARP du démon, restauration d'urgence, appairage Sensor, get_guard_status, etc.).

Une erreur d'attribution découverte — et corrigée — lors d'un exercice avec un LLM local
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.

§9

Par défaut, RoamSwitch pour Linux n'effectue qu'un seul type de requête sortante : le GET d'un manifeste signé par la mise à jour quotidienne (§11). Tout le reste – chaque évaluation, détection et audit – s'exécute localement. Les communications qui n'ont lieu que lorsqu'un opérateur ou un utilisateur les active ou les déclenche explicitement sont énumérées au 9.4. RoamSwitch pour Linux est un logiciel gratuit propriétaire dont le code source n'est pas publié ; les inspections ci-dessous ont donc été réalisées par lafine. Les résultats figurent au 9.2 et les étapes de l'annexe C sont reproductibles avec le seul paquet distribué.

9.1 Méthode d'audit

  1. Isolation des crates — une pile client HTTP/TLS n'est autorisée que dans un seul crate, roamswitch-updater, qui construit le binaire autonome de mise à jour piloté par minuterie. scripts/audit_no_network.sh (exécuté en CI, bloquant) fait échouer la compilation si roamswitch-core, roamswitch-daemon, roamswitch-app, roamswitch-mcp, roamswitchkit (ou les crates CLI et Server) intègrent une pile client HTTP/TLS comme reqwest / ureq / rustls / native-tls / openssl-sys, ou si un fichier .rs hors de roamswitch-updater et des fichiers cités au 9.4 utilise TcpStream / TcpListener / UdpSocket / to_socket_addrs / lookup_host. La seule autorisation supplémentaire concerne tonic / hyper, permis uniquement dans roamswitch-core pour du gRPC sur socket de domaine UNIX vers le capteur eBPF (Tetragon) ; le même script interdit de l'associer à TLS.
  2. Inspection de l'arborescence des dépendances — le Cargo.lock complet a été recherché pour les SDK de télémétrie/analyse/rapport de crash (sentry, opentelemetry, …).
  3. roamswitch-updater review — sa cible de requête unique, l'absence de chaîne de requête et de cookies, un User-Agent fixe comme seul en-tête qui nomme le logiciel et sa vérification Ed25519 de tout ce qu'il télécharge.
  4. Énumération des sous-processus — chaque site d'appel Command::new(...) a été examiné.

9.2 Résultats

Résultats de l'audit
Élément d'auditRésultat
Pile HTTP/TLSPrésent en roamswitch-updater uniquement (ureq + rustls). Pas en core / daemon / app / mcp / kit
SDK de télémétrie/analyse/rapport d'accidentsAbsent nulle part
TcpStream / TcpListener / UdpSocketUniquement dans roamswitch-updater et dans les quatre fichiers des fonctions optionnelles citées au 9.4
API de socket utiliséesAF_UNIX (IPC locale) ; AF_NETLINK (canal de verdict NFQUEUE, contrôle de nftables et ip neigh – aucun paquet émis) ; AF_INET uniquement dans roamswitch-updater et les fonctions optionnelles du 9.4 (TLS/HTTP sont délégués aux sous-processus système openssl/curl) ; AF_PACKET seulement quand la revérification ARP (§4) envoie un unique ARP who-has à la passerelle (sur le LAN). Le tunnel VPN (§4), s'il est activé, est wg-quick (backend WireGuard) ou tailscaled (backend Tailscale) – un sous-processus, pas le démon – qui envoie du trafic vers le point de terminaison / tailnet de l'utilisateur
Mise à jour quotidienneDeux requêtes environ toutes les 24 h : GET https://lafine.net/updates/v1/manifest et sa signature manifest.sig (les fichiers de données ne sont récupérés en plus que lorsque leur version est plus récente). Pas de chaîne de requête, pas de cookies, User-Agent: RoamSwitch-Updater fixe (le seul en-tête qui nomme le logiciel), pas d'identifiant propre à l'appareil. Toutes les charges utiles sont vérifiées par Ed25519. updates.enabled = false le désactive complètement
Tunnel VPN (§4)Désactivé par défaut. Lorsqu'il est activé, wg-quick crée un tunnel WireGuard vers le point de terminaison dans le propre .conf de l'utilisateur — sans rapport avec lafine. Résolution du nom d'hôte du point de terminaison via getent ahosts (glibc NSS), pas une API de socket Rust
Activation de la licenceVérification de la signature Ed25519 hors ligne. Aucune communication avec un serveur d'activation
Conclusion

Par défaut, RoamSwitch pour Linux n'initie qu'un seul type de requête sortante : la vérification quotidienne, en réception seule, d'un manifeste signé, avec téléchargement de données de détection signées lorsqu'une version plus récente existe ; rien de cela ne contient d'information sur l'utilisateur ni sur la machine. Sauf activation ou déclenchement explicite par un opérateur ou un utilisateur, rien d'autre dans le produit ne contacte un serveur externe (9.4). Le ping envoyé dans le réseau local pour la fixation de la table de voisinage et la surveillance ARP (#2 du 9.3) ne va que vers des adresses de loopback, privées, link-local et ULA, et n'est jamais envoyé vers une adresse publique.

9.3 Liste complète des opérations orientées vers l'extérieur

Toutes les opérations du fonctionnement par défaut susceptibles de faire sortir un paquet de la machine locale, directement ou indirectement (le tableau ci-dessous ; les opérations qui n'ont lieu que lorsqu'un opérateur ou un utilisateur les active ou les déclenche figurent au 9.4) :

#FonctionnementDestinationDéclencheurCe qui est envoyé
1Daily updater — GET /v1/manifest et sa signature ; les fichiers de données seulement lorsque leur version est plus récente (les cartes CVE du noyau, des paquets de l'OS et des langages sont vérifiées environ une fois tous les 30 jours ; un appareil Ubuntu sans la carte CVE d'Ubuntu la récupère lors de la prochaine exécution quotidienne)lafine.net/updates/v1 (CAN)Minuterie systemd : première exécution 15 minutes après le démarrage, puis environ toutes les 24 h avec un délai aléatoire pouvant atteindre 3 h. Éteint lorsque updates.enabled = false ou que roamswitch-update.timer est désactivéun simple HTTPS GET : pas de chaîne de requête, pas de cookies, User-Agent: RoamSwitch-Updater fixe, rien sur l'utilisateur ou la machine. Reçoit un manifeste signé + un flux
1afreshclam (le propre programme de mise à jour de ClamAV), invoqué par le programme de mise à jour quotidien lorsque ClamAV est installéle CDN du projet ClamAVdans le cadre du #1 ; également lorsque l'utilisateur appuie sur le bouton « Mettre à jour les signatures antivirus maintenant (freshclam) » de l'interface graphiquele trafic de ClamAV lui-même (un projet distinct) ; RoamSwitch le lance uniquement
2ping -c 1 -w 1 -W 1 <adresse IP de la passerelle> (le même type de ping unique vers le routeur IPv6 et vers les serveurs DNS du réseau local)la passerelle LAN par défaut ; pour la fixation ARP/NDP, également le routeur IPv6 par défaut et chaque serveur DNS listé dans resolv.conf (/run/systemd/resolve/resolv.conf sous systemd-resolved). Seuls ceux dont l'adresse est de loopback, privée, link-local ou ULA reçoivent un ping. Aucun paquet n'est envoyé à un résolveur public (par exemple Quad9 du DNS de protection contre les menaces)pour la passerelle, uniquement lorsque sa MAC est nécessaire (surveillance ARP, etc.) et ne se trouve ni dans le cache ARP, ni dans la table de voisinage, ni dans la copie en mémoire de moins de 15 secondes (au plus une fois par 8 s pour une même cible). Pour le routeur IPv6 et les serveurs DNS, lorsque le verrouillage ARP de la passerelle (gateway_arp_lock_enabled, activé par défaut) refixe la table de voisinage (lors du passage à un réseau non fiable et lorsque le paramètre est modifié)une seule requête d'écho ICMP (avec les données standard de ping). Pas de résolution DNS
3lancement du navigateur système (xdg-open)https://roamswitch.com/linux (site produit), https://lafine.net/ (site de l'opérateur, conditions d'utilisation, contact) et https://github.com/lafine1211/roamswitch-support (notes de version et assistance)uniquement lorsque l'utilisateur clique sur un bouton tel que « Ouvrir les notes de version »le processus RoamSwitch n'ouvre aucun socket ; il transmet l'URL au navigateur du système d'exploitation
4solvectl dns …— (aucune communication de RoamSwitch lui-même)DNS de protection contre les menaces sur un réseau non fiable (activé par défaut, désactivable). <iface> est chaque carte réseau physique transportant une route par défaut (filaire + sans fil à la fois ; lo / tailscale* / wg* / docker* / veth* exclus) ; également appliqué sur un réseau de confiance lorsque dns_scope = always_on ; réconcilié chaque cycle sentinellecela modifie uniquement le paramètre du résolveur du système d'exploitation ; RoamSwitch lui-même n'envoie aucune requête DNS. Ensuite, la résolution de noms de cette carte réseau passe par le DNS de protection contre les menaces choisi (Quad9 par défaut)
5mises à jour du gestionnaire de paquetsles dépôts apt / dnf / zypper / pacman de la distributionlorsque l'utilisateur exécute apt upgrade, etc., ou clique sur le bouton « Mettre à niveau maintenant » de l'interface graphique (qui exécute apt-get / dnf / zypper / pacman via pkexec)Sur les systèmes apt, RoamSwitch lit la sortie locale de apt-cache policy et lance le gestionnaire de paquets. Sur les systèmes dnf, il exécute dnf check-update (notamment à l'ouverture de l'onglet Mises à jour), ce qui peut interroger les dépôts configurés (zypper : zypper list-updates, pacman : pacman -Qu) ; dans tous les cas, sans rapport avec lafine
6Tunnel VPN (§4) — Backend WireGuard : le tunnel wg-quick construit / Backend Tailscale : tailscale set --exit-node achemine tout le trafic via le nœud de sortiele point de terminaison VPN / tailnet de l'utilisateur (WireGuard : le point de terminaison dans le .conf ; Tailscale : le nœud de sortie choisi et les relais DERP)lorsque vpn_on_untrusted_enabled et que le réseau n'est pas fiable. Désactivé par défauttrafic WireGuard/Tailscale crypté ; la destination est le propre serveur VPN/tailnet de l’utilisateur, sans rapport avec lafine. Résolution des points de terminaison WireGuard via getent ahosts (glibc NSS) ; Tailscale est piloté par tailscaled (un processus distinct)

Toutes les autres fonctions de RoamSwitch – contrôle de nftables, épinglage ARP/NDP de la table de voisinage (ip neigh, aucun paquet émis), garde des liens (NFQUEUE, uniquement AF_NETLINK), scan de ports (analyse de la sortie de ss), surveillance ARP, évaluation de l'état de santé, surveillance d'entropie, canaris, fanotify, surveillance USB (saisie evdev), quarantaine, audit des journaux, audit d'URL et de secrets – s'exécutent uniquement par lecture de fichiers locaux et analyse de sorties de sous-processus. Le tunnel VPN du §4 est la « communication sortante configurée explicitement par l'utilisateur » ; sa destination est le propre serveur VPN de l'utilisateur – aucune donnée n'est envoyée à l'infrastructure de lafine. La destination de chaque fonction du 9.4 y est indiquée.

9.4 Opérations qui n'atteignent l'extérieur que sur activation ou action de l'utilisateur

Le tableau du 9.3 décrit ce qui se passe par défaut. Ce qui suit recense les endroits du code qui ouvrent un socket ou confient la communication à un programme externe (curl / openssl / npm) et qui ne s'exécutent que si un opérateur ou un utilisateur les active ou les déclenche explicitement. scripts/audit_no_network.sh n'autorise les API de socket que dans cinq fichiers : event_forward.rs, webhook_guard.rs, active_vuln_scan.rs, web_vuln_scan.rs et sensor_pairing.rs (webhook_guard.rs se contente de résoudre le nom de la destination du webhook avec le résolveur du système, et seulement lorsque le paramètre forward_webhook_block_private_addresses, désactivé par défaut, est activé, afin de vérifier qu'il ne s'agit pas d'une adresse interne ; il ne se connecte jamais. En outre, tests/fleet_roundtrip.rs est réservé aux tests et se connecte uniquement à un serveur factice sur 127.0.0.1). La compilation échoue si une autre source utilise TcpStream et consorts. Ces fonctions n'ajoutent pas non plus de crate HTTP/TLS : TLS est laissé au openssl du système et HTTP au curl du système, en sous-processus.

  • Transfert d'événements (event_forward.rs) — désactivé par défaut (forward_enabled = false), et les seules destinations sont celles que l'opérateur configure à la main ; il n'existe aucune destination intégrée. Les puits pris en charge sont syslog (RFC 5424 ou CEF, en UDP / TCP / TLS), un fichier JSON Lines et un webhook signé HMAC-SHA256. Le syslog TLS passe par le openssl s_client du système et le webhook par le curl du système (les secrets comme l'URL et la signature sont transmis dans une configuration sur l'entrée standard, jamais en ligne de commande) ; le démon ne comporte aucun crate TLS/HTTP. Seuls ces champs sont envoyés : nom d'hôte, ID d'événement, horodatage, source, gravité, règle, action, PID, nom du processus et résumé (ni contenu de fichier ni journal brut d'exécution). Les événements non remis en TCP / TLS / webhook sont conservés dans une file sur disque en 0600 (plafonnée par défaut à 20 Mo / 72 heures) et renvoyés.
  • Fleet (niveau Business, côté appareil uniquement) (roamswitch-core/src/fleet/) — le code côté appareil est inclus dans le binaire, mais un appareil non enrôlé n'envoie rien et ne résout même pas le nom d'hôte de Fleet (le worker ne lit qu'un fichier d'état local). L'enrôlement n'a lieu que lorsqu'un opérateur exécute roamswitch fleet enroll. Le service de gestion n'est ni déployé ni publié : il n'y a donc actuellement rien auquel se connecter. Les garanties de conception présentes dans le code sont les suivantes. (a) L'appareil n'accepte que des données de politique signées par l'organisation, vérifiées avec la clé publique de l'organisation épinglée à l'enrôlement ; une politique qui échoue à la vérification n'est pas appliquée. La conception prévoit que la clé de signature de l'organisation soit détenue par le client, non par lafine. (b) Il n'existe aucun canal de commande à distance : côté appareil, aucun point de terminaison, champ ni chemin de code ne permettrait d'ordonner d'isoler, de lever, de collecter ou d'exécuter. (c) Une politique ne peut pas affaiblir le plancher local : désactiver une protection ou entraver la levée / la restauration d'urgence ne peut même pas s'exprimer dans le schéma, et la valeur la plus stricte l'emporte. Une politique ne peut en outre changer la destination du transfert d'événements que vers des hôtes autorisés par le root local (fleet_forwarding_allowed_hosts, vide par défaut). (d) Un appareil n'envoie qu'un résumé d'état minimal – version de l'application, famille d'OS, état d'isolement (normal / isolé), version de politique appliquée, score de santé (0–100) et époque de clé – plus l'ID d'appareil et l'ID d'organisation dans les en-têtes de signature. Il utilise le curl du système (TLS) et uniquement pour un appareil enrôlé.
  • Jumelage avec un Sensor (sensor_pairing.rs) — désactivé par défaut (sensor_pairing_enabled). Il se connecte uniquement, et directement, à l'adresse du Sensor saisie par l'opérateur ; il n'existe aucune destination intégrée. Il prend en charge TLS 1.3 (via le openssl du système, avec épinglage de l'empreinte du certificat), mais un ancien jumelage sans épinglage retombe en clair avec un avertissement. Le Sensor est un produit distinct et n'est pas couvert par les affirmations Zero Telemetry de ce document.
  • Vérification active des vulnérabilités (active_vuln_scan.rs / web_vuln_scan.rs) — désactivée par défaut et soumise à un réglage d'activation. Dans cette édition poste de travail, le chemin se limite à des sondes vers 127.0.0.1 pour les ports en écoute déjà observés sur cette machine (verrouillé par les tests propres aux modules).
  • Le bouton « Rechercher les mises à jour » de l'interface graphique — uniquement lorsque l'utilisateur appuie sur le bouton, et seulement sur les distributions de type apt, l'interface graphique effectue un unique GET de https://lafine.net/apt/dists/stable/main/<arch>/Packages avec le curl du système pour connaître la dernière version (délai de 4 secondes ; URL publique ne contenant aucun secret, sans identifiant ni chaîne de requête ajoutés). Sur les systèmes dnf, zypper et pacman, curl n'est pas utilisé ; c'est le gestionnaire de paquets correspondant qui est interrogé (#5 du 9.3).
  • Vérification des signatures npm — roamswitch audit-npm-signatures <DIR> --confirm ne s'exécute que si le réglage npm_audit_signatures_enabled (false par défaut) est activé et que l'utilisateur ajoute --confirm ; c'est alors npm lui-même qui contacte registry.npmjs.org.

§10

  • Aucun compte — aucune inscription d'utilisateur, aucune connexion, aucune adresse e-mail jamais demandée.
  • La configuration est locale uniquement — stockée en texte brut à ~/.config/roamswitch/config.json et ne quitte jamais la machine. Il n'y a pas de synchronisation cloud.
  • Les résultats de l'évaluation sont locaux uniquement — les rapports de sécurité et les résultats d'audit des journaux sont uniquement affichés à l'écran et, si l'utilisateur le souhaite, exportés vers un fichier local.
  • Les clés de licence sont vérifiées hors ligne — Les jetons de licence Pro/Business contiennent une signature Ed25519 et sont vérifiés avec une clé publique. Aucune requête n'est adressée à un serveur lafine.
  • Aucun rapport de crash — en cas de panique, aucune trace de pile n'est envoyée nulle part.
  • Le programme de mise à jour quotidien n'envoie rien à votre sujet — pas de chaîne de requête, pas de cookies, pas d'identifiant propre à l'appareil ou à l'installation, un User-Agent: RoamSwitch-Updater fixe comme seul en-tête qui nomme le logiciel. Il s'agit d'un téléchargement, pas d'un rapport. Les verdicts Link-guard, les hôtes bloqués et les résultats d'analyse sont écrits uniquement dans la vue d'activité locale/journald, jamais transmis.
  • Tant que le DNS de protection contre les menaces est utilisé, les requêtes DNS parviennent à un résolveur externe. Le DNS de protection contre les menaces (§4.5 ; activé par défaut, réseaux non fiables uniquement) oriente le résolveur DNS de l'OS vers Quad9 (9.9.9.9 ; le 1.1.1.2 de Cloudflare peut aussi être choisi). Pendant ce temps, les requêtes sont envoyées à ce résolveur par l'OS, et non par RoamSwitch, qui n'envoie lui-même rien. Pour l'arrêter, mettez dns_enabled à false.
  • L'historique des notifications est purgé après 7 jours et limité en longueur — l'historique des notifications envoyées est stocké localement, et les entrées de plus de 7 jours sont supprimées à chaque écriture. Un titre est tronqué à 200 caractères et un corps à 2000 caractères (une bulle de notification de bureau affiche au plus 1000 caractères de corps). Cet historique ne quitte pas la machine, sauf si le transfert d'événements (désactivé par défaut) est activé.
  • Le journal d'exécution reste lui aussi local — le journal de l'enregistreur d'exécutions de processus (§5) n'est stocké, par défaut, que sur la machine (/var/lib/roamswitch/exec_log, 0600). Comme il contient des arguments de ligne de commande, il est supprimé automatiquement selon la durée de rétention (14 jours par défaut) et la taille (200 Mo par défaut).

§11

Éditions
ÉditionPourPrixLicence
Édition communautaireUtilisation de Linux à titre personnel et pour les activités internes d'une entreprise (sans limite du nombre d'appareils)GratuitLogiciel gratuit propriétaire (CLUF fourni)
Business (prévu)Gestion de flotte, distribution de politiques, apt interne signé, SLA pour les organisationsPayéLicence commerciale. Même porte binaire + niveau

Dès que le niveau Business sera proposé, la politique prévue est d'accorder gratuitement les fonctions Business sur leurs propres machines Linux aux acheteurs de macOS RoamSwitch Pro Lifetime. Le niveau Business n'est pas proposé aujourd'hui, et il n'existe aucun serveur de gestion.

Distribution et chaîne d'approvisionnement

ChaîneDétail
APTEhttps://lafine.net/apt (stable main). Release signé avec une clé RSA-4096
RPMhttps://lafine.net/rpm (Fedora / openSUSE séparé). repomd.xml signé GPG
AURroamswitch-bin (en préparation. L'AUR n'accepte plus de nouveaux comptes ; pour l'instant, on l'installe donc avec makepkg à partir de https://lafine.net/linux/dl/PKGBUILD ; l'archive tar de lafine.net/linux/dl est vérifiée par sha256 et par une signature détachée (validpgpkeys))
archive tar.tar.gz + .sha256 + .asc (signature détachée par la clé de signature du dépôt) pour x86_64 / aarch64 sur https://lafine.net/linux/dl/. Le dépôt des sources est privé ; c'est donc ici, et non sur GitHub Releases, que se trouve le téléchargement public
  • Épinglé glibc floor — les binaires de version sont construits dans un conteneur Debian 12 (bookworm, glibc 2.36). Cela évite les incompatibilités de versions de symboles sur Debian 12 / Ubuntu 22.04 et versions ultérieures. Une étape CI « vérifier le plancher de la glibc » fait échouer la construction si une dépendance nécessitant plus de 2,36 est introduite.
  • Gestion des clés de signature — la clé privée pour la signature du référentiel apt/rpm existe uniquement dans un secret chiffré GitHub Actions ; seule la clé publique est validée dans le référentiel.

11.3 Le programme de mise à jour quotidien

roamswitch-updater est un binaire autonome exécuté par une minuterie systemd (roamswitch-update.timer : 15 minutes après le démarrage, puis toutes les 24 heures, avec un délai aléatoire pouvant aller jusqu'à 3 heures). Par défaut, c'est le seul composant qui atteint une URL de distribution externe (hors fonctions optionnelles du 9.4). À chaque exécution :

  1. GET https://lafine.net/updates/v1/manifest et sa signature détachée.
  2. Vérifiez la signature Ed25519 du manifeste avec une clé de signature de flux de menaces dédiée, distincte de la clé de mise à jour de l'application Sparkle. Le flux est re-signé quotidiennement par le CI côté serveur, de sorte que la clé privée réside dans un secret CI ; le limiter au flux signifie qu'une fuite permet uniquement à un attaquant de servir une mauvaise liste de blocage (gouffre / avertissement - pas de chemin de code, pas de chemin de mise à jour de l'application). La clé de signature de mise à jour de l'application ne quitte jamais le trousseau macOS. Un manifeste qui n’effectue pas de vérification est supprimé : le programme de mise à jour ne revient jamais aux données non signées.
  3. Si la version du flux de menaces a changé, téléchargez le flux, vérifiez sa signature SHA-256 et Ed25519, et installez-le atomiquement pour le link guard (§4). Les autres données ne sont récupérées que si le manifeste indique une version plus récente (la carte CVE du scan actif et la liste des paquets npm populaires). Les cartes CVE du noyau, des paquets du système et des écosystèmes de langages ne sont récupérées que si environ 30 jours se sont écoulés depuis la dernière mise à jour. La carte CVE d'Ubuntu n'est récupérée que sous une version d'Ubuntu prise en charge ; un appareil qui ne l'a pas encore la récupère lors de la prochaine exécution quotidienne, sans attendre les 30 jours. Tout n'est installé qu'après vérification de la signature.
  4. Lorsque la version du manifeste est plus récente, les règles de détection de la couche applicative Web (§5) sont aussi récupérées. Le SHA-256 et la signature Ed25519 sont vérifiés, et on s'assure que le fichier est lisible comme règles avant de l'installer de façon atomique. Un fichier illisible n'est jamais installé et les règles en vigueur ne sont pas remplacées. Aucune nouvelle destination n'est ajoutée : la récupération s'intègre à celle du manifeste. Dans l'édition Serveur, elle suit cve_kernel_map_updates_enabled : désactivée, il n'y a aucun contact réseau.
  5. Si ClamAV est installé, invoquez freshclam.
  6. Enregistrez la dernière version de l'application pour une notification (le programme de mise à jour never installe l'application ; sous Linux, l'utilisateur exécute apt upgrade).

L'échec de n'importe quelle étape (hors ligne, signature incorrecte, freshclam absent) est enregistré dans lastError de updates.json et le processus quitte toujours avec le code 0, donc une exécution de minuterie ne génère jamais de notification d'échec de systemd. L'ensemble du mécanisme est désactivé par "enabled": false dans updates.json ou par systemctl disable --now roamswitch-update.timer ; le produit s'exécute alors uniquement sur les données regroupées dans le paquet. Le flux de menaces est également fourni à l'intérieur du paquet, de sorte qu'une nouvelle installation dispose d'un flux fonctionnel dès le départ, et apt upgrade l'actualise même avec le programme de mise à jour désactivé.

§12

  • OS : Ubuntu 22.04 / 24.04 et versions ultérieures, Debian 12 et versions ultérieures, Linux Mint / Pop!_OS / elementary / Zorin, famille Fedora / RHEL, openSUSE, famille Arch (le paquet AUR est en préparation ; installation à partir du PKGBUILD), Raspberry Pi OS 64 bits (Bookworm), Ubuntu pour Raspberry Pi
  • Architecture : x86_64 / aarch64
  • Obligatoire : systemd, nftables, iproute2 (ip neigh, le verrouillage ARP/NDP), conntrack et libnotify (notifications du bureau) ; tout sauf systemd est installé automatiquement par les dépendances des paquets .deb / .rpm
  • Facultatif : clamav (analyse des logiciels malveillants), nmcli (détection du chiffrement Wi-Fi), iw (informations sans fil de repli), bluez (garde Bluetooth), systemd-resolved (configuration du DNS de protection contre les menaces), lsof (détection de repli des ports en écoute), nmap (appoint pour les vérifications actives de vulnérabilités), wireguard-tools (le backend WireGuard du tunnel VPN + kill-switch, §4), tailscale (son backend Tailscale, §4), polkit / pkexec (mise à niveau, désinstallation et redémarrage du démon depuis l'interface graphique)
  • Non pris en charge : distributions non-systemd (Alpine / Void / Devuan)
  • Display : 1 280 × 720 ou plus (la fenêtre et la police s'adaptent automatiquement à la taille de l'écran, prenant en charge les petits écrans Raspberry Pi)

Annexe A

CaisseObjectifRéseau
serde / serde_jsonsérialisation de la configuration et de l'IPCaucun
tokioruntime asynchrone (features = ["full"] ; par défaut, seuls les sockets Unix sont utilisés)aucun (pas de TCP par défaut ; le jumelage Sensor optionnel fait exception)
traçage / traçage-abonnéjournalisation (local / journald)aucun
cette erreur / de toute façongestion des erreursaucun
expression régulièreanalyse de chaînes (diagnostics, mise à l'échelle CSS)aucun
URLAnalyse d'URL (heuristique audit_url_safety)aucun (ne récupère pas)
chronomètregestion de la date/heureaucun
ed25519-dalekvérification hors ligne des signatures de licence et des données signées telles que le manifeste du programme de mise à jour et le flux de menaces (également utilisée par l'appairage Sensor, sur activation volontaire)aucun
base64 / sha2jetons de licence, hachages Canaryaucun
bibliothèquechown / kill / appels systèmeaucun
idnadécodage des hôtes IDN/punycode pour la détection d'homographesaucun
nfq (démon uniquement)Liaisons NFQUEUE pour le link guard — pure Rust, AF_NETLINKaucun (canal de verdict, pas de sortie)
ouvertappelle xdg-open pour ouvrir « Ouvrir les notes de version » ainsi que les liens du CLUF et des pages d'assistancele processus lui-même ne communique pas
GTK3 / ksni (application uniquement)Interface graphique et plateauaucun
serde_yamllecture de /etc/roamswitch/guard.yaml (core)aucun
tomlanalyse de Cargo.lock / poetry.lock (analyse CVE des paquets de langages, core)aucun
md-5hachage des empreintes JA3 du Link Guard (sans usage d'authentification ni d'intégrité, core)aucun
bs58contrôle Base58 des clés privées de crypto-actifs (détection de secrets, core)aucun
randgénération de la clé d'appareil de l'appairage Sensor en opt-in (core)aucun
tonic / tonic-prost / prost / tower / hyper-utilgRPC vers le capteur Tetragon ; socket de domaine Unix uniquement, sans TLS (core)aucun (socket Unix uniquement)
flate2décompression gzip de la carte CVE des paquets de la famille RHEL (updater uniquement ; Rust pur)aucun (décompression uniquement)
inotifysurveillance des accès aux leurres honeytoken (roamswitch-honeytokens)aucun
ayachargeur du boot gate XDP expérimental (roamswitch-xdp-gate, référencé par le daemon)aucun (bpf() / netlink uniquement)
urlencoding / gdk / gdk-pixbuf / glib / pangodécodage d'URL dans la GUI et crates GTK3 associés (app uniquement)aucun
ureq + rustls (roamswitch-updater uniquement)les requêtes HTTPS GET du programme de mise à jour quotidien (manifeste, signature et données modifiées)la seule pile HTTP/TLS dans l'arborescence ; confiné au binaire du programme de mise à jour
Point clé

Aucun client HTTP capable d'accéder à Internet, aucune pile TLS ni aucun SDK de télémétrie n'apparaît dans roamswitch-core, roamswitch-daemon, roamswitch-app, roamswitch-mcp ou roamswitchkit. Le couple tonic / hyper de roamswitch-core sert au gRPC avec Tetragon sur un socket de domaine Unix uniquement, n'a pas de TLS et n'accède jamais au réseau. ureq + rustls ne sont présents que dans roamswitch-updater (scripts/audit_no_network.sh l'applique dans CI). Il n'existe aucun SDK de télémétrie nulle part.

Annexe B

table inet roamswitch {                 # Pare-feu de profil (open / balanced / lockdown)
    chain input {
        type filter hook input priority 0; policy <accept|drop>;
        ct state established,related accept
        iif "lo" accept
        ip protocol icmp accept            # open est en policy accept sans règle ; balanced / lockdown sont en policy drop (ICMP / ICMPv6 ne sont autorisés qu'en balanced)
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
}

table inet roamswitch {                 # Pendant l'Air-Gap (isolement), la même table est recréée : policy drop en input et en output, seul lo est autorisé (les connexions établies ne passent pas non plus)
    chain input  { type filter hook input  priority -100; policy drop; iif "lo" accept }
    chain output { type filter hook output priority -100; policy drop; oif "lo" accept }
}

table inet roamswitch_linkguard {        # §4.5 — indépendante de la table de profil
    chain output {
        type filter hook output priority 0; policy accept;
        udp dport 443 reject with icmpx type port-unreachable   # uniquement lorsque block_quic est activé (désactivé par défaut)
        meta l4proto udp udp dport 53      queue num 92 bypass
        meta l4proto tcp tcp dport 53      queue num 92 bypass
        meta l4proto tcp tcp dport { 80, 443 } ct state new,established ct original packets 1-14 queue num 92 bypass   # seuls les 14 premiers paquets de chaque connexion sont envoyés à l'inspection
    }
}

table inet roamswitch_dev_guard {        # §4 Garde d'anomalie de port / isolation manuelle de port, table séparée
    chain input {
        type filter hook input priority -10; policy accept;
        iif != "lo" tcp dport { … } drop
    }
}

table inet roamswitch_portscan {        # détection et blocage des scans de ports (activé par défaut)
    chain block  { type filter hook input priority -200; policy accept; ip saddr @blocked_ips ct state new log … drop }
    chain detect { type filter hook input priority -100; policy accept; iifname != "lo" ct state new tcp flags syn log … }
}

table inet roamswitch_egress_block {     # liste de blocage sortante des IP malveillantes (activée par défaut)
    chain output  { type filter hook output  priority -150; policy accept; ip daddr { … } log … drop }
    chain forward { type filter hook forward priority -150; policy accept; ip daddr { … } log … drop }
}

table inet roamswitch_docker_guard {     # contre-mesure au contournement du pare-feu par les ports publiés de Docker (uniquement avec le backend nftables natif de Docker)
    chain forward { type filter hook forward priority -10; policy accept; … iifname { … } drop }
}

table inet roamswitch_isolate {          # isolement par processus après une détection eBPF (par cgroup / UID)
    chain process_block { type filter hook output priority -100; policy accept; … drop }
}

table inet roamswitch_vpn {              # §4 Coupe-circuit du backend WireGuard
    chain output {
        type filter hook output priority -150; policy drop;
        oifname "lo" accept
        oifname "roamswitch" accept
        meta l4proto { icmp, ipv6-icmp } accept
        udp sport 68 udp dport 67 accept          # DHCP
        ip  daddr { <endpoint v4> } udp dport <port> accept
        ip6 daddr { <endpoint v6> } udp dport <port> accept
        ct state established,related accept
    }
    chain input  { type filter hook input priority -150; policy drop; … }  # symétrique
}

table inet roamswitch_ts {               # §4 Coupe-circuit du backend Tailscale (plus permissif)
    chain output {
        type filter hook output priority -150; policy drop;
        oifname "lo" accept
        oifname "tailscale0" accept
        meta l4proto { icmp, ipv6-icmp } accept
        udp sport 68 udp dport 67 accept          # DHCP (le côté v6 est également symétrique)
        ip  daddr 100.64.0.0/10 accept            # tailnet CGNAT
        ip6 daddr fd7a:115c:a1e0::/48 accept
        ip  daddr 100.100.100.100 udp dport 53 accept   # MagicDNS
        ip  daddr 100.100.100.100 tcp dport 53 accept
        udp dport 3478 accept                     # STUN
        udp dport 41641 accept                    # Connexion directe WireGuard
        tcp dport 443 accept                      # Repli DERP
        ct state established,related accept
    }
    chain input  { type filter hook input priority -150; policy drop; … }  # symétrique
}

L'épinglage ARP/NDP de la passerelle / routeur IPv6 / résolveur DNS (§4) se fait dans la table voisine du noyau (ip neigh replace … nud permanent), pas dans les nftables. Les entrées épinglées sont enregistrées dans /var/lib/roamswitch/arp_lock.json.

Annexe C

Chaque affirmation contenue dans ce document peut être vérifiée sur votre propre machine. Les commandes ci-dessous s'exécutent sur un hôte sur lequel le package roamswitch est installé.

C.1 Vérification du trafic sortant

Les binaires démon/application/MCP ne lient pas de bibliothèque HTTP/TLS :

for b in roamswitch-daemon roamswitch-app roamswitch-mcp; do
  ldd "$(command -v $b)" | grep -iE 'ssl|crypto|nghttp|curl' && echo "  ^ in $b"
done
# → Aucune sortie. La pile HTTP/TLS n'est liée statiquement qu'à roamswitch-updater.

Le démon en cours d'exécution ne contient aucune socket Internet :

sudo lsof -p "$(pgrep -x roamswitch-daem)" -a -i
# → Aucune sortie = zéro connexion Internet TCP/UDP
sudo lsof -p "$(pgrep -x roamswitch-daem)" -a -U | grep roamswitch
# → Uniquement /run/roamswitch/roamswitch.sock et events.sock (réception des événements de détection eBPF) (le link guard ajoute un socket AF_NETLINK,
#   mais il s'agit d'un canal noyau, pas d'un socket internet)

La seule sortie attribuable à RoamSwitch est la mise à jour quotidienne :

watch -n 5 'sudo ss -tupn | grep -E "roamswitch-(daem|app|mcp)" || echo "(none)"'
# → Toujours « (none) ». Aucun de ces trois processus n'ouvre jamais de socket internet.
systemctl list-timers roamswitch-update.timer
sudo systemctl start roamswitch-update.service
journalctl -u roamswitch-update.service -n 20 --no-pager
# → Il se contente de faire des HTTPS GET vers lafine.net/updates/v1 (manifeste, signature et données modifiées), puis se termine. Si ClamAV est installé, freshclam se connecte aussi aux miroirs de ClamAV.

Désactivez-le et confirmez le silence total :

sudo systemctl disable --now roamswitch-update.timer
# ou modifier /var/lib/roamswitch/updates.json en mettant "enabled": false
sudo strace -f -e trace=network -p "$(pgrep -x roamswitch-daem)" 2>&1 | grep -i 'connect('
# → Se connecte uniquement à AF_UNIX / AF_NETLINK. AF_INET / AF_INET6 n'apparaissent jamais.

C.2 Vérification reproductible de la défense (suite de tests Docker)

Les divers mécanismes de défense de RoamSwitch (Air-Gap, auto-réparation après effacement forcé du pare-feu, détection de ransomware, /tmp noexec, Yama LSM, détection d'homographes, etc.) peuvent être reproduits sur votre propre machine avec la suite de tests Docker publiée dans le dépôt d'assistance officiel (roamswitch-support). La suite ne manipule que son propre espace de noms réseau et les fichiers à l'intérieur du conteneur ; le réseau et les fichiers de l'hôte ne sont donc pas affectés. Elle s'exécute toutefois avec --privileged : les paramètres partagés par l'ensemble du noyau, comme ptrace_scope de Yama et suid_dumpable, sont donc aussi modifiés sur l'hôte, et supprimer le conteneur ne les rétablit pas. Exécutez-la dans un environnement jetable.

# Récupérer et reproduire la suite de vérification (vérification automatisée dans un conteneur isolé, avec le paquet de distribution officiel)
git clone https://github.com/lafine1211/roamswitch-support.git
cd roamswitch-support/test/docker
docker build -t roamswitch-test .
docker run --rm --privileged roamswitch-test

Le conteneur de test récupère automatiquement le paquet de distribution officiel du dépôt APT (lafine.net/apt), exécute les vérifications dans un espace de noms réseau et un espace de noms de montage isolés, puis rend son verdict. La suite publique se compose de pentest.sh (les sept scénarios PENT-1 à 7) et de selftest.sh (auto-tests destructifs du démon : Air-Gap, auto-réparation, canaris, détection de rafales, Vault de quarantaine, etc.). Les ID des tableaux ci-dessous sont des numéros d'ordre des enregistrements de vérification et ne correspondent pas un à un aux contrôles des scripts. BadUSB (SP-6), les modes du Link Guard (famille SP-7), le verrouillage ARP et les kill-switches VPN (SP-8 à 10), les vérifications individuelles du renforcement du noyau (famille SP-11) ainsi que PENT-8 et 9 ne font pas partie de la suite publique ; ce sont des éléments confirmés par d'autres procédures (machine réelle, espaces de noms réseau indépendants, injection manuelle d'événements).

(1) Défense de réseau autonome et auto-réparation de l'espace aérien

pièce d'identitéScénarioMéthode de vérificationCritère de réussite
SP-3Activation de l'Air-Gap → chute de sortieEnvoyer enable_air_gap IPCpolicy drop appliqué au crochet de sortie ; tout le trafic sortant bloqué
SP-3bPurge forcée et auto-réparation du pare-feuPurger les règles via nft delete table inet roamswitchL'auto-guérison Anti-Clobber restaure les règles de chute dans les 5 secondes
SP-3cFermeture en cas d'échec lors du redémarrage du démonArrêter le démon pendant l'Air-Gap (selftest.sh utilise kill, c'est-à-dire SIGTERM) puis le redémarrerLa politique de suppression de sortie persiste de manière transparente lors du redémarrage
SP-3dRécupération d'entrefer autoriséeEnvoyer un disable_air_gap IPC autoriséPolitique de suppression levée, tous les fichiers de marqueurs supprimés, sortie restaurée
SP-8Épinglage préventif ARP/NDPActiver dans un environnement de passerelle Netns isolé → envoyer un ARP gratuit usurpéL'entrée voisine de la passerelle reste PERMANENT ; ARP usurpé ignoré
SP-9Kill-switch VPN (WireGuard)Serveur Live WireGuard dans des réseaux séparés, importez .conf → vpn_upLa poignée de main se termine jusqu'à inet roamswitch_vpn ; zéro fuite à la mort du tunnel
SP-10Kill-switch VPN (Tailscale)Stub tailscale CLI + réseaux séparés, tailscale0 IFTexte en clair hors tunnel TCP/80 et DNS local bloqués ; DERP et MagicDNS autorisés

(2) Durcissement du noyau et du support

pièce d'identitéScénarioMéthode de vérificationCritère de réussite
SP-11aRenforcement du système réseauExécutez apply_kernel_sysctl_hardening IPCrp_filter=1, tcp_syncookies=1, accept_redirects=0 atteignent les valeurs cibles
SP-11bProtection de la mémoire et Yama LSMVérifiez ptrace_scope et suid_dumpablekernel.yama.ptrace_scope=1 (bloque la surveillance), fs.suid_dumpable=0 (pas de core dump)
SP-11c/tmp & /dev/shm défense noexecExécutez apply_mount_hardening IPC/tmp et /dev/shm ont remonté noexec,nosuid,nodev ; exécution refusée
SP-11dExécution légitime des utilisateurs préservéeExécuter des scripts dans le répertoire personnel standard (~/)Fonctionne sans entrave sans interrompre les flux de travail légitimes des développeurs/administrateurs

(3) Défense multicouche contre les logiciels malveillants et les ransomwares et Canaries

pièce d'identitéScénarioMéthode de vérificationCritère de réussite
SP-1Détection de sabotage CanaryTronquer ou renommer le fichier Canary leurre (.xlsx) en ~/Documents/Sabotage détecté en quelques secondes, déclenchant un Air-Gap immédiat
SP-2Détection des rafales de chiffrement en masse des ransomwaresUn seul processus génère rapidement plusieurs fichiers à haute entropieProcessus du travailleur incriminé gelé avec SIGSTOP ; Air-Gap déclenché
SP-2bPas de faux positif sur shredExécutez shred -n 3 -u sur les fichiers de testLa correspondance de la liste verte empêche la détection ou le gel des faux positifs
SP-2cLes processus critiques ne sont jamais gelésValider la logique de la cible gelée lors de la détection de rafalesLes démons critiques (dockerd, systemd, containerd) ne sont jamais gelés
SP-4La chaîne de test EICAR est à notification uniquementÉcrivez la chaîne de test EICAR dans ~/Downloads/Aucune notification, uniquement une entrée dans l'historique des notifications ; le fichier n'est jamais automatiquement mis en quarantaine
SP-4bConfirmation de la véritable signature du malware en premierÉcrivez une vraie signature en dehors des dossiers surveillésOuvre une boîte de dialogue d'approbation modale ; mis en quarantaine uniquement après confirmation de l'utilisateur
SP-4cRenforcement des autorisations de Quarantine VaultInspecter les autorisations du répertoire du coffre-fort et des exemples de fichiersRépertoire Vault défini sur 0700, échantillons mis en quarantaine définis sur 0400
SP-4dRestauration privilégiée du fichier original en toute sécurité IPCEnvoyer restore_quarantine_file IPCFichier restauré au chemin d'origine avec les autorisations utilisateur (0644) et ajouté aux exclusions

(4) Protection de lien passif et défense contre le phishing

pièce d'identitéScénarioMéthode de vérificationCritère de réussite
SP-7mode = block application stricteConnectez-vous aux hôtes répertoriés dans le flux ou blocklistExtraAbandonné au niveau de la couche TLS (analyse SNI) ; les hôtes sécurisés non répertoriés atteignent HTTP 200
SP-7bmode = warn maintien en cas de fermetureConnectez-vous à l'hôte d'avertissement sans réponse de l'utilisateur (délai d'expiration de 8 s)Supprime les paquets après expiration du délai (fail-closed) ; autorisé uniquement sur approbation explicite
SP-7cmode = off démontage completDésactiver la protection des liensLa table inet roamswitch_linkguard est complètement désinstallée

(5) Atténuation BadUSB et contrôle des appareils

pièce d'identitéScénarioMéthode de vérificationCritère de réussite
SP-6Maintien des droits acquis du clavier connectéVérifiez les claviers connectés au démarrage du démonLes appareils existants bénéficient de droits acquis ; seuls les hot-plugs ultérieurs ont été saisis

(6) Éléments d'auto-vérification simulant des attaques (PENT-1 à 7 dans la suite Docker publique, PENT-8 et 9 issus d'une vérification manuelle distincte)

pièce d'identitéVecteur d'attaqueDétail de la simulationCritères de défense/détection
PENT-1Exfiltration C2Tentative de sortie HTTP/TCP vers C2 sous Air-GapTous les paquets abandonnés sur netfilter ; zéro fuite
PENT-1bSuppression forcée du micrologicielPurger les règles avec nft delete table inet roamswitchL'auto-guérison Anti-Clobber restaure les règles en quelques secondes
PENT-1cRestauration sécurisée de l'espace d'airProblème authentifié disable_air_gap IPCRègles de suppression supprimées ; marqueurs supprimés ; connectivité restaurée
PENT-2/tmp Porte dérobéeExécuter le script de porte dérobée supprimé dans /tmpRefusé par la protection de monture noexec (Permission denied)
PENT-2b/dev/shm Mémoire partagéeExécuter la charge utile binaire placée dans /dev/shmBloqué par la défense de monture noexec
PENT-2cPréservation de l'espace utilisateurExécuter le script dans le répertoire utilisateur légitime (/home/tester/)Exécution sans entrave ; zéro interruption du flux de travail
PENT-3Surveillance de la mémoire ptracePTRACE_ATTACH à cibler pour extraire les secrets/jetonsRefusé par Yama LSM kernel.yama.ptrace_scope=1
PENT-3bGrattage secret du Core DumpDéclencher un crash du processus pour obtenir un vidage mémoireEmpêché par fs.suid_dumpable=0
PENT-4Falsification des CanariesMettre à zéro le fichier leurre Canary dans ~/Documents/Détection en temps réel des anomalies de taille et des changements de hachage
PENT-4bEssuyage en vrac CanarySuppression massive des fichiers leurres CanaryDisparition de fichiers détectée immédiatement, déclenchant un incident
PENT-5Usurpation d'adresse IPTransmettre des paquets avec une adresse IP source usurpéeSupprimé par rp_filter=1 (filtre de chemin inverse)
PENT-5bSYN Flood DoSInonder les connexions TCP semi-ouvertesTable d'état protégée par tcp_syncookies=1
PENT-5cDétournement de route ICMPInjecter de faux paquets de redirection ICMPIgnoré en raison de accept_redirects=0
PENT-6Hameçonnage par homographe cyrilliqueAccédez à gооgle.com (homographe cyrillique)Identifié par LinkGuard, noté 40 (DANGEREUX / blocage)
PENT-6bTrafic IP brut et texte brutConnexion sortante directe à l'IP bruteSignalé avec précision comme facteur de risque pour la sécurité
PENT-7Fuite de clé API OpenAIExposer le jeton sk-proj-... dans le code ou les journauxDétecté via l'analyse d'entropie de Shannon et masqué
PENT-7bFuite du jeton GitHubExposer le jeton personnel ghp_... dans le codeCorrespondance par modèle de jeton et vérification d'entropie
PENT-8Préemption LPE (Frag Gap) du noyauunshare -U -r non privilégié (tentative CVE-2026-53362)Dans la mesure de la version v1.1.0, rejeté immédiatement par user.max_user_namespaces = 0 avec Operation not permitted. L'édition Client actuelle n'effectue pas ce refus par défaut (auto_enable_userns_restriction dans guard.yaml est désactivé par défaut ; la Server Edition l'applique en permanence)
PENT-9Capteur de menace eBPF et gel des processusAlerte critique/urgence injectée via la prise Falco eBPFProcessus gelé via SIGSTOP en quelques millisecondes (état : Ts ); Air-Gap déployé

Nature des vérifications — Dans le tableau ci-dessus, PENT-2/2b (noexec), PENT-3/3b (Yama, core dumps) et PENT-5/5b/5c (rp_filter, SYN cookies, redirections ICMP) confirment qu'une fonction standard du noyau ou de mount configurée par RoamSwitch est bien active. PENT-8 (interdiction des espaces de noms utilisateur) est du même type, mais il s'agit d'un enregistrement de la version v1.1.0 et l'édition Client actuelle ne l'applique pas par défaut. Les points qui attaquent la logique propre de détection et de confinement de RoamSwitch sont PENT-1/1b/1c (Air-Gap et auto-réparation), PENT-4/4b (canaris), PENT-6/6b (Link Guard), PENT-7/7b (détection de secrets) et PENT-9 (gel et confinement après une détection eBPF). PENT-2c confirme que le travail ordinaire n'est pas perturbé.

(7) Résumé de l'auto-vérification (autonome sans Falco [Standalone] et avec Falco [intégration eBPF])

Résumé des résultats de l'auto-vérification complète simulant des attaques, exécutée dans un environnement Docker isolé (Cible : Ubuntu 24.04 avec RoamSwitch Client Edition ; Attaquant : Nmap / curl / hping3, etc.). Le tableau ci-dessous reprend la mesure de Client v1.1.0 du 2026-09-06 ; le 2026-09-25, une version corrigée construite à partir des sources 1.10 a réussi 11 éléments sur 11 de la suite cliente (il ne s'agit pas d'une version publique ; voir audit/RESULTS-PENTEST-2026-09-25.ja.md dans le dépôt public).

Catégorie de vérificationSans Falco (autonome durci)Avec Falco (eBPF amélioré)Verdict de la Défense
Résistance au scan du port externe (Nmap SYN)PASS (100 % filtré / DROP dans Lockdown)PASS (100 % filtré / DROP dans Lockdown)Furtivité externe totale
Coupure d'accès direct externe (TCP/80, 3306)PASS (expiration du délai / réponse nulle)PASS (expiration du délai / réponse nulle)Exposition entrante nulle
Atténuation du noyau LPE (Frag Gap)PASS (mesure de la v1.1.0 ; unshare bloqué dans un environnement où user.max_user_namespaces = 0 est défini. Désactivé par défaut dans l'édition Client actuelle)PASS (alerte eBPF + SIGSTOP gel + Air-Gap)Défense en profondeur vérifiée
Détection de port non autorisé (anomalie)PASS (liaison 0.0.0.0 signalée à risque ÉLEVÉ)PASS (liaison 0.0.0.0 signalée à risque ÉLEVÉ)100 % détecté et guidé
Prévention des ballonnements de journaux et des surcharges du processeurEmpreinte de journal zéro (désinstallé)PASS (réduction du bruit de 95 % / tampon de 2 Mo / plafond de 50 Mo)Bureau optimisé

C.3 Limites connues

LimitationImpactAtténuation
Sur certains noyaux, FAN_MARK_MOUNT sur tmpfs (/tmp) ne délivre aucun événementles écritures de fichiers sous /tmp ne sont pas surveillées par fanotify/tmp est éphémère et n'est pas un dossier utilisateur surveillé ; le ransomware cible ~/. Les échecs de marquage sont consignés explicitement
Pas de livraison fanotify sur les conteneurs overlayfs / tmpfsla détection d'explosion d'entropie dans le conteneur ne peut pas être validéevalidé sur du matériel réel (ext4) ; le harnais saute automatiquement via une sonde
Les opérations nftables de RoamSwitch partagent l'espace netfilter avec iptables-nft de DockerLes opérations des règles RoamSwitch peuvent interférer avec les règles NAT de Dockerisolé dans des tables dédiées (inet roamswitch et les autres tables roamswitch_*), mais les opérations globales telles que nft flush ruleset doivent être évitées
Le Link Guard voit le DoH d’un navigateur uniquement via TLS SNI ; Encrypted Client Hello (ECH) cache cela aussiune fois qu'ECH est largement déployé, une connexion utilisant ECH à un site prenant en charge ECH n'est pas identifiable par son nom d'hôtela liste de blocage locale et les heuristiques agissent toujours sur DNS/HTTP et TLS non-ECH ; La protection contre les menaces DNS (§4.5, activée par défaut) couvre le chemin du résolveur
La fraîcheur du programme de mise à jour quotidien dépend de la cadence de publication lorsque enabled vaut false dans updates.jsonavec les mises à jour désactivées, le flux est aussi actuel que le dernier apt upgradeles détections les plus graves (homographe de marque) ne nécessitent aucune alimentation ; réinscrivez-vous ou activez le DNS de protection contre les menaces pour une couverture toujours à jour
Ce document reflète l'implémentation telle qu'elle est à la version RoamSwitch for Linux v1.11.1 (durcissement LPE du noyau, protection runtime eBPF, gel SIGSTOP des processus attaquants à la milliseconde, optimisation à faible surcharge de Falco, mise en quarantaine Vault de surveillance en temps réel et restauration IPC privilégiée à l'original, auto-réparation Air-Gap, détection de tunnellisation DNS, capture de preuves forensiques, honeytokens, suivi du pass_age de la vérification des vulnérabilités, cartographie NIST CSF/CIS Controls, réévaluation des diagnostics, audit de la configuration MCP). Consultez le CHANGELOG de roamswitch-support pour l'historique détaillé des modifications. Pour les informations les plus récentes, consultez roamswitch.com/linux.