Lafine Systems Design · Technical document

RoamSwitch for Linux
Architecture & Security Whitepaper

A technical document explaining what RoamSwitch for Linux does at its privilege boundary. It avoids marketing language; the claims can be checked against the actual behavior of the distributed package and the self-check in the appendix.

Version v1.0 Applies to RoamSwitch for Linux 1.0 Requires systemd + nftables Issued 2026-09-02

§1Overview

RoamSwitch for Linux is a resident network-security and system-diagnostics application that brings the zero-trust network defense model of macOS RoamSwitch to Linux (systemd + nftables environments).

It identifies the connected Wi-Fi / wired network by its gateway MAC address and autonomously switches the active nftables firewall profile. It adds behavioral ransomware detection (fanotify + Shannon entropy + canaries), BadUSB mitigation, a 20-item security health assessment, and a built-in Model Context Protocol (MCP) server.

This document describes the architecture, threat model, and design of each defense mechanism, and how the principle of “fully local processing, zero data sent off the machine” is guaranteed at the code level. It avoids marketing language; the claims can be checked against the actual behavior of the distributed package (binary) and the self-check in the appendix. RoamSwitch for Linux is proprietary freeware and its source code is not published (§11).

Design principles
PrincipleDetail
Zero telemetryThe product code never contacts an external server for any purpose. No telemetry, analytics, crash reporting, usage metrics, remote config, auto-update download, or license-activation server.
Fully local processingEvery assessment, detection, and audit runs to completion locally. URL safety analysis and secret scanning evaluate their input heuristically without transmitting it.
Least privilegePrivileged operations are confined to a root daemon; the UI, CLI, and MCP request them over a Unix-domain-socket IPC. No TCP/UDP sockets anywhere.
Fail closedAir-Gap (emergency isolation) survives a daemon restart and is re-asserted within seconds if its rules are externally deleted.
Offline licensingPro / Business licenses are verified purely by Ed25519 signature check. No call to an activation server occurs.

§2Threat model

RoamSwitch for Linux primarily targets the laptop / mobile workstation that roams between untrusted networks.

Attackers in scope

  • An attacker on the same LAN — port scans on shared Wi-Fi, ARP spoofing (MitM), unauthorized access to a dev server or local LLM API accidentally bound to 0.0.0.0.
  • Malicious USB devices (BadUSB) — impersonating a keyboard and injecting keystrokes before the screen locks.
  • Ransomware / fileless malware — entering via a downloaded file and bulk-encrypting the user’s documents.
  • DNS-based C2 and phishing — malware using name resolution to reach a C2 server or phishing site.

Out of scope

  • An attacker who already holds root; kernel- or firmware-level rootkits.
  • Attacks that physically remove the disk (the domain of full-disk encryption; RoamSwitch diagnoses whether encryption is present but does not encrypt).
  • AirDrop-style proximity wireless sharing control (no OS-native mechanism on Linux; not supported).

§3Components and trust boundaries

RoamSwitch for Linux is implemented in Rust across six crates. All communication with the daemon goes through /run/roamswitch/roamswitch.sock (a Unix domain socket). There is no listening TCP/UDP socket in any component. The only thing the app binds is a single Unix socket to prevent a second instance.

# User space (unprivileged)
roamswitch-app      GTK3 + AppIndicator tray
roamswitch (CLI)    thin shell front-end
roamswitch-mcp      stdio JSON-RPC (spawned by the AI client)
      │
      │  AF_UNIX  /run/roamswitch/roamswitch.sock  (local IPC only)
      ▼
roamswitch-daemon   root / systemd Type=notify + WatchdogSec=30
  · nftables profile control (inet roamswitch table)
  · sentinel loop (network detection, profile application)
  · fanotify guard / canary engine / quarantine vault
  · udev USB monitoring (BadUSB) / Ed25519 license verification

roamswitch-core     pure-logic library (shared by all crates)
roamswitchkit       MCP client SDK
Privilege separation
ComponentPrivilegeRole
roamswitch-daemonroot (systemd)All privileged operations. As the single writer, controls nftables, DNS, and systemd units.
roamswitch-applogin userGTK3 GUI + tray. Configuration editing and visualization only; privileged actions go through IPC.
roamswitch (CLI)login userThin shell front-end using the same IPC socket.
roamswitch-mcpspawned by the AI clientstdio JSON-RPC. Provides read-only diagnostic information.

User configuration is stored as plaintext JSON at ~/.config/roamswitch/config.json. The daemon reads /home/*/.config/roamswitch/config.json with root privileges (it runs with HOME=/root). Configuration never leaves the machine; there is no account registration and no cloud sync.

§4Autonomous network defense

The sentinel loop (5-second cycle by default) reads the default gateway’s MAC from /proc/net/arp and ip neigh and matches it against the registered MACs in config.json. MAC is used rather than SSID because an SSID is trivially spoofed.

Profiles
ProfileSituationnftables behavior
open (trusted)A registered home or corporate networkpolicy accept. Local traffic allowed.
balancedUnregistered but configured as relatively safeDefault-deny inbound. Only established connections and lo allowed; exposed-port exposure monitored.
lockdown (away protection)Public Wi-Fi / unregistered networkAll inbound packets stealth-dropped. Sharing services auto-stopped (opt-in).

All rules are isolated in a dedicated nftables table named inet roamswitch and never collide with other firewall configuration (ufw / firewalld). Manual-override expiry (1 hour / 2 hours / 4 hours / until midnight / until cleared / until next disconnect) is enforced by the daemon as the single writer. The GUI never writes an expiry; it re-reads the config.json the daemon rewrote on the next tick and reflects it in the UI (avoiding a conflict).

Air-Gap (emergency network isolation)

A total-isolation mode that sets policy drop (priority -100) on both the input and output hooks of the inet roamswitch table, permitting only lo.

  • Fail closed — on daemon restart, detecting the marker file (/run/roamswitch/airgap.active) re-asserts isolation.
  • Self-healing — if an external process deletes the rules with nft delete table, the sentinel loop re-applies within seconds.
  • Automatic expiry — 600 seconds after activation (identical to the macOS maxAirGapDuration) the isolation lifts automatically, preventing a permanent loss of connectivity from a misclick.
table inet roamswitch {
    chain input {
        type filter hook input priority -100; policy <accept|drop>;
        iif "lo" accept
        ct state established,related accept
        # balanced/lockdown: drop everything else
    }
    chain output {
        type filter hook output priority -100; policy <accept|drop>;
        # policy drop only under Air-Gap; only lo and ct established allowed
    }
}

§5Layered malware / ransomware defense

Shannon-entropy burst detection

Write events are tracked per PID. If a single process creates 6 or more distinct files with entropy ≥ 7.85 within 3 seconds, it is treated as ransomware bulk-encryption: the process is frozen with SIGSTOP and Air-Gap is triggered.

  • False-positive mitigation — roughly 60 tools that legitimately write high-entropy data (shred, gpg, ffmpeg, tar, borg, restic, dockerd, qemu, …) are allow-listed. Repeated overwrites of the same file (e.g. shred’s three passes) count as one event.
  • Critical-process protection — PIDs are checked against a hard-coded NEVER_FREEZE list (systemd, dockerd, NetworkManager, sshd, gnome-shell, …) and are never frozen, preventing a machine-wide hang.

Canaries (decoy files)

Decoy files are placed in the user’s Documents, Desktop, Downloads, and Pictures folders and a dedicated directory, and compared against a SHA-256 baseline on a 3-second cycle. Any rename, deletion, truncation, or content change triggers Air-Gap immediately. Because the daemon runs as root, it enumerates /home/* and deploys canaries into every real user’s directories.

fanotify monitoring · Quarantine Vault · ClamAV

  • The daemon monitors /home, /var/tmp, /dev/shm at the mount level and / at the filesystem level via FAN_CLASS_CONTENT (fallback FAN_CLASS_NOTIF).
  • Detected malware is relocated to ~/.local/share/roamswitch/quarantine/. The vault is 0700; each sample is 0400 (read-only, non-executable). On restore, the file returns to 0644 and is chowned to the destination directory’s owner.
  • When ClamAV is present, newly downloaded executables (.sh, .deb, .elf, …) and dangerous AI models (.pkl, .pt) are scanned against the local DB. ClamAV is an optional dependency; the other defenses work without it.

§6BadUSB mitigation

udev USB events are monitored and keyboard-class (HID) devices are tracked.

  • On by default. The first-run wizard adds the keyboards connected at that moment to a whitelist.
  • Thereafter, an unregistered keyboard present at daemon startup is treated as suspect (a device is not trusted unconditionally just because it was present on first connection).
  • When an unregistered keyboard connects, a desktop notification is shown and the warning persists until the user explicitly authorizes it.

§7Security health assessment (20 items)

The LinuxHealthChecker in roamswitch-core evaluates 20 items and produces a 0–100 score, an A–F grade, and per-item advice. From the GTK dashboard, the CLI (roamswitch status), and MCP (get_security_report), the title, detail, and recommendation of all 20 items appear in the user’s language (10 languages).

Assessment categories
CategoryExample items
Disk / bootLUKS / dm-crypt encryption, UEFI Secure Boot
Access controlLSM (AppArmor / SELinux), SSH / sudo config audit
Kernel hardeningsysctl hardening, core-dump control, noexec on /tmp and /dev/shm
Updatesautomatic security update configuration
NetworkARP spoofing monitoring, exposed-port audit, Wi-Fi encryption strength
MalwareClamAV / fanotify / entropy monitoring / noexec
BrowserFirefox / Chrome / Chromium Safe Browsing settings
DNS / USBthreat-protection DNS (Pro), zero-trust state of the USB bus

Every check is based on reading local files and command output (getenforce, mokutil, resolvectl, ss, …). There is no external query of any kind.

§8MCP server security model

roamswitch-mcp speaks JSON-RPC over stdio and is spawned by AI clients such as Claude Desktop, Cursor, and Antigravity. Every tool is read-only and fully local.

Exposed tools
ToolPurpose
get_security_report20-item assessment, score, recommendations
get_exposed_portsexposed ports and their firewall block status
get_guard_statuscurrent protection level and defense-module state
audit_url_safetyheuristic URL safety analysis (does not fetch the target)
audit_secretsdetection of API keys / secrets in text (does not transmit the text)
audit_security_logsaggregation of local journald / auth logs
get_quarantine_status / get_canary_statusquarantine and canary state
get_app_helpsearch of the built-in knowledge base

The MCP server uses no network at all; it either connects to the daemon’s Unix socket via roamswitchkit or calls roamswitch-core logic directly.

§9Data handling and Zero Telemetry

lafine systems design audited the entire RoamSwitch for Linux source (v1.0.0, six crates) for outbound communication. RoamSwitch for Linux is proprietary freeware and its source is not published, so the dependency-tree and source inspections below were performed by lafine; the results are in 9.2. The steps in Appendix A are reproducible from the distributed package alone.

Audit method

  1. Dependency tree inspection — the full Cargo.lock was searched for known HTTP clients (reqwest, hyper, ureq, curl, …), TLS stacks (rustls, native-tls, openssl), and telemetry / analytics / crash-reporting SDKs.
  2. Source code inspection — every .rs file was searched for TcpStream, TcpListener, UdpSocket, to_socket_addrs, lookup_host, and HTTP URL literals.
  3. Subprocess enumeration — every Command::new(...) call site was reviewed.
Audit results
Audit itemResult
HTTP client libraryNot present in the dependency tree
TLS stackNot present
Telemetry / analytics / crash-reporting SDKNot present
TcpStream / TcpListener / UdpSocketNot a single occurrence in the source
Socket API in useAF_UNIX (local IPC) only
License activationOffline Ed25519 signature verification. No activation-server traffic
Auto-updateReads local apt-cache policy output only. Performs no network fetch
Conclusion

The RoamSwitch for Linux product code never initiates communication with an external server for any purpose.

Complete list of externally-facing operations

For transparency, every operation that could cause a packet to leave the machine even indirectly. None is RoamSwitch itself sending an HTTP request; each is an explicit user action or a configuration change to an existing OS mechanism.

#OperationTriggerWhat is sent
1ping -c1 -W1 <gw>to refresh the ARP cache during ARP-spoofing detection; destination is the LAN default gateway onlyICMP echo request only. No DNS resolution, no payload
2launching the system browser (xdg-open)only when the user clicks a button such as “Open release notes”the RoamSwitch process opens no socket; it hands the URL to the OS browser
3freshclamonly when the user clicks “Update Virus Signatures”. Never automatic or scheduledtraffic of ClamAV itself (a separate project); RoamSwitch only launches it
4resolvectl dns …threat-protection DNS on an untrusted network (Pro, opt-in)only changes the OS resolver setting; RoamSwitch itself sends no DNS query
5package-manager updateswhen the user runs apt upgrade, etc.RoamSwitch only reads local apt-cache policy output; it initiates no network fetch

Every other RoamSwitch feature (nftables control, port scanning, ARP monitoring, health assessment, entropy monitoring, canaries, fanotify, USB monitoring, quarantine, log auditing, URL and secret auditing) completes using only local file reads and analysis of subprocess output.

What stays on the machine (privacy)

  • No account — no user registration, no login, no email address ever requested.
  • Configuration is local only — plaintext at ~/.config/roamswitch/config.json, never leaves the machine. No cloud sync.
  • Assessment results are local only — displayed on screen and, if the user chooses, exported to a local file.
  • License keys are verified offline — Ed25519 signature verified with a public key. No query to a lafine server.
  • No crash reporting — on panic, no stack trace is sent anywhere.

§10Licensing and distribution

Editions
EditionForPriceLicense
Community EditionLinux personal useFreeProprietary freeware (bundled EULA)
Business (planned)fleet management, policy distribution, signed internal apt, SLAPaidCommercial license. Same binary + tier gate

Purchasers of macOS RoamSwitch Pro Lifetime receive Business features free on their own Linux machines.

Distribution and supply chain

ChannelDetail
APThttps://lafine.net/apt (stable main). Release signed with an RSA-4096 key
RPMhttps://lafine.net/rpm (separate Fedora / openSUSE). repomd.xml GPG-signed
AURroamswitch-bin (fetches the GitHub Releases tarball, verifies sha256)
tarballamd64 / aarch64 .tar.gz + .sha256 on GitHub Releases
  • Pinned glibc floor — release binaries are built inside a Debian 12 (bookworm, glibc 2.36) container, preventing symbol-version mismatches on Debian 12 / Ubuntu 22.04 and later. A “verify glibc floor” CI step enforces this.
  • Signing key management — the private signing key exists only in a GitHub Actions encrypted secret; only the public key is committed to the repository.

§11Requirements

  • OS: Ubuntu 22.04 / 24.04+, Debian 12+, Linux Mint / Pop!_OS / elementary / Zorin, Raspberry Pi OS 64-bit (Bookworm), Ubuntu for Raspberry Pi
  • Architecture: x86_64 / aarch64
  • Required: systemd, nftables (pulled in automatically by the package dependencies)
  • Optional: clamav (malware scanning), nmcli (Wi-Fi encryption detection), libnotify (desktop notifications)
  • Not supported: non-systemd distros (Alpine / Void / Devuan)
  • Display: 1280×720 or larger (the window and font auto-scale to the screen size, supporting small Raspberry Pi displays)

§12Direct dependency crates

CratePurposeNetwork
serde / serde_jsonserialization of config and IPCnone
tokioasync runtime (only Unix sockets used)none (no TCP)
tracinglogging (local / journald)none
urlURL parsing (audit heuristics)none (does not fetch)
ed25519-dalekoffline verification of license signaturesnone
base64 / sha2license tokens, canary hashesnone
libcchown / kill / syscallsnone
opencalls xdg-open for “Open release notes”the process itself does not communicate
GTK3 / ksni (app only)GUI and traynone
Bottom line

There is no HTTP client, TLS stack, or telemetry SDK anywhere in the dependency tree.

Appendix ASelf-check (verify it yourself)

Every claim in this document can be verified on your own machine. The following runs on a host with roamswitch installed.

A.1 Confirm “zero data sent off the machine” yourself (from the package alone)

The source is not published, but the following run on a host with roamswitch installed and each supports “no outbound communication”. The full dependency-tree audit results are in §9.

The distributed binary links no HTTP / TLS library:

ldd "$(command -v roamswitch-daemon)" | grep -iE 'curl|ssl|crypto|nghttp|http'
# -> no output = no HTTP/TLS shared library is linked at all

The running daemon holds no internet socket:

sudo lsof -p "$(pgrep -x roamswitch-daemon)" -a -i
# -> no output = zero TCP/UDP internet connections
sudo lsof -p "$(pgrep -x roamswitch-daemon)" -a -U | grep roamswitch
# -> only /run/roamswitch/roamswitch.sock

Live measurement over time (no RoamSwitch-owned outbound connection appears):

watch -n 5 'sudo ss -tupn | grep roamswitch || echo "(no external sockets)"'
# -> always "(no external sockets)"

Syscall-level check (connect targets are AF_UNIX only):

sudo strace -f -e trace=connect -p "$(pgrep -x roamswitch-daemon)" 2>&1 | grep -i connect
# -> only connect() to AF_UNIX. No AF_INET / AF_INET6 appears

A.2 Destructive self-test suite (run by lafine during the audit)

lafine maintains a disposable --privileged container (its own network namespace, so an Air-Gap does not cut the host). It is built on Debian 12 (the same glibc floor as the release CI), starts the daemon, and verifies the following in sequence. Results are in A.3.

IDScenarioPass criterion
SP-1Ransomware-decoy (canary) tamper detection“Canary tampered” detected within seconds, Air-Gap triggered
SP-2Ransomware bulk-encryption burst detectionthe process is frozen with SIGSTOP, Air-Gap triggered
SP-2bNo false positive on shredno “RANSOMWARE BURST” (allow-listed)
SP-2cCritical processes never frozendockerd and similar are not frozen
SP-3Air-Gap enable → isolationpolicy drop on the output hook; external traffic blocked
SP-3bAir-Gap self-healingexternal nft delete table → the sentinel loop re-applies within 5 s
SP-3cAir-Gap fail-closeddaemon restart mid-Air-Gap → isolation persists
SP-3dAir-Gap disable → recoverydrop policy lifted, all marker files removed, connectivity restored
SP-4Malware quarantine (EICAR)removed from Downloads, relocated to the Quarantine Vault
SP-4bQuarantine Vault permission hardeningdirectory 0700, sample 0400

Additionally verified on real hardware (ext4 ~/) since the container’s overlayfs / tmpfs delivers no fanotify events: SP-2 on host (a python3 burst of 6+ files → freeze → Air-Gap), SP-1 on host (rename of one of 16 monitored canaries detected in ~2 s), SP-5 (MCP get_guard_status manual-override label consistency), SP-6 (BadUSB: an unregistered keyboard present at startup treated as suspect), and every CLI subcommand (status / guards / wifi / ports / canary / quarantine / audit-url / audit-secrets / audit-logs / knowledge / sharing) in both languages.

A.3 Most recent run

Result

Docker self-test (roamswitch-test image): 14 / 15 PASS. The single non-pass is SP-2 (in-container entropy burst), due to the known limitation that a container’s overlayfs / tmpfs delivers no fanotify permission events from the kernel. The harness detects this with a probe and treats it as SKIP rather than FAIL, with a separate PASS confirmed on real hardware (ext4 ~/).

Every defect found during the self-penetration test (the canary’s $HOME-relative path bug, the MCP serde camelCase mismatch, a missing re-assert in disable_air_gap, over-permissive Quarantine Vault modes, a shred false positive, an erroneous freeze of the Docker daemon) has been fixed and re-verified.

A.4 Known limitations

LimitationImpactMitigation
On some kernels, FAN_MARK_MOUNT on tmpfs (/tmp) delivers no eventsfile writes under /tmp are not monitored by fanotify/tmp is ephemeral and not a monitored user folder; ransomware targets ~/. Mark failures are logged explicitly
No fanotify delivery on container overlayfs / tmpfsin-container entropy-burst detection cannot be validatedvalidated on real hardware (ext4); the harness auto-SKIPs via a probe
RoamSwitch’s nftables operations share the netfilter space with Docker’s iptables-nftRoamSwitch rule operations can interfere with Docker’s NAT rulesdocumented as an operational caveat; isolated in the dedicated inet roamswitch table, but whole-ruleset operations such as nft flush ruleset should be avoided
This document reflects the implementation as of RoamSwitch for Linux v1.0. For the latest information, see lafine.net/linux.