§1
RoamSwitch for Linux 是一款常驻网络安全和系统诊断应用程序,它将 macOS RoamSwitch 的零信任网络防御模型引入 Linux(systemd + nftables 环境)。
此版本针对客户端计算机(工作站、笔记本电脑、单板计算机),并假设机器在网络之间移动时遵循机器的自主行为。强化服务器/无头基础设施版本(RoamSwitch 服务器版本 - 始终在线默认拒绝、关键路径 FIM 完整性、远程 Webhooks 和静态策略实施)作为专用服务器版本提供。
它通过网关 MAC 地址识别连接的 Wi-Fi/有线网络,并自动切换活动的 nftables 防火墙配置文件。除此之外,它在不受信任的网络上提供三层中间人防御 - VPN 隧道 + 终止开关(WireGuard 或 Tailscale,主要防御)、预防性 ARP/NDP 锁定 和 ARP 欺骗检测 - 加上行为勒索软件检测(fanotify + Shannon entropy + canary),这是一种标记的被动链路防护或阻止网络钓鱼连接 (NFQUEUE + SNI)、端口异常防护、BadUSB 缓解、内核本地权限升级 (LPE) 抢占(Frag Gap CVE-2026-53362 缓解)、eBPF 运行时威胁传感器 (Falco / Tetragon) 与亚秒级进程冻结 (SIGSTOP) 和 Air-Gap 网络隔离 集成, 24 项安全健康评估和内置模型上下文协议 (MCP) 服务器。
本白皮书描述了每种防御机制的架构、威胁模型和设计,特别是如何在代码级别保证“完全本地处理;机器发送的唯一内容是对签名检测数据的请求,不携带任何关于您的信息”的原则。它避免了营销语言;可以根据分布式包(二进制)的实际行为和附录中的自检来检查声明。 Linux 版 RoamSwitch 是专有的免费软件,其源代码未发布 (§11)。
| 原理 | 详情 |
|---|---|
| 零遥测 | 该产品不会向任何人发送任何有关您或您的机器的信息。没有遥测、分析、崩溃报告、使用指标、远程配置或许可证激活服务器。 |
| 仅接收更新 | 该产品发出的一个出站请求是每天一次的 GET,来自 lafine.net/updates/v1 的固定签名清单 - 链接防护的诈骗站点列表、ClamAV 签名版本、最新应用程序版本、软件包 CVE 映射(Homebrew/npm/PyPI/crates.io/RubyGems/Packagist/Go/Maven)、实证型漏洞诊断的 CVE 映射,以及用于打字仿冒检测的热门 npm 软件包名称列表。它不携带查询字符串、cookie、识别标头,也不包含有关您的信息。 updates.enabled = false 完全关闭它;然后,该产品可以在零外部接触的情况下运行捆绑数据。 |
| 完全本地处理 | 每个评估、检测和审核都在本地计算机上运行完成。 URL 安全分析和秘密扫描会启发式地评估其输入,而无需将其传输到任何地方。 |
| 最小权限 | 特权操作仅限于 root 守护进程; UI、CLI 和 MCP 服务器通过 Unix 域套接字 IPC 请求它们。守护进程不打开 TCP/UDP 套接字;链路防护仅使用 AF_NETLINK NFQUEUE 判定通道(内核 ↔ 用户空间,无数据包出口)。 |
| 失败关闭 | Air-Gap(紧急隔离)在守护程序重新启动后仍然存在,并且如果其规则被外部删除,则会在几秒钟内重新生效。 |
| 离线许可 | Pro / Business 许可证纯粹通过 Ed25519 签名检查进行验证。不会调用激活服务器。 |
§2
RoamSwitch for Linux 主要针对在不受信任网络之间漫游的 笔记本电脑/移动工作站。
范围内的攻击者
- 同一 LAN 上的攻击者 — 共享 Wi-Fi 上的端口扫描、ARP/NDP 欺骗(包括恶意路由器广告)以查找中间人、恶意 DHCP、MAC 泛洪后的被动嗅探、对开发服务器的未经授权的访问或意外绑定到
0.0.0.0的本地 LLM API。 - 恶意 USB 设备 (BadUSB) — 冒充键盘并在屏幕锁定之前注入击键;未注册的 USB 大容量存储,用于数据泄露或携带恶意软件。
- 勒索软件/无文件恶意软件 — 通过下载的文件进入并对用户的文档进行批量加密。
- 基于 DNS 的 C2 和网络钓鱼 — 使用名称解析到达 C2 服务器或网络钓鱼站点的恶意软件。
超出范围
- 已经拥有root权限的攻击者;内核或固件级 Rootkit。
- 物理删除磁盘的攻击(全盘加密领域;RoamSwitch 诊断是否存在加密但不加密)。
- AirDrop 式近距离无线共享控制(Linux 上没有操作系统本机机制;不支持)。
§3
适用于 Linux 的 RoamSwitch 在 Rust 中跨七个 crate 实现。与守护进程的所有通信都通过 /run/roamswitch/roamswitch.sock (Unix 域套接字)。 守护程序、应用程序或 MCP 服务器中没有侦听 TCP/UDP 套接字。 应用程序 binds 唯一的东西是单个 Unix 套接字,以防止第二个实例。
# 用户空间(非特权) roamswitch-app GTK3 + AppIndicator 托盘 roamswitch (CLI) 面向 shell 的轻量前端 roamswitch-mcp stdio JSON-RPC(由 AI 客户端启动) │ │ AF_UNIX /run/roamswitch/roamswitch.sock (仅本地 IPC) ▼ roamswitch-daemon root / systemd Type=notify + WatchdogSec=30 · nftables 配置文件控制(inet roamswitch 表) · sentinel 循环(网络判定与配置文件应用) · VPN 隧道 + 断网开关(WireGuard: wg-quick / roamswitch_vpn,Tailscale: roamswitch_ts) · 预防性 ARP/NDP 锁定(neigh 表固定) / 检测 · 端口异常防护(roamswitch_dev_guard) · fanotify 防护(勒索软件/恶意软件) · 链路防护(NFQUEUE — inet roamswitch_linkguard) · 诱饵引擎 / 隔离 Vault · 内核 LPE 加固(禁用 userns、Yama LSM) · eBPF 运行时联动(/var/run/roamswitch/events.sock) · 以毫秒级 SIGSTOP 冻结攻击进程并联动 Air-Gap · USB 监控(evdev EVIOCGRAB — BadUSB) / 批准队列 · Ed25519 许可证验证 roamswitch-updater 独立的 systemd 定时器 helper。是唯一拥有 HTTP/TLS 协议栈的 crate(每日获取一次签名清单) roamswitch-core 纯逻辑库(所有 crate 共用) roamswitchkit MCP 客户端 SDK
| 组件 | 特权 | 角色 |
|---|---|---|
| 漫游交换机守护进程 | 根(systemd,Type=notify + WatchdogSec=30) | 所有特权操作。作为单一编写者,控制 nftables、DNS 和 systemd 单元。 |
| 漫游交换机应用程序 | 登录用户 | GTK3 GUI + AppIndicator 托盘。仅配置编辑和可视化;每个特权操作都经过IPC。 |
| 漫游交换机 (CLI) | 登录用户 | 薄壳前端使用相同的IPC插座。 |
| 漫游交换机-MCP | 由AI客户端产生的进程 | stdio JSON-RPC。提供只读诊断信息。 |
与守护程序的所有通信都通过 /run/roamswitch/roamswitch.sock (具有 0700 等效权限的目录)。用户配置以纯文本 JSON 形式存储在 ~/.config/roamswitch/config.json 处。守护进程以 root 权限读取 /home/*/.config/roamswitch/config.json(它以 HOME=/root 运行)。配置永远不会离开机器;没有帐户注册,也没有云同步。
§4
哨兵循环(默认为 5 秒循环)从 /proc/net/arp 和 ip neigh 读取默认网关的 MAC,并将其与 config.json 中注册的 MAC 进行匹配。使用 MAC 而不是 SSID,因为 SSID 很容易被欺骗。配置文件切换时显示的桌面通知会说明切换的 reason(“未注册的网络”/“您对注册网络 X 的设置”/“无网络连接”)。
| 公司简介 | 情况 | nftables 行为 |
|---|---|---|
| 开放(可信) | 已注册的家庭或公司网络 | policy accept。允许本地交通。 |
| 平衡 | 未注册但配置为相对安全 | 默认拒绝入站。只允许已建立的连接和 lo;暴露端口暴露监测。 |
| 锁定(离开保护) | 公共 Wi-Fi/未注册网络 | 所有入站数据包均被秘密丢弃。共享服务自动停止(选择加入)。 |
所有规则都隔离在名为 inet roamswitch 的专用 nftables 表中,并且永远不会与其他防火墙配置(ufw/firewalld)发生冲突。手动覆盖到期(1 小时/2 小时/4 小时/直到午夜/直到清除/直到下次断开连接)由守护进程作为单个写入者强制执行。 GUI 永远不会写过期日期;它会重新读取守护进程在下一个时钟周期重写的 config.json 并将其反映在 UI 中(避免冲突)。
Air-Gap(紧急网络隔离)
一种完全隔离模式,在 inet roamswitch 表的输入和输出挂钩上设置 policy drop(优先级 -100),仅允许 lo。
- 失败关闭 — 在守护程序重新启动时,检测标记文件 (
/run/roamswitch/airgap.active) 重新断言隔离。 - 自我修复 — 如果外部进程删除带有
nft delete table的规则,哨兵循环会在几秒钟内重新应用。 - 自动过期 — 激活后 600 秒(与 macOS
maxAirGapDuration相同),隔离会自动解除,防止因误点击而导致连接永久丢失。
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
}
chain output {
type filter hook output priority -100; policy <accept|drop>;
# 仅在 Air-Gap 时 policy drop,仅允许 lo 与 ct established
}
}
被动链接防护(网络钓鱼连接检测)
该守护进程监视出站连接的去向,并标记(或者,对于狭窄的“明显的网络钓鱼”集,阻止)与危险主机的连接。它取代了旧的粘贴 URL 检查器,后者需要用户采取行动。
- Interception — 专用的 nftables 表
inet roamswitch_linkguard挂钩output路径,并通过 NFQUEUE 将 DNS 问题和新的/早期的 Web 连接排队到用户空间工作线程。 NFQUEUE 是一个基于AF_NETLINK的内核↔用户空间判决通道;守护进程不绑定任何套接字,也不产生任何数据包。队列规则带有bypass,因此如果工作线程未运行,流量将畅通无阻(Air-Gap 仍然是单独的硬停止)。 - Hostnamesources — DNS 问题名称、TLS ClientHello SNI(因此使用 DoH 的浏览器仍被主机名覆盖)以及明文 HTTP
Host标头。加密的 ClientHello (ECH) 一旦广泛部署,就是一个已知的盲点。 - Verdict(
roamswitch-core,纯逻辑)— 根据本地网络钓鱼/威胁源和一组离线启发式检查主机:受保护品牌的 IDN/易混淆同形异义词、非官方域中的品牌令牌、原始 IP 主机、高风险 TLD。
模式 (linkGuard.mode) | Feed 命中或品牌同形异义词 | 其他可疑信号 |
|---|---|---|
off | 未检查 | 未检查 |
warn | 连接保持长达 8 s + 允许/阻止批准提示 | 仅通知 |
block(默认) | 连接断开 + 允许对话框(每个主机一次) | 仅通知 |
只有热门信息或品牌同形异义主机才会被硬阻止;启发式标记的所有内容都是警告。 warn 模式 保持 与危险主机的连接最多 8 秒,同时询问用户;如果保留到期且没有答复,则为 fail-close (数据包被丢弃且判决未缓存) - 不回答“此站点正常吗?”不得表示“是”,下次尝试会再次提示。 block 模式显示一个通知和一个允许对话框 每个主机 (浏览器打开与同一被阻止主机的多个连接不再淹没通知)。在危险的批准对话框中,默认按钮是“继续阻止”,“允许”被设计为安静的非默认选项。可以永久允许被阻止的主机,也可以在收到通知后五分钟内允许 (linkGuard.allowlist)。
DNS 经过检查,从未伪造:列入黑名单的 DNS 问题仅产生通知;强制执行发生在后续 TCP 连接上,因此 RoamSwitch 永远不会注入合成 DNS 响应。 feed 是网络钓鱼/诈骗域的编译列表,在软件包中提供并由每日更新程序刷新(§11)。在没有提要的情况下,离线启发式仍然运行。加载前会检查 feed 文件的 Ed25519 签名;不良或丢失的提要无法“仅启发式”,而绝不会“允许所有”。
VPN 隧道 + 终止开关(主要的 MITM 防御)
不依赖于第 2 层完整性的反 MITM 保护。在不受信任的网络上,到受信任端点的始终在线加密隧道使本地 ARP/NDP 欺骗、恶意 DHCP 和被动嗅探变得无关紧要 — 攻击者只能看到密文。 ARP/NDP 锁定和下面的检测是此功能的次要功能。
两个后端可供选择 —“WireGuard(配置文件)”或“Tailscale(退出节点)”。 RoamSwitch 没有实施自己的加密技术,也不是 VPN 提供商。两个后端都是可选的依赖项;如果不存在,GUI 将显示安装命令。
(A) WireGuard 后端
- 不提供 VPN 服务器 — 使用用户提供的 WireGuard 配置:来自 Mullvad、IVPN 或 Proton VPN 等提供商的
.conf,或用户自己的 WireGuard 服务器(VPS 或家庭服务器)。没有提供默认配置。 - Config — 守护程序验证
.conf并将其存储在/etc/wireguard/roamswitch.conf(0600,接口roamswitch)。它不能存在于其他地方:Ubuntu 24.04+ / Debian 13 上的 AppArmorwg-quick配置文件拒绝wg-quick读取/etc/wireguard/之外的.conf— 即使作为 root 也是如此。分割隧道配置(AllowedIPs不是0.0.0.0/0/::/0)被标记为“并非所有流量都受到保护”。 - Kill-switch — 专用的 nftables 表
inet roamswitch_vpn(policy drop,两个钩子) 丢弃除环回、隧道接口(roamswitch,与oifname匹配)、固定端点 IP、DHCP 和 ICMP 之外的所有内容。它是在 之前安装的 隧道启动(首先通过getent ahosts解析到固定 IP 的端点),因此不存在泄漏窗口。如果wg-quick up失败,终止开关将保持开启状态。
(B) Tailscale 后端
- 对于已经运行 Tailscale 的用户。 RoamSwitch not 运行
tailscale up/ 登录 / 安装tailscaled— 它读取tailscale status --json并运行tailscale set --exit-node=<node>/--exit-node=(清除)。 - 需要出口节点 — 反 MITM 保护仅适用于路由所有流量的出口节点。如果未配置任何内容,则不会启用保护,并且 UI 会警告“未受保护”。如果所选节点离线,保护会自动解除并通知用户(通过死节点路由所有内容将使连接陷入黑洞)。
- Kill-switch (
inet roamswitch_ts,policy drop) — Tailscale 的传输无法固定到单个端点 IP(DERP 中继 + 漫游对等点),因此这比 WireGuard 更宽松 X12__。它允许:环回、隧道接口 (tailscale0)、CGNAT 范围100.64.0.0/10和fd7a:115c:a1e0::/48、STUN (udp/3478)、DERP (tcp/443)、udp/41641、DHCP、ICMP、MagicDNS (100.100.100.100:53)、已建立的连接。 其他所有内容 — 任意 UDP、本地解析器的 DNS、SMB、mDNS、纯文本 HTTP、任意 TCP — 均被丢弃。 - Kill-switch 警告 — (1) 比 WireGuard 后端更广泛的允许列表(
udp/3478、tcp/443、整个 CGNAT/10)。 (2) 路径上的攻击者仍然可以看到元数据(Tailscale 正在使用、DERP 区域、计时)并且可以丢弃/延迟 — 但无法读取隧道有效负载。 (3) 当直接 UDP 被阻止时,机密性将依赖于 Tailscale 自己的固定证书 DERP TLS。 (4) 在 MagicDNS 关闭 + 本地解析器的情况下,DNS 在武装时中断(UI 警告)。 (5) 启用前必须先登录强制门户。
Common — 仅当 vpn_on_untrusted_enabled 且网络不受信任 (level != open) 时才启用。在受信任的网络上,退出节点被清除,终止开关被删除(从不 tailscale down)。默认关闭,选择加入。 WireGuard 隧道/Tailscale 传输前往 用户自己的 VPN 端点/tailnet,与 lafine 的基础设施无关 (§9)。
预防性 ARP/NDP 锁定
一旦加入不受信任的网络,MITM 攻击者需要模拟的链路基础设施的 MAC 地址就会被 作为 PERMANENT(静态)条目 固定到内核邻居表中。一旦固定,内核就会忽略欺骗性的 ARP 回复/邻居通告。
- 固定的内容 — (1) IPv4 默认网关,(2) IPv6 默认路由器(通常是链路本地
fe80::地址),(3) 任何链接 DNS 解析器(通过网关到达公共解析器并被跳过)。 IPv6 邻居通过ip -6 neigh replace ... nud permanent固定。 - 受信任的网络永远不会被固定 - 因此家庭路由器重新启动不会使用户陷入黑洞。重新固定在每个网络交换机上(TOFU);下次重新连接时将进行合法的故障转移。固定条目记录在
/var/lib/roamswitch/arp_lock.json中。 - 默认启用(社区版政策;与 macOS Pro 版本的细分相同)。切换:
gateway_arp_lock_enabled。
ARP 欺骗检测(通知优先)
监视 /proc/net/arp 是否有一个 IP 映射到多个 MAC 以及网关 MAC 的可疑突然变化。 锁定中:切断所有流量(气隙)+通知。 在平衡/可信网络上:通知而不切断;用户从 RoamSwitch 触发紧急 Air-Gap。这样 (a) 可以阻止攻击者将单个欺骗性 ARP 数据包武器化以导致自身造成的中断,并且 (b) 可以阻止路由器重新启动或接入点漫游错误地触发它。切换:arp_spoof_guard_enabled。
端口异常守卫
自动包含新暴露给外部 LAN 的侦听端口(后门、C2 或意外绑定到 0.0.0.0 的开发服务器/本地 LLM API)。结论是 baseline-relative,而不是“危险端口号”。启用后,当前公开的可执行文件被学习为“已知”;此后,仅标记 新出现的 公开的可执行文件(身份 = 可执行文件路径,快照标准化为 snap:<name>)。系统守护程序超出范围。遏制是通过 inet roamswitch_dev_guard 表进行的:将 TCP 从 lo 以外的任何端口丢弃到该端口 — 机器本身继续使用它。自动遏制后,会立即出现桌面通知和确认对话框(“允许此端口”/“保持阻止”),允许一键永久列入白名单。还可以从端口诊断屏幕单独允许任何端口,并重新捕获基线。默认开启。切换:port_anomaly_guard_enabled。从 macOS 移植。
Docker 风险检测与防火墙绕过防护
Docker 的公开端口(-p)存在绕过主机标准防火墙的已知问题,因此会始终在 DOCKER-USER 链中插入检查规则,阻止未被明确允许的外部流量(无需配置,默认始终启用)。此外,还会实时监视 docker events,在 --privileged 容器启动或挂载 /var/run/docker.sock(容器逃逸风险)的瞬间检测并立即通知。由于这只是有风险的配置而非确认的入侵,因此仅提供通知,不会自动阻止。
机密信息/API 密钥泄露扫描器
通过香农熵与模式匹配检测 OpenAI、Anthropic、AWS、GitHub、Slack、Stripe 等 API 密钥及 SSH 私钥。roamswitch audit-secrets <text|文件|目录> 除文本、文件外,还可递归扫描整个目录(自动跳过 .git、node_modules 等,超过 2MB 或疑似二进制的文件将被跳过)。也可通过 audit_secrets MCP 工具及 Python SDK 的 audit_secrets_directory() 调用相同功能。内容绝不会被发送到任何地方。
首次运行设置向导和确认 UI
该软件包附带 /etc/xdg/autostart/roamswitch.desktop,因此安装向导在安装后首次登录时启动(此后它仅驻留在托盘中)。启用/禁用安全防护会显示与 macOS 版本相同的防护的确认对话框(端口异常、ARP 自动遏制、USB 存储、BadUSB 键盘、蓝牙)。事件时决策(未知的 USB 键盘、未注册的 USB 驱动器、自动阻止的未知端口、紧急气隙)以模式对话框的形式呈现。
§5
香农熵突发检测·YARA扫描
写入事件按照 PID 进行跟踪。如果单个进程在 5 秒内创建 20 个或更多 distinct 文件,且熵≥ 7.92,则会被视为勒索软件批量加密:该进程会被 SIGSTOP 冻结,并触发 Air-Gap。
- YARA 扫描和熵跟踪完全分离 — YARA 恶意软件检查无一例外地应用于所有写入文件(零绕过、扫描
~/.cache/、node_modules/、/tmp/和存档)。同时,勒索软件熵爆发分析排除了自然压缩格式(.zip、.tgz、.tar.gz、.gz、.png、.jpg、.mp4、.wasm)和经过验证的包管理器(npm、npx、cargo、rustc、dpkg、apt、tar)在构建/缓存树中操作以消除误报 SIGSTOP 冻结。 - 同一文件的重复覆盖 (例如
shred的三遍)算作一个事件。 - 关键进程保护 — PID 根据硬编码的 NEVER_FREEZE 列表(
systemd、dockerd、NetworkManager、sshd、gnome-shell、...)进行检查,并且永远不会冻结,从而防止机器范围内的挂起。
金丝雀(诱饵文件)
诱饵文件被放置在用户的文档、桌面、下载和图片文件夹以及专用目录中,并以 3 秒为周期与 SHA-256 基线进行比较。任何重命名、删除、截断或内容更改都会立即触发 Air-Gap。因为守护进程以 root 身份运行,所以它会枚举 /home/* 并将金丝雀部署到每个真实用户的目录中。
fanotify 监控 · 隔离库 · ClamAV
- 守护进程在挂载级别监视
/home、/var/tmp、/dev/shm,并通过FAN_CLASS_CONTENT(后备FAN_CLASS_NOTIF)在文件系统级别监视/。 - 通知优先,无静默隔离。 裸露的 YARA / ClamAV 命中是 绝不会静默隔离或
FAN_DENY’d。真实签名在/run/roamswitch/approvals.json中提出模式请求(隔离/允许/稍后);在用户确认之前,任何内容都不会进入保管库。只有实际的execve才是FAN_DENY,并且只有当pre_exec_blocking_enabled开启时。批准超时失败开放。 - EICAR 等测试签名仅记录到通知历史 — 行业标准测试字符串不会发出通知,只会记录到通知历史中,并且在 fanotify 防护和下载防护中都不会被隔离或阻止。
- YARA 和 ClamAV 扫描仪都遵循用户配置的
scan_exclusions路径列表(绝对路径,递归应用;它包含旧版clamav_exclusions)。提示上的“允许”和“隔离”选项卡中的“恢复”都附加到它,因此不会重新标记同一文件。 - 检测到并确认的真正恶意软件已转移到
~/.local/share/roamswitch/quarantine/。金库是0700;每个样本都是0400(只读,不可执行)。恢复时,文件返回到0644并被 chowned 到目标目录的所有者。 - 权限事件循环执行零文件系统 I/O — 配置由专用后台线程读取到
Mutex<GuardConfig>快照中;热路径访问器只是锁定和复制。这是一个设计不变性:阻塞FAN_OPEN_PERM会冻结机器。 - 当 ClamAV 存在时,会根据本地数据库扫描新下载的可执行文件(
.sh、.deb、.elf、...)和危险的 AI 模型(.pkl、.pt)。 ClamAV 是一个 可选依赖项 ;没有它,其他防御措施也能发挥作用。安装后,每日更新程序 (§11) 会调用freshclam,以便签名数据库保持最新状态。
内核本地权限提升 (LPE) 抢占和强化
当攻击者或恶意软件获得非特权用户立足点时,深度防御可防止利用 Linux 内核漏洞获取 root 权限。
- 禁用非特权用户命名空间(Frag Gap CVE-2026-53362 缓解) — 许多现代 Linux 内核 LPE 漏洞依赖于非特权用户调用
unshare(CLONE_NEWUSER)来创建隔离的用户命名空间,随后访问未初始化的内核结构或具有提升的命名空间功能的旧网络子系统。 RoamSwitch 通过一键强化或设置向导评估和实施user.max_user_namespaces = 0(或kernel.unprivileged_userns_clone = 0)。即使使用未修补的内核零日漏洞,这也可以关闭非特权进程的攻击面。 - Yama LSM 内存保护 — 强制执行
kernel.yama.ptrace_scope = 1(或2)可防止未经授权的进程通过ptrace附加或通过/proc/$pid/mem读取内存,从而保护浏览器会话和 GPG/SSH 私钥。 - Core Dump Suppression — 设置
fs.suid_dumpable = 0可防止敏感进程将纯文本内存转储写入磁盘而导致崩溃。 /tmp和/dev/shmnoexec安装防御 — 为了防止有效负载丢弃并执行攻击,RoamSwitch 在连接到不受信任的网络时会使用noexec,nosuid,nodev重新安装/tmp和/dev/shm。
eBPF 运行时威胁传感器和自主进程冻结(桌面 eBPF Guard)
RoamSwitch 客户端守护程序包括与内核内现代 eBPF 传感器(Falco / Tetragon / BTF)的本机集成。
# eBPF 运行时威胁检测与自主拦截流程 [恶意进程 (LPE/恶意软件)] │ 非法系统调用执行 (Frag Gap 利用尝试) ▼ [Linux 内核 (eBPF 探针)] │ eBPF 环形缓冲区 (2MB 精简设计) ▼ [Falco (RoamSwitch Optimized)] │ 内存中 UNIX 套接字直接通信 (/var/run/roamswitch/events.sock) ▼ [roamswitch-daemon (root)] ├─► ① 以毫秒级冻结攻击进程:kill(pid, SIGSTOP) [状态:Ts] ├─► ② 完全切断网络:nftables input/output drop [启动 Air-Gap] └─► ③ 桌面紧急通知:GTK3 模态框 & libnotify 通知
- Direct UNIX Domain Socket IPC (
/var/run/roamswitch/events.sock) — 来自 Falco 或其他 eBPF 传感器的警报直接通过内存中 UNIX 套接字接收,无需写入磁盘日志,从而消除磁盘 I/O 延迟并实现亚秒级响应切换。 - 自主多层缓解(进程冻结和气隙) — 当收到优先级为
Critical或Emergency的事件(例如内核 LPE、命名空间逃逸或勒索软件执行)时,守护进程在违规 PID 上执行SIGSTOP以立即暂停进程执行(状态:Ts),跨inet roamswitch(气隙)部署policy drop,并立即触发桌面通知。 - 面向桌面的低开销及日志膨胀防止调优(RoamSwitch Optimized) — 为抑制笔记本电脑和工作站的电池消耗、CPU 负载和磁盘枯竭,随附了优化配置文件(
99-roamswitch-optimized.yaml):- 严重性过滤 (
priority: warning) — 过滤掉 95% 的常规通知/信息事件。 - Kernel Early Drop (
drop_failed_exit: true) — 直接在内核驱动程序中丢弃失败的系统调用退出,将上下文切换和 CPU 负载减半。 - 紧凑环形缓冲区 (
cpus_for_each_buffer: 2) — 将环形缓冲区大小减少至 2MB,将常驻 RAM 保持在 30–50MB,CPU 使用率 <1%。 - 强制日志轮换上限 (
/etc/logrotate.d/roamswitch-falco) — 通过 7 代压缩轮换强制实施严格的 50MB 上限。
- 严重性过滤 (
- 混合弹性(独立与 eBPF 模式) — 没有 Falco,RoamSwitch 通过内核 sysctl (
user.max_user_namespaces = 0)、nftables 漫游配置文件、fanotify 熵跟踪和金丝雀来保护系统。借助 Falco,深层内核行为遥测可拦截高级零日漏洞并实时冻结攻击者进程。
§6
USB事件被监控;跟踪键盘类 (HID) 设备和 USB 大容量存储。 默认关闭 (发货 isEnabled = false,与 macOS USBKeyboardGuard / USBStorageGuard 匹配)。从首次运行向导或 USB 选项卡中选择加入;启用会显示一个确认对话框。
流氓 USB 键盘(击键注入)
- 无内核级物理端口取消授权(重要)。旧的实现(≤1.0.6)将
0写入/sys/bus/usb/devices/*/authorized对于未注册的键盘,物理上断开连接。在键盘+鼠标组合接收器(常见于 Raspberry Pi 或迷你 PC)上,这也会断开鼠标连接,从而锁定没有内置输入的机器。 RoamSwitch 现在硬拒绝将authorized=0写入任何暴露 HID 接口 (bInterfaceClass 03) 的设备,并且守护程序在启动时重新授权任何authorized==0HID 设备,以自我修复先前版本变砖的机器。 - 按键在软件中被删除。 在检测到未注册的键盘时,会在该设备的
/dev/input/eventN上获取独占的 evdevEVIOCGRAB(仅在/proc/bus/input/devices中具有kbd处理程序的节点,与 VID:PID 匹配)。鼠标节点未受影响。这是 macOSCGEventTap的 Linux 版本;设备保持通电状态。 - Safety — 仅当有另一个键盘可供输入(内置键盘或经批准并连接的 USB 键盘)时才会进行抓取,因此具有单个新键盘的机器永远不会被锁定。
- 批准对话框 — 在抓取旁边,批准请求在
/run/roamswitch/approvals.json中排队,GUI 显示模式(允许/拒绝)。允许将设备列入白名单并释放抓取。 3 分钟后失效打开。
流氓 USB 大容量存储设备
当插入未注册的 USB 大容量存储设备(无 HID 接口)、启用防护且网络不受信任/处于锁定状态时,它会以 authorized=0(对于非 HID 设备来说是安全的)保存,并显示批准对话框。允许重新授权和白名单;拒绝保留。 macOS 版本与 1.7.4 中的“保留只读 + 批准提示”模型保持一致。
身份识别和追溯
HID 由 bInterfaceClass == "03" 和 /proc/bus/input/devices 中的 Handlers= 行确定。组合接收器(一个 VID:PID 带有 kbd 和鼠标部分)由 VID:PID 合并。 守护进程启动时出现的未注册键盘是祖父;仅抓取事后热插拔的键盘。
§7
roamswitch-core 中的 LinuxHealthChecker 评估 24 个项目,并生成 0-100 分、A-F 等级和每个项目的建议。从 GTK 仪表板、CLI (roamswitch status) 和 MCP (get_security_report) 中, 所有 24 个项目的标题、详细信息和推荐均以用户的语言 (10 种语言)显示。
| 类别 | 示例项目 |
|---|---|
| 磁盘/启动 | LUKS / dm-crypt 加密、UEFI 安全启动 |
| 访问控制 | LSM (AppArmor / SELinux)、SSH / sudo 配置审核 |
| 内核强化 | sysctl 强化、非特权用户命名空间禁用(碎片间隙缓解)、核心转储控制、/tmp 和 /dev/shm 上的 noexec |
| 更新 | 自动安全更新配置 |
| 网络 | ARP欺骗监控、网关ARP/NDP锁定状态、暴露端口审计、Wi-Fi加密强度 |
| 恶意软件 | ClamAV / fanotify (还检查守卫是否实际运行) / 熵监控 / noexec |
| 浏览器 | Firefox / Chrome / Chromium 安全浏览设置 |
| 域名解析/USB | 威胁防护 DNS、USB 总线的零信任状态 |
每次检查都基于读取本地文件和命令输出(getenforce、mokutil、resolvectl、ss、...)。 没有任何类型的外部查询。
信任感知分级. RoamSwitch 故意在受信任(家庭)网络上应用 not 进行强化 — 将网关 MAC 固定为永久(这将在路由器重新启动时使 LAN 陷入黑洞)、重新安装 /tmp 和 /dev/shm noexec (这会破坏软件包构建和某些安装程序)以及默认范围下的威胁保护 DNS — 显示为绿色带有解释而不是红色的“未强化”,并指出它会自动应用于不受信任的网络。运行时真相来自 /run/roamswitch/state.json (active_level / network_trusted / fanotify_ready),由守护进程在每个周期写入。 fanotify 项检查防护是否确实被标记并正在运行(/run/roamswitch/fanotify.ready),而不仅仅是内核支持它。暴露端口项读取守护程序的实时配置文件(MCP 硬代码已消失)。
每项强化。 具有自动修复功能的失败项目(内核 sysctl / Yama / 核心转储、/tmp noexec、USB 零信任、网关 ARP 固定、威胁保护 DNS、fanotify 防护关闭时重新启动守护进程)在 GTK 仪表板中带有“🔧 强化”按钮。守护进程以 root 身份运行,因此除了守护进程重新启动之外不需要 pkexec 。
§8
roamswitch-mcp 通过 stdio 进行 JSON-RPC,由 Claude Desktop、Cursor 和 Antigravity 等 AI 客户端生成。每个工具都是只读且完全本地。
| 工具 | 目的 |
|---|---|
| 获取安全报告 | 24项评估、评分、建议 |
| 获取暴露端口 | 暴露的端口及其防火墙阻止状态 |
| 获取守卫状态 | 当前保护级别和防御模块状态 |
| 审核网址安全 | 启发式 URL 安全分析(不获取目标) |
| 审计秘密 | 检测文本中的 API 密钥/秘密(不传输文本) |
| 审计安全日志 | 本地日志/验证日志的聚合 |
| get_quarantine_status / get_canary_status | 检疫和加那利州 |
| 获取应用程序帮助 | 搜索内置知识库 |
| run_active_vuln_scan | 仅限 127.0.0.1 的非破坏性实证漏洞验证。唯一会使用网络的工具,默认关闭,需在设置中主动启用。 |
| run_package_cve_scan | 将已安装的软件包(Mac:Homebrew / Linux:dpkg・dnf・zypper・pacman)与本地 CVE 映射进行比对。完全不产生任何网络通信。 |
| run_package_cve_scan_languages | 将 npm / PyPI / crates.io / RubyGems / Packagist / Go / Maven 等依赖锁定文件与同一本地 CVE 映射进行比对。完全不产生任何网络通信。 |
| verify_fim | 对约 150 处关键系统文件重新计算哈希,并与基线比对以验证是否被篡改。 |
| get_file_scan_guard_status | 返回文件扫描防护(ClamAV)的配置及其所用隔离 Vault 的状态。 |
| get_port_anomaly_incidents | 返回端口异常防护的基线状态、当前自动阻断的端口,以及最近 50 条事件。响应中明确注明:当前自动阻断的端口是不带时间戳的当前状态快照,与带时间戳的事件历史是两回事。 |
| get_ebpf_incidents | 返回 eBPF 运行时防护的当前隔离状态,以及触发隔离的事件历史。 |
| get_resource_guard_incidents | 返回资源耗尽/进程异常检测防护的历史(内存泄漏、崩溃循环),并附置信等级。 |
| get_notification_history | 返回 RoamSwitch 发送的通知(安全日志审计异常、ClickFix 检测等)历史记录,最近1周、最新在前。 |
| get_incident_timeline | 将各防护模块的检测结果合并为单一时间线返回(含进程谱系与 MITRE ATT&CK 标签,实验性)。 |
MCP 服务器自身不打开任何网络连接,仅通过 roamswitchkit 连接守护进程的 Unix 套接字,或直接调用 roamswitch-core 的逻辑。唯一的例外是默认关闭的 run_active_vuln_scan,它会向本机 127.0.0.1 发送非破坏性探测。
在本地 LLM 实战演练中发现并修复的错误归因问题
2026 年 9 月,我们使用本 MCP 服务器让本地 LLM(Qwen 3.8 27B)进行一次事件调查实战演练(fire drill)时,发现模型误读了 get_port_anomaly_incidents 返回的数据,把一个与本次事件无关的旧端口阻断记录,错误地编入了当前正在发生的事件叙述中。原因在于:不带时间戳的「当前阻断端口」快照,与带时间戳的真实事件历史,在同一个响应中未加区分地一并返回。修复方案是新增说明字段,明确标注哪一部分是时间线数据、哪一部分只是快照。修复后,用同一场景对同一模型重新运行,模型引用了说明字段中的文字,明确拒绝将旧端口归因于当前事件。我们通过自动回归测试和真实本地 LLM 复现两种方式,验证了修复的有效性。
§9
RoamSwitch for Linux 使 恰好是一种出站请求 :签名清单的每日更新者 GET (§11)。其他一切——每项评估、检测和审计——都在本地完成。 RoamSwitch for Linux 是专有的免费软件,其源代码未公开,因此以下检查由 lafine 执行;结果在 9.2 中,并且附录 C 中的步骤可以单独从分布式包中重现。
9.1 审核方法
- Crate 隔离 — HTTP/TLS 堆栈仅允许在 一个 crate 、
roamswitch-updater中使用,它构建独立的计时器驱动的更新程序二进制文件。如果roamswitch-core、roamswitch-daemon、roamswitch-app、roamswitch-mcp或roamswitchkit引入reqwest/hyper/ureq/rustls/native-tls/openssl,或者roamswitch-updater之外的任何.rs文件使用TcpStream/,scripts/audit_no_network.sh(在 CI 中运行,门控)将导致构建失败TcpListener/UdpSocket/to_socket_addrs/lookup_host。 - 依赖树检查 — 搜索完整的
Cargo.lock以查找遥测/分析/崩溃报告 SDK(sentry、opentelemetry,...)。 roamswitch-updaterreview — 它的单一请求目标,没有查询字符串/cookies/识别标头,以及它下载的所有内容的 Ed25519 验证。- 子进程枚举 - 每个
Command::new(...)调用站点都经过审查。
9.2 结果
| 审核项目 | 结果 |
|---|---|
| HTTP / TLS 堆栈 | 仅存在于 roamswitch-updater (ureq + rustls) 中。不在 core / daemon / app / mcp / kit 中 |
| 遥测/分析/崩溃报告 SDK | 不存在任何地方 |
TcpStream / TcpListener / UdpSocket | 在 roamswitch-updater 之外没有出现一次 |
| 使用的 Socket API | AF_UNIX(本地IPC); AF_NETLINK(NFQUEUE 判决通道、nftables 和 ip neigh 控制 — 无出口); AF_INET only 在 roamswitch-updater 中。 VPN 隧道 (§4) 启用后,是 wg-quick(WireGuard 后端)或 tailscaled(Tailscale 后端)——子进程,而不是守护进程——将流量发送到用户自己的端点/tailnet |
| 每日更新 | 每约 24 小时 1 个 GET https://lafine.net/updates/v1/manifest。没有查询字符串,没有cookie,通用用户代理,没有标识符。所有有效负载均经过 Ed25519 验证。 updates.enabled = false 完全禁用它 |
| VPN 隧道 (§4) | 默认关闭。启用后,wg-quick 会构建一条到 用户自己的 .conf 端点的 WireGuard 隧道 — 与 lafine 无关。通过 getent ahosts (glibc NSS) 解析端点主机名,而不是 Rust 套接字 API |
| 许可证激活 | 离线Ed25519签名验证。与激活服务器没有通信 |
RoamSwitch for Linux 只发起一种出站请求 - 每日仅接收签名检测数据的下载,不包含有关用户或计算机的信息。产品中的任何其他内容都不会与外部服务器联系。
9.3 面向外部的操作的完整列表
每个可能导致数据包直接或间接离开本地计算机的操作:
| # | 操作 | 目的地 | 触发 | 发送了什么 |
|---|---|---|---|---|
| 1 | 每日更新程序 — GET /v1/manifest,以及版本更改时的提要文件 | lafine.net/updates/v1 (CDN) | systemd 计时器,每约 24 小时一次,随机延迟。禁用 updates.enabled = false 或 roamswitch-update.timer 时关闭 | 普通的 HTTPS GET:没有查询字符串,没有 cookies,通用 User-Agent, 与用户或机器无关。接收签名的清单 + feed |
| 1a | freshclam(ClamAV 自己的更新程序),安装 ClamAV 时由每日更新程序调用 | ClamAV 项目的 CDN | 作为 #1 的一部分 | ClamAV 本身的流量(一个单独的项目); RoamSwitch 只启动它 |
| 2 | ping -c1 -W1 <网关 IP> | LAN 默认网关(链路本地) | 在 ARP 欺骗检测期间刷新 ARP 缓存 | 仅 ICMP 回显请求。无 DNS 解析,无负载 |
| 3 | 启动系统浏览器 (xdg-open) | https://lafine.net/...(产品站点) | 仅当用户单击按钮(例如“打开发行说明”)时 | RoamSwitch 进程未打开套接字;它将 URL 传递给操作系统浏览器 |
| 4 | 解析 dns | —(无通讯发生) | 不受信任网络上的威胁保护 DNS(选择加入)。 <iface> 是每个带有默认路由的物理网卡(同时有线+无线;lo / tailscale* / wg* / docker* / veth* 除外);当 dns_scope = always_on 时也应用于可信网络;协调每个哨兵周期 | 它仅更改操作系统解析器设置; RoamSwitch 本身不发送 DNS 查询 |
| 5 | 包管理器更新 | 发行版的 apt / dnf / zypper 存储库 | 当用户运行apt upgrade等,或单击GUI“立即升级”按钮(通过pkexec运行apt-get / dnf / zypper) | RoamSwitch 读取本地 apt-cache policy 输出并启动包管理器;与拉芬无关 |
| 6 | VPN 隧道 (§4) — WireGuard 后端:隧道 wg-quick 构建/Tailscale 后端:tailscale set --exit-node 通过出口节点路由所有流量 | 用户自己的VPN端点/tailnet(WireGuard:.conf中的端点;Tailscale:所选的出口节点和DERP中继) | 当 vpn_on_untrusted_enabled 并且网络不受信任时。默认关闭 | 加密的 WireGuard / Tailscale 流量;目的地是用户自己的VPN服务器/tailnet,与lafine无关。通过 getent ahosts (glibc NSS) 进行 WireGuard 端点解析; Tailscale 由 tailscaled 驱动(一个单独的进程) |
所有其他 RoamSwitch 功能 — nftables 控制、邻居表的 ARP/NDP 固定(ip neigh,无数据包出口)、链路保护(NFQUEUE,仅 AF_NETLINK)、端口扫描(解析 ss 输出)、ARP 监控、健康评估、熵监控、金丝雀、fanotify、USB 监控(evdev 抓取)、隔离、日志审计、URL 和秘密审计 — 仅使用 本地文件读取来完成以及子进程输出的分析。 §4 VPN 隧道是唯一的“用户明确配置的出站通信”,其目的地是用户自己的 VPN 服务器 - 没有数据流向 lafine 的基础设施。
§10
- 没有帐户 — 无需用户注册,无需登录,也无需请求电子邮件地址。
- 配置仅在本地 — 以明文形式存储在
~/.config/roamswitch/config.json处,并且永远不会离开机器。没有云同步。 - 评估结果仅在本地 — 安全报告和日志审核结果仅显示在屏幕上,并且如果用户选择,还可以导出到本地文件。
- 许可证密钥已离线验证 — Pro / Business 许可证令牌包含 Ed25519 签名并使用公钥进行验证。不会向 lafine 服务器发出任何查询。
- 没有崩溃报告 — 出现紧急情况时,不会向任何地方发送堆栈跟踪。
- 每日更新程序不会发送任何有关您的信息 — 没有查询字符串、没有cookie、没有设备或安装标识符、通用
User-Agent。这是下载,而不是报告。链接防护判决、阻止的主机和扫描结果仅写入本地活动视图/日志,从不传输。
§11
| 版本 | 对于 | 价格 | 许可证 |
|---|---|---|---|
| 社区版 | Linux个人使用 | 免费 | 专有免费软件(捆绑 EULA) |
| 业务(计划) | 车队管理、策略分发、签署内部 apt、组织 SLA | 付费 | 商业许可证。相同的二元+层门 |
macOS RoamSwitch Pro Lifetime 的购买者可以在自己的 Linux 计算机上免费获得商业功能。
分销和供应链
| 频道 | 详情 |
|---|---|
| APT | https://lafine.net/apt (stable main)。 Release 使用 RSA-4096 密钥签名 |
| 转速 | https://lafine.net/rpm(单独的 Fedora / openSUSE)。 repomd.xml GPG 签名 |
| 尿素尿率 | roamswitch-bin(获取 GitHub 发布 tarball,验证 sha256) |
| 压缩包 | GitHub 上的 amd64 / aarch64 .tar.gz + .sha256 版本 |
- 固定 glibc 地板 — 发行版二进制文件构建在 Debian 12(书虫、glibc 2.36)容器内。这可以防止 Debian 12 / Ubuntu 22.04 及更高版本上的符号版本不匹配。如果引入需要超过 2.36 的依赖项,“验证 glibc 底层”CI 步骤将导致构建失败。
- 签名密钥管理 — apt/rpm 存储库签名的私钥仅存在于 GitHub Actions 加密密钥中;仅将公钥提交到存储库。
11.3 每日更新程序
roamswitch-updater 是由 systemd 计时器(roamswitch-update.timer、具有随机 3 小时延迟的 OnCalendar=daily 和 Persistent=true)运行的独立二进制文件。它是到达网络的唯一组件。每次运行:
GET https://lafine.net/updates/v1/manifest及其独立签名。- 使用专用的威胁源签名密钥验证清单的 Ed25519 签名 - 与 Sparkle 应用程序更新密钥分开。服务器端 CI 每天都会对 feed 进行重新签名,以便私钥存在于 CI 秘密中;将其范围限定为提要意味着泄漏只会让攻击者提供错误的阻止列表(污水坑/警告 - 没有代码路径,没有应用程序更新路径)。应用程序更新签名密钥永远不会离开 macOS 钥匙串。未验证的清单将被丢弃 - 更新程序永远不会回退到未签名的数据。
- 如果威胁源版本发生更改,请下载该源,验证其 SHA-256 和 Ed25519 签名,并为链接防护自动安装它 (§4)。
- 如果安装了 ClamAV,则调用
freshclam。 - 记录最新的应用程序版本以获取通知(更新程序 never 安装应用程序;在 Linux 上,用户运行
apt upgrade)。
任何步骤的失败(脱机、错误签名、freshclam 丢失)都会记录为 updates.lastError 并且进程仍然退出 0,因此计时器运行永远不会生成 systemd 失败通知。整个机制被 updates.json 中的 updates.enabled = false 或 systemctl disable --now roamswitch-update.timer 禁用;然后,该产品将根据包中捆绑的数据运行。威胁源也在 包内 中提供,因此全新安装在更新程序运行之前就有一个工作源,并且 apt upgrade 即使在禁用更新程序的情况下也会刷新它。
§12
- OS:Ubuntu 22.04 / 24.04 及更高版本、Debian 12 及更高版本、Linux Mint / Pop!_OS / Primary / Zorin、Raspberry Pi OS 64 位 (Bookworm)、Ubuntu for Raspberry Pi
- 架构:x86_64 / aarch64
- Required:
systemd、nftables、iproute2(ip neigh— ARP/NDP 锁)(由.deb/.rpm依赖项自动拉入) - 可选:
clamav(恶意软件扫描)、nmcli(Wi-Fi 加密检测)、libnotify(桌面通知)、wireguard-tools(VPN 隧道的 WireGuard 后端 + 终止开关,§4)、tailscale(其 Tailscale 后端,§4)、polkit/pkexec(从 GUI 运行升级) - 不支持:非systemd发行版(Alpine / Void / Devuan)
- Display:1280×720或更大(窗口和字体自动缩放到屏幕尺寸,支持小型Raspberry Pi显示器)
附录 A
| 板条箱 | 目的 | 网络 |
|---|---|---|
| serde / serde_json | 配置和IPC的序列化 | 无 |
| 东京 | 异步运行时(features = ["full"],但仅使用 Unix 套接字) | 无(无 TCP) |
| 跟踪/跟踪订阅者 | 日志记录(本地/日志) | 无 |
| 这个错误/无论如何 | 错误处理 | 无 |
| 正则表达式 | 字符串解析(诊断、CSS 缩放) | 无 |
| 网址 | URL 解析(audit_url_safety 启发式) | 无(不获取) |
| 计时 | 日期/时间处理 | 无 |
| ed25519-dalek | 离线验证许可证签名 | 无 |
| Base64/SHA2 | 许可证令牌、金丝雀哈希 | 无 |
| 库 | chown / kill / 系统调用 | 无 |
| 伊德娜 | 解码 IDN/punycode 主机以进行同形异义词检测 | 无 |
| nfq(仅限守护进程) | 链接防护的 NFQUEUE 绑定 — 纯 Rust,AF_NETLINK | 无(判定通道,无出口) |
| 打开 | 调用 xdg-open 表示“开放发行说明” | 进程本身不进行通信 |
| GTK3 / ksni(仅限应用程序) | GUI 和托盘 | 无 |
| ureq + rustls(仅限 roamswitch-updater) | 每日更新者的一次 HTTPS GET | 树中唯一的 HTTP/TLS 堆栈;仅限于更新程序二进制文件 |
roamswitch-core、roamswitch-daemon、roamswitch-app、roamswitch-mcp 或 roamswitchkit 中未出现 HTTP 客户端、TLS 堆栈或遥测 SDK。 ureq + rustls 仅出现在 roamswitch-updater 中(scripts/audit_no_network.sh 在 CI 中强制执行此操作)。任何地方都没有遥测 SDK。
附录 B
table inet roamswitch { # 配置文件防火墙(Air-Gap / balanced / lockdown)
chain input {
type filter hook input priority -100; policy <accept|drop>;
iif "lo" accept
ct state established,related accept
# balanced/lockdown:其余全部 drop
}
chain output {
type filter hook output priority -100; policy <accept|drop>;
# 仅在 Air-Gap 时 policy drop,仅允许 lo 与 ct established
}
}
table inet roamswitch_linkguard { # §4.5 — 独立于配置文件表
chain output {
type filter hook output priority 0; policy accept;
udp dport 53 queue num 92 bypass
tcp dport 53 queue num 92 bypass
tcp dport { 80, 443 } ct state new,established queue num 92 bypass
}
}
table inet roamswitch_dev_guard { # §4 端口异常防护/手动端口隔离,独立的表
chain input {
type filter hook input priority -10; policy accept;
iif != "lo" tcp dport { … } drop
}
}
table inet roamswitch_vpn { # §4 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; … } # 对称
}
table inet roamswitch_ts { # §4 Tailscale 后端断网开关(更宽松)
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(v6 一侧同样对称)
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 # WireGuard 直连
tcp dport 443 accept # DERP 回退
ct state established,related accept
}
chain input { type filter hook input priority -150; policy drop; … } # 对称
}
网关/IPv6 路由器/DNS 解析器 (§4) 的 ARP/NDP 固定是在内核邻居表 (ip neigh replace … nud permanent) 中完成的,而不是在 nftables 中完成的。固定条目记录在 /var/lib/roamswitch/arp_lock.json 中。
附录 C
本文档中的每项声明都可以在您自己的机器上进行验证。以下命令在安装了 roamswitch 软件包的主机上运行。
C.1 出站流量验证
守护进程/应用程序/MCP二进制文件链接没有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
# → 无输出。HTTP/TLS 协议栈仅静态链接到 roamswitch-updater。
正在运行的守护进程没有互联网套接字:
sudo lsof -p "$(pgrep -x roamswitch-daemon)" -a -i
# → 无输出 = 没有任何 TCP/UDP 互联网连接
sudo lsof -p "$(pgrep -x roamswitch-daemon)" -a -U | grep roamswitch
# → 仅 /run/roamswitch/roamswitch.sock(链路防护会额外使用 AF_NETLINK 套接字,
# 但这是内核通道,并非互联网套接字)
RoamSwitch 唯一的出口是每日更新:
watch -n 5 'sudo ss -tupn | grep -E "roamswitch-(daemon|app|mcp)" || echo "(none)"'
# → 始终为 "(none)"。这三者从不打开任何互联网套接字。
systemctl list-timers roamswitch-update.timer
sudo systemctl start roamswitch-update.service
journalctl -u roamswitch-update.service -n 20 --no-pager
# → 仅向 lafine.net/updates/v1 发起一次 HTTPS GET,随后退出。
禁用它并确认完全沉默:
sudo systemctl disable --now roamswitch-update.timer
# 或将 /var/lib/roamswitch/updates.json 编辑为 "enabled": false
sudo strace -f -e trace=network -p "$(pgrep -x roamswitch-daemon)" 2>&1 | grep -i 'connect('
# → 仅 connect 到 AF_UNIX / AF_NETLINK。AF_INET / AF_INET6 从未出现。
C.2 可重复的防御验证(Docker 测试套件)
RoamSwitch 的所有检测、控制和防御机制(气隙执行、防火墙破坏后的自我修复、勒索软件金丝雀篡改、/tmp noexec、Yama LSM、同形异义词网络钓鱼拦截和凭证泄漏检测 — 总共 17 项) 可以使用公共支持存储库中提供的官方 Docker 测试套件在隔离容器中 100% 安全地复制和验证,而不影响主机网络或文件(roamswitch-支持
# 获取并复现验证套件(使用官方发行包,在隔离容器内自动验证)
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
测试容器自动从 APT 存储库 (lafine.net/apt) 获取官方发行包,并在隔离的网络和挂载命名空间内评估以下每个系统测试项目(SP-1–11、PENT-1–7)。
(1) 自主网络防御和气隙自愈
| 身份证号 | 场景 | 验证方法 | 通过标准 |
|---|---|---|---|
| SP-3 | 气隙启用 → 出口下降 | 发送 enable_air_gap IPC | policy drop 应用于输出挂钩;所有出站流量均被阻止 |
| SP-3b | 防火墙强制清除和自我修复 | 通过 nft delete table inet roamswitch 清除规则 | Anti-Clobber 自愈功能可在 5 秒内恢复掉落规则 |
| SP-3c | 跨守护进程重新启动时失败关闭 | 硬终止守护进程 (SIGKILL) 中气隙并重新启动 | 出口丢弃策略在重启后无缝持续 |
| SP-3d | 授权气隙恢复 | 发送授权disable_air_gap IPC | 取消丢弃策略,删除所有标记文件,恢复出口 |
| SP-8 | 预防性 ARP/NDP 固定 | 在隔离的netns网关环境中启用→发送欺骗性的免费ARP | 网关邻居条目仍为 PERMANENT;欺骗 ARP 被忽略 |
| SP-9 | VPN 终止开关 (WireGuard) | 单独网络中的 Live WireGuard 服务器,导入 .conf → vpn_up | 握手通过inet roamswitch_vpn完成;隧道死亡时零泄漏 |
| SP-10 | VPN 终止开关(Tailscale) | 存根 tailscale CLI + 单独的网络,tailscale0 IF | 隧道外明文 TCP/80 和本地 DNS 被阻止;允许 DERP 和 MagicDNS |
(2) 内核和挂载强化
| 身份证号 | 场景 | 验证方法 | 通过标准 |
|---|---|---|---|
| SP-11a | 网络系统强化 | 运行 apply_kernel_sysctl_hardening IPC | rp_filter=1、tcp_syncookies=1、accept_redirects=0 达到目标值 |
| SP-11b | 内存保护和 Yama LSM | 验证 ptrace_scope 和 suid_dumpable | kernel.yama.ptrace_scope=1(阻止窥探),fs.suid_dumpable=0(无核心转储) |
| SP-11c | /tmp 和 /dev/shm noexec 防御 | 运行 apply_mount_hardening IPC | /tmp 和 /dev/shm 重新安装 noexec,nosuid,nodev;执行被拒绝 |
| SP-11d | 保留合法用户执行 | 执行标准主目录 (~/) 中的脚本 | 不受阻碍地运行,不会破坏合法的开发人员/管理工作流程 |
(3) 多层恶意软件和勒索软件防御和金丝雀
| 身份证号 | 场景 | 验证方法 | 通过标准 |
|---|---|---|---|
| SP-1 | 金丝雀篡改检测 | 截断或重命名 ~/Documents/ 中的诱饵金丝雀文件 (.xlsx) | 在几秒钟内检测到篡改,立即触发气隙 |
| SP-2 | 勒索软件批量加密突发检测 | 单个进程快速生成多个高熵文件 | 违规工作进程被 SIGSTOP 冻结;气隙触发 |
| SP-2b | shred 上没有误报 | 跨测试文件运行 shred -n 3 -u | 允许列表匹配可防止误报检测或冻结 |
| SP-2c | 关键流程从未冻结 | 在突发检测期间验证冻结目标逻辑 | 关键守护进程(dockerd、systemd、containerd)永远不会被冻结 |
| SP-4 | EICAR 测试字符串仅用于通知 | 将 EICAR 测试字符串写入 ~/Downloads/ | 不发出通知,仅记录到通知历史;文件永远不会被自动隔离 |
| SP-4b | 真正的恶意软件签名首先确认 | 在监视文件夹外写入真实签名 | 引发模式批准对话框;仅在用户确认后才隔离 |
| SP-4c | Quarantine Vault 权限强化 | 检查 Vault 目录和示例文件的权限 | 保管库目录设置为 0700,隔离样本设置为 0400 |
| SP-4d | 特权IPC安全原始文件恢复 | 发送 restore_quarantine_file IPC | 文件已使用用户权限 (0644) 恢复到原始路径并添加到排除项 |
(4) 被动链接防护和网络钓鱼防御
| 身份证号 | 场景 | 验证方法 | 通过标准 |
|---|---|---|---|
| SP-7 | mode = block 严格执行 | 连接到 feed-listed 或 blocklistExtra 主机 | 在 TLS 层丢弃(SNI 解析);未列出的安全主机达到 HTTP 200 |
| SP-7b | mode = warn 故障关闭保持 | 连接到警告主机,无用户响应(超时 8 秒) | 超时时丢弃数据包 (fail-close);仅在明确批准的情况下才允许 |
| SP-7c | mode = off 完整拆解 | 禁用链接防护 | inet roamswitch_linkguard 表已完全卸载 |
(5) BadUSB 缓解和设备控制
| 身份证号 | 场景 | 验证方法 | 通过标准 |
|---|---|---|---|
| SP-6 | 连接键盘祖父 | 检查守护进程启动时连接的键盘 | 现有设备不再适用;仅抓取后续热插拔 |
(6) Docker环境渗透测试套件(17项检查)
| 身份证号 | 攻击向量 | 模拟细节 | 防御/检测标准 |
|---|---|---|---|
| 彭特-1 | C2 渗漏 | 在气隙下尝试将出口 HTTP/TCP 发送至 C2 | 所有数据包均在 netfilter 处丢弃;零泄漏 |
| PENT-1b | 固件强制删除 | 使用 nft delete table inet roamswitch 清除规则 | 防破坏自我修复可在几秒钟内恢复规则 |
| PENT-1c | 气隙安全恢复 | 发出经过验证的 disable_air_gap IPC | 删除删除规则;标记已删除;连接恢复 |
| PENT-2 | /tmp 后门 | 在 /tmp 中执行删除的后门脚本 | 被 noexec 安装保护 (Permission denied) 拒绝 |
| PENT-2b | /dev/shm 共享内存 | 执行放置在 /dev/shm 中的二进制有效负载 | 被 noexec 坐骑防御阻挡 |
| PENT-2c | 用户空间保留 | 在合法用户目录(/home/tester/)中运行脚本 | 执行畅通无阻;零工作流程中断 |
| PENT-3 | ptrace内存窥探 | PTRACE_ATTACH 目标提取秘密/令牌 | 被 Yama LSM kernel.yama.ptrace_scope=1 拒绝 |
| PENT-3b | 核心转储秘密抓取 | 触发进程崩溃以获取内存核心转储 | 由 fs.suid_dumpable=0 阻止 |
| PENT-4 | 金丝雀篡改 | ~/Documents/ 中的清零金丝雀诱饵文件 | 实时检测大小异常和哈希变化 |
| PENT-4b | 金丝雀批量擦拭 | 大规模删除金丝雀诱饵文件 | 立即检测到文件丢失,引发事件 |
| PENT-5 | IP地址欺骗 | 发送带有欺骗源IP的数据包 | 由 rp_filter=1 删除(反向路径过滤器) |
| PENT-5b | SYN 洪水 DoS | 淹没半开 TCP 连接 | 受 tcp_syncookies=1 保护的状态表 |
| PENT-5c | ICMP 路由劫持 | 注入伪造的 ICMP 重定向数据包 | 由于 accept_redirects=0 被忽略 |
| PENT-6 | 西里尔文同形异义词网络钓鱼 | 导航至 gооgle.com(西里尔字母同形异义词) | 由 LinkGuard 识别,得分 40(危险/块) |
| PENT-6b | 原始 IP 和明文流量 | 直接出站连接到原始 IP | 准确标记为安全风险因素 |
| PENT-7 | OpenAI API 密钥泄露 | 在代码或日志中公开 sk-proj-... 令牌 | 通过香农熵分析检测并屏蔽 |
| PENT-7b | GitHub 令牌泄漏 | 在代码中公开 ghp_... 个人令牌 | 通过token模式和熵验证进行匹配 |
| PENT-8 | 内核 LPE(碎片间隙)抢占 | 非特权 unshare -U -r(CVE-2026-53362 尝试) | 立即被 user.max_user_namespaces = 0 (Operation not permitted) 拒绝 |
| PENT-9 | eBPF 威胁传感器和流程冻结 | 通过 Falco eBPF 套接字注入关键/紧急警报 | 进程在几毫秒内通过 SIGSTOP 冻结(状态:Ts);已部署气隙 |
(7) 渗透测试总结(独立强化版与 eBPF 增强版)
在隔离的 Docker 环境中执行的完整渗透测试的结果摘要(目标:带有 RoamSwitch 客户端版本的 Ubuntu 24.04;攻击者:Nmap/curl/hping3;请参阅官方报告 docs/CLIENT_PENTEST_REPORT.ja.md):
| 验证类别 | 不带 Falco(独立强化) | 使用 Falco(eBPF 增强型) | 辩方判决 |
|---|---|---|---|
| 外部端口扫描电阻(Nmap SYN) | PASS(100% 过滤/放入 Lockdown) | PASS(100% 过滤/放入 Lockdown) | 总外部隐身 |
| 外部直接访问截止(TCP/80、3306) | PASS(超时下降/零响应) | PASS(超时下降/零响应) | 零入境暴露 |
| 内核 LPE(碎片间隙)缓解 | PASS(unshare 通过 sysctl 阻止) | PASS(eBPF 警报 + SIGSTOP 冻结 + 气隙) | 深度防御验证 |
| 未授权端口检测(异常) | PASS(0.0.0.0 绑定标记为高风险) | PASS(0.0.0.0 绑定标记为高风险) | 100% 检测和引导 |
| 日志膨胀和 CPU 开销预防 | 零日志占用(已卸载) | PASS(95% 噪音消除/2MB 缓冲区/50MB 上限) | 桌面优化 |
C.3 已知限制
| 局限性 | 影响 | 缓解措施 |
|---|---|---|
在某些内核上,tmpfs (/tmp) 上的 FAN_MARK_MOUNT 不提供任何事件 | fanotify 不监控 /tmp 下的文件写入 | /tmp 是临时文件夹,不是受监控的用户文件夹;勒索软件的目标是 ~/。显式记录标记失败 |
| 容器 Overlayfs / tmpfs 上没有 fanotify 交付 | 无法验证容器内熵爆发检测 | 在真实硬件 (ext4) 上进行验证;线束通过探针自动跳过 |
RoamSwitch 的 nftables 操作与 Docker 的 iptables-nft 共享 netfilter 空间 | RoamSwitch 规则操作可能会干扰 Docker 的 NAT 规则 | 记录为操作警告;在专用 inet roamswitch 表中隔离,但应避免诸如 nft flush ruleset 之类的整个规则集操作 |
| 链接防护只能通过 TLS SNI 查看浏览器的 DoH;加密的 Client Hello (ECH) 也隐藏了这一点 | 一旦 ECH 被广泛部署,使用 ECH 的连接到支持 ECH 的站点就无法通过主机名来识别 | 本地阻止列表和启发式仍然作用于 DNS/HTTP 和非 ECH TLS; DNS 威胁保护(§4.5,选择加入)涵盖解析器路径 |
每日更新程序的新鲜度取决于 updates.enabled = false 时的发布节奏 | 关闭更新后,提要仅与最后一个 apt upgrade 一样最新 | 最严重的检测(品牌同形异义词)无需馈送;选择重新加入或启用威胁防护 DNS,以获得始终最新的覆盖范围 |