Lafine系统设计·技术文件

适用于 Linux 的 RoamSwitch
架构与安全白皮书

解释 RoamSwitch for Linux 在其权限边界上执行的操作的技术文档。它避免了营销语言;可以根据分布式包的实际行为和附录中的自检来检查声明。

版本 v1.11.1 适用对象 RoamSwitch for Linux 1.11.1(内核 LPE 加固/eBPF 运行时防护/攻击进程 SIGSTOP 冻结/Falco 低负载优化/DNS 隧道检测/取证证据保全/蜜令牌/漏洞检测 pass_age 追踪/NIST CSF・CIS Controls 映射/诊断复评/MCP 配置审计) 要求 systemd + nftables 发布日期 2026-10-08

§1

RoamSwitch for Linux 是一款常驻网络安全和系统诊断应用程序,它将 macOS RoamSwitch 的零信任网络防御模型引入 Linux(systemd + nftables 环境)。

此版本针对客户端计算机(工作站、笔记本电脑、单板计算机),并假设机器在网络之间移动时遵循机器的自主行为。强化服务器/无头基础设施版本(RoamSwitch 服务器版本 - 始终在线默认拒绝、关键路径 FIM 完整性、远程 Webhooks 和静态策略实施)作为专用服务器版本提供。

它通过网关 MAC 地址识别当前连接的 Wi-Fi / 有线网络,并自主切换 nftables 防火墙配置文件。此外,它在不受信任的网络上提供三层中间人攻击(MITM)防御:VPN 隧道 + 终止开关(WireGuard 或 Tailscale,主要防御)、预防性 ARP/NDP 锁定和 ARP 欺骗检测。同时还内置勒索软件行为检测(fanotify + 香农熵 + 金丝雀)、对钓鱼连接发出警告或予以阻断的被动链路防护(NFQUEUE + SNI)、端口异常防护、BadUSB 缓解、内核本地权限提升(LPE)缓解(作为 Frag Gap CVE-2026-53362 的缓解措施,可选择启用在不受信任的网络上拒绝非特权用户命名空间,默认关闭)、与 eBPF 运行时威胁传感器(Falco / Tetragon)联动,以毫秒级冻结攻击进程(SIGSTOP)并自动执行 Air-Gap 隔离、27 项安全健康诊断,以及模型上下文协议(MCP)服务器。

本白皮书说明架构、威胁模型和各防御机制的设计,以及“完全本地处理。默认情况下,唯一离开本机的是对签名检测数据的请求,其中不包含任何用户信息”这一隐私原则如何在代码层面得到保证。需要运维人员明确启用的可选功能(如事件转发)是此默认行为的例外,均已列在下表和 §9.4 中。文中避免宣传性措辞,所述内容可通过分发软件包(二进制)的实际行为以及本书附录的自检来验证。RoamSwitch for Linux 是专有免费软件,不公开源代码(§11)。

设计原则
原理详情
零遥测本产品默认不会向外部发送任何有关用户或设备的信息。不存在遥测、分析、崩溃报告、使用统计、远程配置或许可证激活服务器。唯一可能产生通信的,是运维人员明确启用的可选功能,已列在下一行“默认行为的例外”和 9.4 中。
默认行为的例外(均为可选启用)只有在运维人员明确启用时,以下功能才会对外通信。目的地仅限运维人员所配置的,没有内置目的地。(1) 事件转发(forward_enabled,默认 OFF)——将检测事件转发到运维人员指定的 syslog / JSON Lines 文件 / 签名 Webhook。(2) Fleet(Business 层,仅设备端代码)——未注册的设备不发送任何内容,也不解析 Fleet 主机名;管理服务尚未部署或发布。包括由用户操作触发的更新检查在内,其余情况均列于 9.4。
仅接收更新本产品默认发出的对外通信,只有每天一次对固定 URL(lafine.net/updates/v1)上已签名清单的 GET 请求。如果已安装 ClamAV,每次运行时还会启动 ClamAV 自带的更新工具 freshclam,但这部分通信属于 ClamAV 一侧,已在 §9.4 中单独列出。从清单中获取的内容包括:链接防护的欺诈网站列表、本体最新版本、内核与操作系统软件包(Debian/Ubuntu/Arch/RHEL 系/SUSE)及语言软件包(npm/PyPI/crates.io/RubyGems/Packagist/Go/Maven)的 CVE 映射、实证型漏洞检测的 CVE 映射,以及用于仿冒包名(typosquatting)检测的热门 npm 包名列表。每天获取的只有清单及其签名(2 次 GET)。链接防护列表、实证型漏洞检测的 CVE 映射和热门 npm 列表只在清单版本更新时获取,内核和软件包的 CVE 映射只在距上次更新约 30 天后才获取。Ubuntu 的 CVE 映射只有运行受支持版本 Ubuntu 的设备才会获取;尚未安装的设备不必等 30 天,而是在下一次每日运行时获取。请求不含查询字符串和 Cookie,能表明软件身份的标头只有固定的 User-Agent: RoamSwitch-Updater,也不包含任何用户信息。设置 updates.enabled = false 即可完全停止;此时仅使用内置数据运行,只要不启用可选加入的功能,对外接触为零。
完全本地处理每个评估、检测和审核都在本地计算机上运行完成。 URL 安全分析和秘密扫描会启发式地评估其输入,而无需将其传输到任何地方。
最小权限特权操作仅限 root 守护进程执行,UI、CLI 和 MCP 通过 Unix 域套接字 IPC 提出请求。守护进程默认不打开 TCP/UDP 套接字(运维人员启用的可选功能除外,见 §9.4)。链接防护仅使用 AF_NETLINK 的 NFQUEUE verdict 通道(内核 ↔ 用户空间,不发送数据包)。会改变状态的 IPC 调用仅接受来自 root 或拥有本地登录会话的 UID 的请求(自 1.10.4 起;只读调用不变)。
失败关闭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 实现,软件包中包含 8 个可执行文件(roamswitch-daemon、roamswitch-app、roamswitch(CLI)、roamswitch-mcp、roamswitch-updater、roamswitch-honeytokens、roamswitch-incident-capture,以及实验性且默认禁用的 roamswitch-xdp-gate)。共用逻辑位于 roamswitch-core 库中,roamswitchkit 是客户端 SDK。与守护进程的所有通信都通过 /run/roamswitch/roamswitch.sock(Unix 域套接字)。守护进程、应用程序和 MCP 服务器中均不存在 TCP/UDP 监听套接字。应用程序 bind 的唯一对象,是用于防止重复启动的单个 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 为可选设置,默认关闭)
  · eBPF 运行时联动(/var/run/roamswitch/events.sock)
  · 以毫秒级 SIGSTOP 冻结攻击进程并联动 Air-Gap
  · USB 监控(evdev EVIOCGRAB — BadUSB) / 批准队列
  · Ed25519 许可证验证

roamswitch-updater  独立的 systemd 定时器 helper。是唯一拥有
                    HTTP/TLS 协议栈的 crate(每日获取一次签名清单)
roamswitch-honeytokens          由守护进程启动的辅助进程(部署并监控诱饵凭据)
roamswitch-incident-capture     由守护进程启动的辅助进程(隔离时采集证据包)
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(目录权限为 0755,套接字权限为 0666)。访问控制不依赖文件权限,而是通过确认连接来源来实现:通过 SO_PEERCRED 获取 UID,拒绝系统账户(UID 1 至 999 以及 nobody)的连接,改变状态的调用仅限 root 或持有本地登录会话的 UID(§1)。用户配置以纯文本 JSON 形式存储在 ~/.config/roamswitch/config.json。守护进程以 root 权限扫描并读取 /home/*/.config/roamswitch/config.json(因为守护进程以 HOME=/root 运行),只读取归该账户自身所有、不是符号链接且其他用户无法写入的文件。配置永远不会离开本机;没有账户注册,也没有云同步。

§4

sentinel 循环(3 秒周期)从 /proc/net/arp 和 ip neigh 读取默认网关的 MAC,并将其与 config.json 中注册的 MAC 进行匹配。使用 MAC 而不是 SSID,是因为 SSID 很容易被伪造。配置文件切换时的桌面通知会说明切换的原因(“因为是未注册的网络”/“您对已注册网络‘〇〇’的设置”/“因为未连接网络”)。

型材
公司简介情况nftables 行为
开放(可信)已注册的家庭或公司网络policy accept。允许本地交通。
平衡未注册但配置为相对安全默认拒绝入站。仅允许已建立的连接、lo、ICMP/ICMPv6。监测公开端口暴露。
锁定(离开保护)公共 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 的规则,哨兵循环会在几秒钟内重新应用。
  • 低置信度触发由短计时器解除。在没有警告上下文的情况下按下的手动 Air-Gap(GUI 按钮或 roamswitch airgap)等低置信度触发,会在短计时器(airgap_low_confidence_secs,默认 600 秒)到期后完全恢复,以免因误操作或误报使设备长时间断网。lockdown 之外的 ARP 异常、端口异常以及 eBPF 的 Error 及以下级别,在通用状态机中同样归为低置信度,但在客户端中它们不会自动触发 Air-Gap,仅限于通知、记录,或阻断相关端口。
  • 高置信度触发不会悄悄恢复。金丝雀被篡改、eBPF 的 Emergency/Alert(Critical 为无法对单个进程实施隔离时)、lockdown 期间涉及网关的 ARP 欺骗,以及为响应 ARP 警告而按下的 Air-Gap(仅在选择启用时,还包括执行记录器的高严重度规则,§5),无论过去多久都不会自动恢复。短计时器到期后,每个周期都会重新确认原因是否仍然存在(被冻结的 PID 是否仍存活、金丝雀是否再次被改写、ARP 异常是否持续等),只有确认原因已消失时才会附带通知地解除。无法确认的情况(包括无法判定)按“未解决”处理。勒索软件的熵突增在对相应进程执行 SIGSTOP 冻结并发出通知之外,也会触发 Air-Gap,并同样按高可信度触发条件处理。
  • DEGRADED(降级)模式 — 高置信度隔离达到上限时间(airgap_hard_cap_secs,默认 3600 秒,0 = 无上限)后,如果用户未解除且未确认原因已消失,则转入 DEGRADED。客户端版的 DEGRADED 与 FULL 一样丢弃除回环外的所有流量(已建立的连接也不放行),区别在于绝不会因时间流逝而自动放开。解除方式只有本地用户(GUI 的解除按钮或 roamswitch emergency-restore),或确认原因已消失的复核。Air-Gap 默认还会通过 rfkill 关闭 Wi-Fi 无线(airgapAutoRfkill),DEGRADED 中除非将 airgap_degraded_lift_rfkill 设为 true,否则保持关闭。
  • 转换的记录与通知 — 从 FULL 转入 DEGRADED、各类自动解除以及用户的解除,都会记录到事件时间线并弹出桌面通知。隔离状态保存在 /run/roamswitch/airgap_isolation.json,守护进程重启后仍然保持(重启会使 ARP 复核的连续计数从头开始,这偏向于继续保持隔离,即 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 为 policy accept 且无规则。balanced / lockdown 为 policy drop(仅 balanced 允许 ICMP、ICMPv6)
    }
    chain output {
        type filter hook output priority -100; policy <accept|drop>;
        # Air-Gap(隔离)期间重建同一张表。input / output 均为 policy drop,仅放行 lo(已建立的连接也不放行)
    }
}

被动链接防护(网络钓鱼连接检测)

该守护进程监视出站连接的去向,并标记(或者,对于狭窄的“明显的网络钓鱼”集,阻止)与危险主机的连接。它取代了旧的粘贴 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-closed(丢弃数据包,且不缓存判定)。“确认无响应”不会被视为“允许”,下次访问时会再次确认。即使在 block 模式下,每个主机也只显示一次通知和允许对话框(浏览器对同一主机的大量连接不会使通知泛滥)。对于危险目标的批准对话框,默认按钮是“继续阻断”,“允许”则采用不显眼的配色。被阻断的主机一旦在批准对话框中选择“允许”,就会被永久加入 linkGuard.allowlist,在从设置中移除之前一直放行。

DNS 经过检查,但从不伪造:列入黑名单的 DNS 查询只产生通知,强制执行发生在后续的 TCP 连接上。RoamSwitch 绝不会注入伪造的 DNS 响应。情报源是钓鱼/欺诈域名的编译列表,随软件包提供,并由每日更新程序(§11)刷新。即使没有情报源,离线启发式仍然运行。情报源的 Ed25519 签名和 SHA-256 会在每日更新程序安装之前验证,不匹配的数据不会被采用。链接防护会加载已安装的情报源(如果没有,则使用软件包内置的版本),两者都没有时以“仅启发式”运行(不会变成“全部允许”)。

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 上的 AppArmor wg-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) 启用前必须先登录强制门户。

通用。仅当 vpn_on_untrusted_enabled 且处于 lockdown(外出保护)时才启用。在 open 和 balanced 下不建立隧道;在 Tailscale 中会清除退出节点并撤除终止开关(不会执行 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,检测一个 MAC 对应多个 IP 的状态(重复 MAC)以及网关 MAC 的可疑突变。lockdown 期间,涉及网关的检测会切断所有流量(Air-Gap)并发出通知。在 balanced/受信任的网络上不会立即切断,只发出通知,用户可以从 RoamSwitch 执行紧急切断。这样既可以 (a) 防止攻击者利用单个伪造的 ARP 数据包让用户自己断网的 DoS,也可以 (b) 防止路由器重启或接入点漫游导致误触发。设置项:arp_spoof_guard_enabled。

解除 Air-Gap 前的确认(ARP 复核)

由 ARP 欺骗引发的 Air-Gap(在 lockdown 期间检测到,或为响应 ARP 警告而按下)只有在满足以下条件时才会被视为“原因已消失”而解除。读取内核邻居表(ip -j neigh show,备用为 /proc/net/arp)和默认路由,默认网关的 MAC 必须与触发时记录的可信 MAC 一致(状态为 REACHABLE/STALE/DELAY/PROBE/PERMANENT 之一,且同一设备上没有其他 IPv4 主机声称拥有该 MAC),并且该状态连续 arp_recheck_consecutive 次(默认 3)且持续至少 arp_recheck_min_secs 秒(默认 60)。仅当邻居表项缺失或处于失败状态,且链路已连通(有 carrier、operstate 为 up、Wi-Fi 未被 rfkill 阻断)时,才会向网关发送一次 ARP who-has(间隔至少 20 秒,经由 AF_PACKET)进行确认(arp_recheck_active_probe,默认 true)。收到的应答中只要有一个 MAC 不是可信 MAC,即视为不一致。不一致、表项缺失、MAC 重复、读取失败、未记录可信 MAC,均视为“未解决”(fail-closed)并重置连续计数。已知局限:复制真实网关 MAC 的攻击者,无论缓存判断还是主动探测,任何 MAC 比较都无法检测。

ARP 欺骗检测只有在网关被牵涉时才会自动触发 Air-Gap。网关的 MAC 发生变化,或网关的 IP 与另一个 IP 共用同一个 MAC,都属于这种情况。其他主机的 MAC 发生变化,或一台设备用同一个 MAC 应答多个 IP(运行虚拟机的电脑、带别名地址的 NAS 等),只会通知,不会断网。解除判定适用同样的规则:只有牵涉网关的重复才算“原因仍在”。在 1.10.7 的修复过程中(2026-09-26),在真实局域网中从另一台设备发送伪造的网关 ARP 后,约 1 秒即触发 Air-Gap,攻击停止约 11 分钟后(默认 10 分钟加 60 秒确认)自动解除。即使两个无关的 IP 持续用同一个 MAC 应答,也能解除(在采用此规则之前,会触发全面阻断,而且无法解除)。

端口异常守卫

自动包含新暴露给外部 LAN 的侦听端口(后门、C2 或意外绑定到 0.0.0.0 的开发服务器/本地 LLM API)。结论是 baseline-relative,而不是“危险端口号”。启用后,当前公开的可执行文件被学习为“已知”;此后,仅标记 新出现的 公开的可执行文件(身份 = 可执行文件路径,快照标准化为 snap:<name>)。系统守护程序超出范围。遏制是通过 inet roamswitch_dev_guard 表进行的:将 TCP 从 lo 以外的任何端口丢弃到该端口 — 机器本身继续使用它。自动遏制后,会立即出现桌面通知和确认对话框(“允许此端口”/“保持阻止”),允许一键永久列入白名单。还可以从端口诊断屏幕单独允许任何端口,并重新捕获基线。默认开启。切换:port_anomaly_guard_enabled。从 macOS 移植。

DNS 隧道/数据外泄检测

作为被动链路防护现有 DNS 路径的一部分始终运行(没有单独的设置开关)。符合 iodine、dnscat2 等 DNS 隧道工具典型特征的查询形状(标签长度超过 40 字节且香农熵超过 3.8,或查询类型为 TXT/NULL 记录)中的任意一种,即被计为“可疑形状”,仅当最近 60 秒内此类查询达到 10 次时才发出通知(对同一 apex domain 的重复通知设有 300 秒冷却时间,通知持续发生时冷却时间会依次延长到 600 秒、1200 秒,上限为 1800 秒;若超过冷却时间的 2 倍都没有通知,则恢复为 300 秒)。检查对象仅限 DNS(UDP/TCP 53 端口),不检查其他流量。仅用于通知,不会自动阻断。只有发往 clamav.net 的 TXT 查询不计入,因为 freshclam 用它来确认数据库是否最新。底层 NFQUEUE 规则带有 bypass 标志,因此即使检测进程停止或无响应,DNS 流量也会照常放行,域名解析不会因该功能出现故障而中断(与被动链路防护自身相同的 fail-open 设计)。

已知限制。以连字符分隔的长主机名(例如 prod-us-east-1-web-server-node-0042),以及不区分目标域名的单一全局计数器统计 TXT/NULL 突发查询的方式,此前经实测确认会在此处引发误报。这两点均已于 2026-09-23 修复。已通过可配置的 entropy 允许清单,以及按 apex domain(对 QNAME 末尾两个标签的简单近似,并非严格的 Public Suffix List 实现)分别计数的突发计数器键来解决。允许清单默认内置 16 个主流 CDN/云服务及 PaaS 的后缀(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),运营者可通过 LinkGuardCfg.dns_tunnel_entropy_allowlist 添加自有组织的内部域名(roamswitch-app 的链接防护设置中有专用的输入栏,以逗号分隔输入)。允许清单只豁免基于标签长度 + 熵值的可疑判定,TXT/NULL 查询类型判定无论是否在允许清单中都会适用,从而防止攻击者在允许清单域名下伪装隧道(仅对 TXT,另设一个很小的排除清单,不统计 freshclam 用于确认数据库是否最新的 clamav.net 域名下的查询)。突发计数器已按 apex domain 分别计数,消除了因单一全局计数器不区分目标域名而产生的误报(60 秒滑动窗口的判定逻辑本身未变。重复通知的冷却时间从 300 秒开始,通知持续发生时延长到 1800 秒)。iodine、dnscat2 等真实恶意流量只会在单一攻击者控制的域名下轮换标签和查询类型,因此按 apex domain 分别计数并不会降低检测能力。

Docker 风险检测与防火墙绕过防护

Docker 的公开端口(-p)存在绕过主机标准防火墙的已知问题,因此会始终在 DOCKER-USER 链中插入检查规则,阻止未被明确允许的外部流量(无需配置,默认始终启用)。此外,还会实时监视 docker events,在 --privileged 容器启动或挂载 /var/run/docker.sock(容器逃逸风险)的瞬间检测并立即通知。由于这只是有风险的配置而非确认的入侵,因此仅提供通知,不会自动阻止。

覆盖范围 — 此 DOCKER-USER 链检查支持 iptables(IPv4)、IPv6 以及 Docker 原生 nftables 后端,并每 60 秒重新检查并自我修复(桌面版自 1.10.4 起同样如此;更早的版本仅覆盖 iptables(IPv4)后端)。

机密信息/API 密钥泄露扫描器

通过香农熵与模式匹配检测 OpenAI、Anthropic、AWS、GitHub、Slack、Stripe 等 API 密钥及 SSH 私钥。roamswitch audit-secrets <text|文件|目录> 除文本、文件外,还可递归扫描整个目录(自动跳过 .git、node_modules 等,超过 2MB 或疑似二进制的文件将被跳过)。也可通过 audit_secrets MCP 工具(传入 text 或 path)以及公开 SDK 的 Rust 客户端 roamswitch-linux-kit(文本用 audit_secrets(),文件和目录用 audit_secrets_path())调用相同的检测。内容绝不会被发送到任何地方。

SSH 暴力破解的检测与阻断(仅在 sshd 运行时)

客户端版的守护进程也运行与服务器版相同的引擎(ssh_guard_enabled,默认 true)。区别有三点。第一,只在 sshd 运行时工作:每分钟检查一次 SSH 端口上是否有监听,连续三次没有就停止。没有 sshd 的终端不会实时读取日志,也不会触碰 nftables 表。第二,设置是 config.json 中与服务器版相同的 10 个键(ssh_guard_*)和相同的默认值,约 1 分钟内生效。第三,通知是桌面通知和客户端的事件记录,不会启动 Air-Gap。失败次数达到阈值(默认 600 秒内 6 次)的来源,其新连接会被丢弃,同时该来源已经建立到 SSH 的连接也会被切断(已认证的会话和豁免的来源不会被切断)。当 fail2ban(sshd 的 jail 已启用)或 CrowdSec 已在保护 sshd 时,只做检测和通知。封禁的列表与解除使用 sudo roamswitch bans 和 sudo roamswitch unban <IP>,GTK 界面中没有,只能通过 config.json 和命令操作。在客户端的默认防护级别下,入站是关闭的,连接可能根本到不了 SSH。

首次运行设置向导和确认 UI

该软件包附带 /etc/xdg/autostart/roamswitch.desktop,因此安装向导在安装后首次登录时启动(此后它仅驻留在托盘中)。启用/禁用安全防护会显示与 macOS 版本相同的防护的确认对话框(端口异常、ARP 自动遏制、USB 存储、BadUSB 键盘、蓝牙)。事件时决策(未知的 USB 键盘、未注册的 USB 驱动器、自动阻止的未知端口、紧急气隙)以模式对话框的形式呈现。

§5

香农熵突发检测·YARA扫描

写入事件按 PID 进行跟踪。如果单个进程在 5 秒内创建 20 个或更多熵不低于 7.92 的不同文件,则被视为勒索软件批量加密,并通过 SIGSTOP 冻结该进程。同时发出桌面通知、记录到事件时间线,并采集证据包。此项检测同样会触发 Air-Gap(切断整个网络),与金丝雀被篡改、eBPF 高严重度事件一样,属于高可信度触发条件。

在真实机器(Ubuntu 24.04,内核 7.0)上,用一个改写 40 个文件的复现程序进行了测量(2026-09-26 的测量,对象是加入了部分加密检测、包含在 1.10.7 中的守护进程版本)。全量加密会在超过 20 个文件后被检测到,并在第 31 个文件处冻结进程。即使进程把自己的名字改成 python3 或 git,也会被检测到(允许列表不只看名字,还核对可执行文件与脚本名是否一致;此前仅凭名字,40 个文件全部漏过)。cp -r、rsync -a、tar -x 和 Python 脚本在看到写入的时刻就完成进程识别,因此即使很快结束也不会误报(此前在评估时进程已退出,被报告为“未知进程”)。仅加密每 32 字节中 16 字节的部分加密(LockBit 系的写法),由于整体平均熵达不到 7.92,此前无法检测,从 1.10.7 起可以检测。它通过识别每 16 字节交替出现类似随机数的块和类似文本的块这一形态来判断,检测出了 94% 的模拟文件,在 19,997 个正常文件中误报 2 个(0.01%)。对每个文件不留间隔、全速写入的复现程序,会在 20 个文件的判定之前就写完 40 个,因此被停止时进程已经结束;每个文件间隔 0.2 秒时,则在第 20 个文件处被停止。仅加密开头部分、跳过区间的加密,以及把加密内容写成 Base64 的情况,不计为检测,只记录到日志中,不会冻结(因为有误报正常文件的风险)。用允许列表中的真实程序(如 python3)编写的勒索软件,也无法通过名字区分。

  • YARA 扫描与熵检测分离 — YARA 恶意软件检测会对所有写入的文件执行,包括 ~/.cache/、node_modules/ 和各类压缩包,不按扩展名排除(用户设置的 scan_exclusions、隔离 Vault、/dev/shm 以及内存中和已删除的文件、/proc、/sys、/dev 等位置,以及守护进程自身、ClamAV、桌面基础组件等系统进程写入的内容除外。读取的是开头的 256 KB)。另一方面,勒索软件熵加密突发判定会排除:本身就是压缩格式的文件(.zip、.tgz、.tar.gz、.gz、.png、.jpg、.mp4、.wasm 等)、开头仍为正规压缩/媒体格式的文件(1.10.25 起),以及正规包管理器(npm、npx、cargo、rustc、dpkg、apt、tar 等)的缓存操作,以防止正规工具解包时因误报而触发 SIGSTOP 冻结。
  • 同一文件的重复覆盖 (例如 shred 的三遍)算作一个事件。
  • 关键进程保护 — 将进程与硬编码的受保护进程列表(PROTECTED_PROCESS_NAMES,包含 systemd / dockerd / NetworkManager / sshd / gnome-shell 等)进行比对,名称一致且可执行文件位于系统区域(/usr、/opt 等)的进程绝不会被冻结(防止整台机器挂起)。只是名字相似、位于 /tmp 或 /home 的程序不受保护。

Web 应用层检测规则(Web Shell 与注入代码)

用专用规则检测 PHP、JavaScript、JSP、ASP 的 Web Shell、被注入的混淆代码、伪装成图片的 PHP,以及通过 .htaccess 劫持执行的手法。它与针对 ELF 和 shell 的内置检测相互独立,用一种简单的语言书写,只含字面量、带间隔上限的字面量序列和两个统计值(极长的单行、巨大的 Base64)。不使用正则表达式,因此处理不会膨胀或卡死,即使规则文件是恶意的,也最多造成误报。规则不含从真实样本中提取的签名,也不绑定任何具名恶意软件家族:18 条规则只描述公开记载的手法(执行解码函数的输出、直接执行请求值、上传目录中可执行的 PHP、图片中的 PHP、.htaccess 处理器映射、用 unescape 拼出的注入脚本、浏览器内挖矿库)。

规则分三层:产品内置的、由每日更新程序(11.3)在验证签名后安装的,以及 /etc/roamswitch/rules.d/*.json 中的自定义规则(只读取 root 所有且组和其他用户不可写的文件)。严重度为 critical、high、medium、low;critical 与 high 进入确认或隔离流程,medium 及以下仅报告。自动隔离默认仅限 critical(/etc/roamswitch/web_rules.conf 的 quarantine),客户端版在写入或执行时的检测仍由用户在确认对话框中决定。tests/、fixtures/、.git/ 之下因测试代码会使用相同写法,默认不检测。

检测之后,若在 /etc/roamswitch/hooks/post-detection 放置可执行文件,检测内容(JSON)会通过标准输入传给它(仅执行 root 所有、不可写且父目录也安全的文件;不经过 shell,环境变量极少,30 秒后终止)。可选的账户处置(默认关闭)会停用被隔离文件所有者的登录 shell。它不会处理 root、UID 低于 account_min_uid(默认 1000)的账户、已登录的用户和守护进程自身的用户,并会记录原 shell,可用 roamswitch web-rules unlock-account 恢复。误报实测:WordPress 核心 2,409 个文件、phpMyAdmin 3,356 个文件、npm 依赖 14,743 个文件均为 0 条。局限:只读取开头 256 KB(写入/执行时)和 4 MB(扫描),无法跟踪变量名拼接('ev'.'al')这类拆分;因没有真实样本,未附带针对已知恶意软件的个别签名。

金丝雀(诱饵文件)

诱饵文件被放置在用户的文档、桌面、下载和图片文件夹以及专用目录中,并以 3 秒为周期与 SHA-256 基线进行比较。任何重命名、删除、截断或内容更改都会立即触发 Air-Gap。因为守护进程以 root 身份运行,所以它会枚举 /home/* 并将金丝雀部署到每个真实用户的目录中。

取证证据保全包(自动取证采集)

仅当勒索软件熵突增检测被触发,或 eBPF 运行时威胁传感器发出的 Critical 严重度事件促成了针对单个进程的定点隔离(SIGSTOP + 网络阻断)时,roamswitch-incident-capture 才会启动,并将证据包保存到 <状态目录>/incident-evidence/<时间戳>-<短 ID>/。采集内容包括:在默认路由接口上通过 tshark 进行的、限定 30 秒的抓包(若未安装 tshark 则记录为跳过),以及在传入 PID 时,目标进程的 /proc/<pid> 快照(status、cmdline、maps,以及将疑似敏感信息替换为 [REDACTED] 的 environ、已打开文件描述符的链接目标列表——不采集其内容本身——以及祖先进程链)。单个证据项采集失败不会视为致命错误,只会记录该情况并继续采集其余内容。对于 eBPF 事件,会传入目标进程的 PID。对于熵突增检测,能够确定写入进程时会传入 PID;如果在判定突增时该进程已经退出,则不带 PID 启动,因此只采集抓包。当定点隔离不可行或失败而回退为主机级 Air-Gap 隔离时,以及由 Emergency/Alert 级别事件触发的主机级隔离,均不会执行证据采集。截至 1.10.4 的发布软件包未包含此功能所需的 roamswitch-incident-capture,因此通过软件包安装的环境中该功能并未运行。自 1.10.5 起已随附。

采集结果汇总在 manifest.json 中,其中还嵌入了包内其他所有文件的 SHA-256 哈希(用于后续检测篡改)。证据包不会自动删除——只有在显式执行 roamswitch-incident-capture prune(默认保留 30 天)时,旧的证据包才会被删除。

采集到的证据在采集时并不会被校验。若需事后确认是否遭到篡改,请运行 roamswitch-incident-capture verify <证据包目录> [--json]。该命令会对证据包内当前的所有文件重新计算哈希,并与 manifest.json 中记录的值进行比对,将差异分别报告为「已修改」「缺失」或「未记录」(存在但清单中未记录的文件)。

已知局限 — 此验证会把 manifest.json 的 SHA-256 与保存在证据包之外的记录进行比对,记录在两处:证据包之外的哈希链(evidence_anchor.log,每一行都连接到上一行,并在文件系统允许时设为仅追加)和系统日志(标签 roamswitch-evidence)。采集后改写 manifest.json 会使 verify 失败;修改或删除链中的某一行也会因链断裂而失败。但如果拥有 root 权限的攻击者能同时改写这条链和系统日志的转发目的地,则无法防范。建议把系统日志转发到另一台机器。尚未实现签名。没有记录的证据包(由旧版本采集)会显示为“无记录”,表示没有外部佐证。

蜜令牌(凭据诱饵)

守护进程启动时,会在攻击者及自动化窃取凭据工具实际会扫描的已知真实路径——~/.aws/credentials、~/.ssh/id_rsa、~/.docker/config.json・~/.pypirc・~/.env.backup(位于调用用户的主目录下)——幂等地部署包含 ROAMSWITCH_HONEYTOKEN 标记的伪造凭据文件。若该路径下已存在真实文件,则绝不覆盖(部署前始终先检查文件是否存在)。对蜜令牌的真实访问还可能触发级联部署,追加相关的诱饵文件。截至 1.10.4 的发布软件包未包含此功能所需的 roamswitch-honeytokens,因此通过软件包安装的环境中该功能并未运行。自 1.10.5 起已随附。自 1.11.1 起,RoamSwitch 自身执行的 docker 命令,会把一个空的专用目录指定为 DOCKER_CONFIG 后启动,因此不会触发诱饵 ~/.docker/config.json。其他程序读取时,仍会像以前一样检测。

后台监视进程使用仅用于通知的 fanotify(按文件监视每个诱饵文件的 FAN_OPEN/FAN_ACCESS)来监视对每个诱饵文件的读取与打开操作。在无法使用 fanotify 的环境(非 root 运行等)中则切换为 inotify。由于任何合法进程都没有理由读取这些文件,因此任何访问都被视为入侵者在得手后进行侦察(MITRE ATT&CK T1552)的强烈信号;系统会尝试识别访问来源进程,并以 Critical 严重度记录到事件时间线(不会弹出桌面通知;如果启用了事件转发,则会成为转发对象)。不过,RoamSwitch 自身可执行文件的访问,以及 OpenSSH 客户端系列(ssh、scp、sftp、ssh-add、ssh-agent)对 SSH 诱饵的访问,为避免误报不予记录。无法确定可执行文件的访问则会记录。该功能仅用于记录,不会冻结或阻断访问来源进程等采取额外处置。

已知限制 — ~/.ssh/id_rsa 诱饵文件的正文包含可读文本,其 base64 主体仍不是有效的 OpenSSH 私钥格式。不过大多数密钥/凭据扫描工具仅通过匹配 -----BEGIN ... PRIVATE KEY----- 头部的正则表达式来标记文件,因此我们判断这对实际检测率的影响有限。另外,识别访问来源进程通常依靠 fanotify 事件所带的 PID。仅在无法使用 fanotify 而切换为 inotify 时才会调用 fuser,因此对于读取文件后立即退出的极短命进程,可能无法获取其 PID 或进程名;此时仅会记为“未知”,不影响对访问本身的检测(事件仍会触发并记录)。

fanotify 监控 · 隔离库 · ClamAV

  • 守护进程通过 FAN_CLASS_CONTENT(许可模式;不可用时用 FAN_CLASS_NOTIF),以文件系统级标记监视 /home、/tmp、/var/tmp、/dev/shm(1.10.18 起。挂载级标记是文件系统级标记不被接受时的替代方案),写入完成的通知也以文件系统级标记覆盖整个 /。自 1.10.4 起,许可事件仅针对执行(FAN_OPEN_EXEC_PERM),open 作为通知处理(fanotify_open_perm 可恢复旧行为)。已判定为干净的可执行文件会缓存 60 秒,守护进程自身的子进程不在监控之列。
  • 通知优先,确认前不隔离(fanotify 防护):fanotify 防护对单纯的 YARA/ClamAV 命中不会在未经确认的情况下隔离,也不会执行 FAN_DENY。真实签名检出时,会在 /run/roamswitch/approvals.json 中提出模态请求(隔离/允许/稍后),在用户确认之前,任何内容都不会进入 Vault。只有实际的 execve 才会 FAN_DENY,并且仅当 pre_exec_blocking_enabled 开启时。批准超时为 fail-open。下载监控(默认为 ~/Downloads 和 ~/Desktop,可通过 watched_folders 追加)走另一条路径:YARA 或 ClamAV 检出的文件(EICAR 除外)会不经确认就地移入隔离 Vault,并发出通知。
  • EICAR 等测试签名仅记录到通知历史 — 行业标准测试字符串不会发出通知,只会记录到通知历史中,并且在 fanotify 防护和下载防护中都不会被隔离或阻止。
  • YARA 和 ClamAV 扫描仪都遵循用户配置的 scan_exclusions 路径列表(绝对路径,递归应用;它包含旧版 clamav_exclusions)。提示上的“允许”和“隔离”选项卡中的“恢复”都附加到它,因此不会重新标记同一文件。
  • 检测到的真正恶意软件(在 fanotify 防护中为用户已确认的)会被转移到仅 root 可访问的 /var/lib/roamswitch/quarantine/(此前为各用户的 ~/.local/share/roamswitch/quarantine/,启动时会自动从旧位置迁移)。Vault 目录为 root 所有的 0700,样本为 root 所有的 0400(只读,不可执行)。用户只能通过守护进程的 IPC 操作,且只能列出、恢复、删除自己隔离的项目。恢复时,由降权至调用用户权限的子进程以 0644 的文件写出(不会覆盖已有文件)。
  • 许可事件循环不读取配置文件:配置由专用后台线程读取到 Mutex<GuardConfig> 快照中,热路径上对配置的处理只是加锁和复制(读取待执行文件开头以进行判定的处理除外)。这是一项设计不变量,用于避免延迟 FAN_OPEN_EXEC_PERM 而使机器卡死。
  • 当 ClamAV 存在时(clamav_enabled,默认启用),对下载监控目标文件夹(默认为 ~/Downloads 和 ~/Desktop)中出现的新文件,先用内置的 YARA 扫描,再用本地数据库扫描。.exe 等面向 Windows 的可执行格式会发出通知,.sh、.deb、.elf 等 Linux 的分发格式只记录到历史中,危险的 AI 模型(.pkl、.pt)则连同来源一并通知。ClamAV 是可选依赖项,未安装时其他防御措施照常运行。安装后,每日更新程序(§11)会执行 freshclam,使签名数据库保持最新。

进程执行记录器(仅本地)

这是一个仅限本地的记录功能,通过 netlink 进程连接器(proc connector)记录 exec 和 uid 变化。记录内容包括可执行文件路径、命令行参数(最多 64 个、每个 512 字符、总计 4096 字符,超出截断,控制字符转义)、父进程谱系、uid、当前目录、容器 ID、可执行文件的 SHA-256 等。默认启用,但在内存不足 1 GiB 的机器上默认关闭(显式设置 exec_recorder_enabled 优先)。其自身 CPU 使用限制在单核的 3% 以内(exec_recorder_cpu_budget_pct),并以 nice 10 运行。无法打开进程连接器时,会降级为低频的 /proc 扫描(会漏掉生命周期很短的进程)。

  • 存储 — 记录以按 8 MiB 分段的 JSON Lines 形式保存在 /var/lib/roamswitch/exec_log(0600)。默认保留 200 MB/14 天(exec_recorder_retention_mb/exec_recorder_retention_days)。附带哈希链,可以检测篡改,但无法防止篡改(取得 root 的攻击者可以重写整条链)。检索使用 roamswitch events search|tree|export|verify,面向 AI 客户端的只读 MCP 工具是 search_exec_events/get_process_tree。
  • 仅通知的关联规则(EXR-001~010) — 共 10 种:由 Web 服务启动 shell、从 /tmp、内存文件(memfd)或已删除文件执行、下载后立即执行、base64/eval 单行命令、没有 setuid 路径却获得 uid 0、执行紧随 cron/systemd 配置变更后放置的二进制、/etc/ld.so.preload 变更后的执行、本地快照(btrfs、LVM)被 snapper、timeshift 等已知管理工具以外的程序删除(EXR-008,1.10.11)、同一来源在 30 秒内通过 which 等方式查找 8 种以上攻击工具(EXR-009,1.11.0)、python 或 node 等脚本解释器在 2 分钟内启动 3 种以上攻击工具(EXR-010,1.11.0。由人在 sshd、login、终端之下手动输入的情况除外),每条规则都标注 MITRE ATT&CK 技术 ID。规则只发通知,单独不会隔离;仅当运维人员明确启用 exec_rules_isolation_optin(默认 false)时,高严重度规则才会被当作 Air-Gap 的高置信度触发(§4)。
  • 不会发送到设备之外 — 原始日志不会被转发。仅当启用事件转发(§9.4)时,才会把规则通知(严重度、规则 ID、进程名、摘要)和分段封印哈希,且仅发送到运维人员所配置的目的地。

凭据文件访问监控(默认开启,仅通知)

默认启用(credential_access_watch_enabled 为 true;设为 false 即停止,也可以用恶意软件防护界面中的开关切换)。当意料之外的程序打开 SSH 私钥(~/.ssh/id_*)、~/.aws/credentials、~/.config/gcloud/ 下的凭据文件(credentials.db 等 4 种)或主流浏览器的凭据/Cookie 存储时发出通知(仅通知)。它只是对少量文件单独设置 fanotify 通知类事件 FAN_OPEN,不会阻止、冻结或拒绝任何操作,内核也不等待响应。打开文件的程序的可执行文件名会与按类别划分的允许列表(ssh/git/gpg-agent、浏览器、aws/terraform/kubectl 等)比对,同一(程序,文件)的重复通知会被抑制 10 分钟。备份工具(restic、borg 等)和索引器(tracker、baloo 等)从一开始就被允许。其他不希望收到通知的程序,请加入 config.json 的 credential_access_watch_allow。对于 AWS 凭据,以 python/python3/ansible 身份运行的工具(如 boto3 脚本或 Ansible)也被允许(对 gcloud 则允许 python/python3)。但 python -c 之类的单行命令仍会通知。这一允许的代价是,以 python 脚本文件形式运行的凭据窃取程序也不会被通知。node、ruby 等其他解释器,以及 grep、rclone 之类通用的搜索与同步工具,因为窃取凭据的程序看起来完全一样,所以没有加入允许列表,可能会产生通知。但是,像 cat 或 head 这样非常短的读取,如果程序在 RoamSwitch 确定其可执行文件之前就已退出,可能不会被通知(持续运行一段时间的程序的读取会被通知)。为避免这一局限,不使用许可类 fanotify,因为它可能导致系统停住。更改约 30 秒内生效,无需重启。

附在文件篡改通知中的入侵路径

客户端版的关键文件完整性监控(FIM)和依赖锁文件监控的通知中,也会附上做出修改的进程(账户、可执行文件、命令)以及该进程所属的 SSH 登录(来源、时间、认证方式)(config.json 中的 fim_intrusion_context_enabled,默认 true,不会向外部发送任何内容)。凭据文件访问监控的通知,只有在该进程属于某个 SSH 登录时才会多出一行。工作方式、信任的值,以及三种答案(找到了/没有找到 SSH 登录/无法查明登录路径)的区分,都与服务器版相同。无法查明的情况,绝不会写成“不是登录”。记录保存在 /var/lib/roamswitch/fim_intrusions.json 以及 MCP verify_fim 的 recentIntrusionContext 字段中。

内核本地权限提升 (LPE) 抢占和强化

当攻击者或恶意软件获得非特权用户立足点时,深度防御可防止利用 Linux 内核漏洞获取 root 权限。

  • 非特权用户命名空间的处理(Frag Gap CVE-2026-53362 缓解) — 许多现代 Linux 内核权限提升漏洞,源于非特权用户调用 unshare(CLONE_NEWUSER) 创建用户命名空间,随后在其中非法访问未初始化的结构或旧的网络子系统。诊断会检查并显示 user.max_user_namespaces 和 kernel.unprivileged_userns_clone 的状态,但由于可能导致浏览器、Flatpak、Electron、无根容器等无法运行,Client 版不建议禁用,也不将其纳入一键强化(如果 user.max_user_namespaces = 0,则会提示恢复为标准值)。禁用只是临时缓解,仅能堵住依赖创建用户命名空间的攻击路径,对其他途径无效。仅在未受信任的网络期间将 kernel.unprivileged_userns_clone 设为 0 的行为,只有在 guard.yaml 的 adaptive_security.untrusted_network_actions.auto_enable_userns_restriction 中明确启用时才会应用(默认禁用;在没有此设置项的内核上不执行任何操作)。Server Edition 默认永久应用(harden_userns_enabled)。根本的办法是更新到已修复的内核并重启。
  • Yama LSM 内存保护 — 强制执行 kernel.yama.ptrace_scope = 1(或 2)可防止未经授权的进程通过 ptrace 附加或通过 /proc/$pid/mem 读取内存,从而保护浏览器会话和 GPG/SSH 私钥。
  • Core Dump Suppression — 设置 fs.suid_dumpable = 0 可防止敏感进程将纯文本内存转储写入磁盘而导致崩溃。
  • /tmp 和 /dev/shm noexec 安装防御 — 为了防止有效负载丢弃并执行攻击,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]
    ├─► ② 阻断网络:Critical 仅阻断该进程的用户 (roamswitch_isolate),Emergency/Alert 为 nftables input/output drop [启动 Air-Gap]
    └─► ③ 桌面紧急通知:GTK3 模态框 & libnotify 通知
  • Direct UNIX Domain Socket IPC (/var/run/roamswitch/events.sock) — 来自 Falco 或其他 eBPF 传感器的警报直接通过内存中 UNIX 套接字接收,无需写入磁盘日志,从而消除磁盘 I/O 延迟并实现亚秒级响应切换。
  • 毫秒级自主响应(进程冻结 + 网络阻断) — 当收到的事件优先级为 Critical(内核 LPE、权限提升、勒索软件加密等)时,守护进程会向相应 PID 发送 SIGSTOP,使进程完全停止(状态:Ts),并通过 roamswitch_isolate 表仅阻断该进程的实际用户(容器则为其 cgroup)发出的通信,即执行“定点隔离”,同时发出桌面通知。当无法确定 PID、因属于受保护进程而拒绝冻结、对 root 进程等隔离失败,以及事件优先级为 Emergency/Alert 时,会在 inet roamswitch 表中部署 policy drop(Air-Gap),如果已知 PID 也会发送 SIGSTOP。低于 Critical 的事件不会阻断,只记录到历史中。
  • 面向桌面的低开销及日志膨胀防止调优(RoamSwitch Optimized) 为抑制笔记本电脑和工作站的电池消耗、CPU 负载和磁盘枯竭,针对部署 Falco 的环境,提供了优化配置文件(99-roamswitch-optimized.yaml)和 logrotate 配置。Client 版的软件包和 install.sh 的桌面模式不会安装 Falco,也不会放置此配置文件(Server Edition 的软件包和 install.sh --server 会放置)。在 Client 上使用 Falco 时,需由用户自行安装 Falco,必要时自行放置此配置文件。主要设置如下。
    • 严重性过滤 (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,也能通过内核 sysctl(Yama 的 ptrace 限制、核心转储抑制、网络保护,每次切换配置文件时应用)、nftables 配置文件、fanotify 和金丝雀监控实现自主防御。部署 Falco 后,再加上基于实时 eBPF 事件的零日拦截和进程冻结,实现最高强度的防御。

§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==0 HID 设备,以自我修复先前版本变砖的机器。
  • 按键在软件中被删除。 在检测到未注册的键盘时,会在该设备的 /dev/input/eventN 上获取独占的 evdev EVIOCGRAB(仅在 /proc/bus/input/devices 中具有 kbd 处理程序的节点,与 VID:PID 匹配)。鼠标节点未受影响。这是 macOS CGEventTap 的 Linux 版本;设备保持通电状态。
  • Safety — 仅当有另一个键盘可供输入(内置键盘或经批准并连接的 USB 键盘)时才会进行抓取,因此具有单个新键盘的机器永远不会被锁定。
  • 批准对话框 — 抓取键盘的同时,会将批准请求写入 /run/roamswitch/approvals.json,GUI 显示模态框(允许/拒绝)。允许后将设备加入白名单并解除抓取。若 3 分钟内无人批准,默认不再释放键盘(1.10.4 起;更早的版本在 3 分钟后故障开放,启用 usb_keyboard_timeout_release 可恢复旧行为)。

流氓 USB 大容量存储设备

当插入未注册的 USB 大容量存储设备(无 HID 接口)且防护已启用时,它会以 authorized=0 保留(对于非 HID 设备来说是安全的),并显示批准对话框。无论是否处于受信任的网络。已挂载或正用作交换分区的驱动器(使用中的外置磁盘、Live USB 等)等同于被拔出,可能导致损坏,因此不予保留,只记录到日志。允许则重新授权并加入白名单,拒绝则继续保留。macOS 版自 1.7.4 起统一为这种“只读保留 + 批准提示”的方式。

身份识别和追溯

HID 由 bInterfaceClass == "03" 和 /proc/bus/input/devices 中的 Handlers= 行确定。组合接收器(一个 VID:PID 同时带有 kbd 和 mouse 两个部分)按 VID:PID 合并。将已有键盘登记到允许列表的 grandfathering 由 GUI 而非守护进程执行。在首次设置向导中启用时(以及防护已启用且已完成首次设置、但当前连接的键盘一个都不在允许列表中的既有环境首次迁移时),会把此时已连接的键盘登记到允许列表。之后在 USB 标签页中启用则不会登记。守护进程启动时已连接但未登记的键盘,由于可能在启动前被替换,会作为未登记处理,如果还有其他可输入的键盘,则成为抓取(grab)对象。

§7

roamswitch-core 中的 LinuxHealthChecker 评估 27 个项目,并生成 0 至 100 的分数、S/A/B/C 等级(100 分为 S,85 至 99 为 A,70 至 84 为 B,低于此为 C)以及每个项目的改进建议。无论从 GTK 仪表板、CLI(roamswitch status)还是 MCP(get_security_report),全部 27 个项目的标题、详细信息和推荐内容均以用户的语言显示(支持 10 种语言)。

评估类别
类别示例项目
磁盘/启动LUKS / dm-crypt 加密、UEFI 安全启动
访问控制LSM (AppArmor / SELinux)、SSH / sudo 配置审核
内核强化sysctl 强化、非特权用户命名空间的状态检查(Frag Gap 缓解。Client 版不建议禁用)、Yama 的 ptrace 限制、核心转储控制、/tmp 和 /dev/shm 上的 noexec
更新自动安全更新配置
网络ARP欺骗监控、网关ARP/NDP锁定状态、暴露端口审计、Wi-Fi加密强度
恶意软件ClamAV(含 EICAR 实际检测测试)/ fanotify(还检查防护是否实际运行,以及是否因队列溢出而丢失事件)/ 熵监控 / noexec / 辅助程序(诱饵凭据与证据采集)的安装检查
浏览器Firefox / Chrome / Chromium 安全浏览设置
域名解析/USB威胁防护 DNS、USB 总线的零信任状态
容器与漏洞Docker 套接字挂载暴露、特权容器(--privileged)审计、容器运行时隔离级别(runc / gVisor / Kata)、运行中内核的已知 CVE 暴露

每次检查都基于读取本地文件和命令输出(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 。

NIST CSF 2.0/CIS Controls v8 映射

为 27 个检查项中的每一项都标注 NIST CSF 2.0 的功能/类别标识符(例如 PR.DS-01、DE.CM-09),并且仅在能够可靠对应的项目上一并标注 CIS Controls v8 的控制编号(例如 3.11、4.1)。对应关系不明确的项目不会被强行映射,而是保持未映射状态。MCP(get_security_report)和 CLI 的 JSON 输出(roamswitch status --json)中包含此映射(nistCsf/cisControl)。GUI 以及 Client 版 CLI 的常规显示中不会出现(Server 版的 roamswitch status --server 显示中会出现)。此对应关系方便需要满足合规要求的运维人员与审计人员,将 RoamSwitch 的各项检查机械式地对应到现有框架。

§8

roamswitch-mcp 是通过 stdio 使用 JSON-RPC 的 MCP 服务器,由 Claude Desktop、Cursor、Antigravity 等 AI 客户端启动。所有工具都是只读的,且绝大多数完全在本地完成。唯一可能连接外部主机的是默认关闭的 run_npm_audit_signatures。诊断评估(get_security_report 与 verify_security_findings)不会向局域网发送任何内容,网关也只从 ARP 缓存等本地信息中读取。只有在重新获取网关 MAC 的其他处理(守护进程的 ARP 监控、紧急恢复、Sensor 配对、get_guard_status 等)中才可能发出 ping。

主要公开工具(完整列表见 MCP 连接设置)
工具目的
获取安全报告27项评估、评分、建议
verify_security_findings按当前状态重新评估 get_security_report 中的各项目(checkId),并为每个项目返回 stillPresent(仍然存在)、resolved(已解决,或按设计不适用)、inconclusive(无法判定)三者之一,以及与语言无关的原因代码。用于在修复之后确认问题是否真的消除;因权限不足、特权辅助程序未连接、无法访问 Docker 等原因而未能测量的项目,不会被报告为安全,而是返回 inconclusive。省略 checkIds 时检查所有项目(最多可指定 Mac 50 项、Linux 64 项;Linux 还可指定 isServer)。Mac 上的 host_firewall、network_stealth_mode、gateway_arp_lock、malware_scanning、dns_threat_guard、usb_zero_trust 由 RoamSwitch 应用内设置的值决定是否合格,因此这些项目反映的是应用内设置,而不是对操作系统实际状态的重新测量。只读,不会连接外部主机,也不会进行修复或更改任何设置。在 Linux 上也不会向局域网发送任何内容,只会从本地缓存读取网关。在 Mac 上,为查询网关的 MAC 地址,可能会向局域网内的网关发送 ICMP ping,通常 1 次(查不到 MAC 时最多数次)。
audit_mcp_configs读取 AI 客户端的 MCP 配置文件(Claude Desktop、Claude Code、Cursor、Windsurf、VS Code、Cline、Gemini CLI,以及已登记项目文件夹中的 .mcp.json 等),指出有风险的设置。会检测无需逐次确认就自动批准的工具(auto_approve)、非 loopback 主机上的明文 http:// 服务器(不仅是 url 字段,像 --server http://… 这样通过启动参数传入的连接目标也包括在内。remote_plain_http)、以 shell 或解释器单行命令启动(shell_wrapper)、获取未固定版本的软件包或镜像的 npx / uvx / docker run 等启动方式(unpinned_package_launcher)、带有 --privileged 或挂载了 Docker 套接字、根文件系统的 docker run(container_host_access)、让进程在启动时加载额外代码的环境变量(如 LD_PRELOAD、NODE_OPTIONS 中的 --require;名称不区分大小写,不返回值;code_loading_env)、指向无认证攻击工具后端 HexStrike AI 的配置(hexstrike)、组或其他用户可写的配置文件(world_writable_config)Claude Code의 enableAllProjectMcpServers(auto_trust_project_servers), 웹 페이지·메일·채팅·Issue처럼 다른 사람이 쓴 글을 읽는 서버와 명령 실행·파일 변경이 가능한 서버가 한 설정 파일에 함께 있는 구성(간접 프롬프트 인젝션의 전제가 되는 조합. 잘 알려진 패키지 이름으로 판정하며 서버 도구의 내용은 보지 않음; injection_exposure)을 검출합니다.无参数、只读,不会向网络发送任何内容。不会返回环境变量或标头的值、参数和任何机密,只返回服务器名称、命令名、URL 主机,以及(为注册表名称时的)软件包或镜像名称。无法解析的文件会列在 unreadableFiles 中,表示未被审计。
audit_agent_setup读取提供给 AI 智能体的指示文件和技能(~/.claude 下的 CLAUDE.md、commands、agents、skills,以及 ~/.codex、~/.cursor/rules、~/.gemini、~/.agents、~/.openclaw,和已登记项目文件夹中的 CLAUDE.md、AGENTS.md、.cursorrules 等)和 OpenClaw 的 openclaw.json,指出危险的内容和设置。对于指示和技能文件,会检测隐藏在不可见 Unicode 标签字符中的句子(hidden_tag_characters)、零宽字符和改变文字方向的控制字符(invisible_characters)、要求忽略先前指示或向用户隐瞒的措辞(override_instruction)、curl … | sh 这类下载后直接执行的写法(remote_script_exec)、解码后执行编码字符串(encoded_payload_exec)、对密钥和凭据存放位置的引用(credential_access)及其附近的外发或数据接收服务地址(credential_exfil_shape、exfil_endpoint)、设置重启后仍保留的机制(persistence_instruction)、技能中的已编译代码(skill_precompiled_code)和他人可写的文件(agent_file_writable)。HTML 注释中的内容渲染后会被隐藏,因此按更高风险处理。对于 OpenClaw,会检测认证被关闭(openclaw_auth_disabled)、可从网络访问的监听方式(openclaw_wide_bind)、不足 24 个字符的令牌(openclaw_weak_token)、允许所有来源(openclaw_origin_wildcard)、已开启的 dangerously… 开关(openclaw_dangerous_flag)、信任所有对象的代理设置(openclaw_broad_trusted_proxy、openclaw_proxy_trust_loosened)、对任何人开放的私信或群组策略(openclaw_open_dm)、mDNS 的 full(openclaw_mdns_full)、被隐藏的自检结果(openclaw_audit_suppressed)以及宽松的权限(openclaw_loose_permissions)。所有规则都是确定性的,匹配只表示“需要人来读一读”,并不断定为恶意。无参数、只读,不会向网络发送任何内容。不会返回文件正文或机密(令牌只报告长度),只返回规则名、行号、URL 主机和配置键路径。无法读取的文件会列在 unreadableFiles 中,表示未被审计。
获取暴露端口暴露的端口及其防火墙阻止状态
获取守卫状态当前保护级别和防御模块状态
审核网址安全启发式 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 映射进行比对。完全不产生任何网络通信。在 Linux 上,如果无法获取软件包列表或超时,不会报告“无匹配”,而是 complete 为 false,并在 incompleteReason 中给出原因(未完成的结果不等于没有问题)。 在 Ubuntu(20.04、22.04、24.04、26.04)上,使用 Ubuntu 专用数据,按源软件包名称和源版本比对,只检查 Ubuntu 已发布修复、CVSS 7.0 及以上的 CVE,因此“无匹配”不代表没有已知 CVE。结果中增加 ecosystem 和 release,每条检出带有 binary(已安装的二进制软件包名称);只有 Ubuntu Pro 才能修复的漏洞带有 proOnly。Ubuntu 衍生版(Linux Mint、Pop!_OS 等)和不支持的版本会返回 complete 为 false,并在 incompleteReason 中给出原因,不会改用 Debian 的数据。
run_package_cve_scan_languages将 npm / PyPI / crates.io / RubyGems / Packagist / Go / Maven 等依赖锁定文件与同一本地 CVE 映射进行比对。完全不产生任何网络通信。
get_vulnerability_scan_history针对 scan-vulns --confirm 记录的每个探测项,返回自上次实际运行并确认以来经过的天数(pass_age_days)。
verify_fim重新计算 23 个固定的关键系统文件、认证/cron/systemd/SSH 配置等 12 个目录下的全部文件、RoamSwitch 自身的二进制以及各用户的 authorized_keys 的哈希,并与基线比对,验证是否被篡改及是否有新增文件。以 root 运行时,结果中还会包含新增字段 recentIntrusionContext。对每次最近的变更,会给出做出修改的进程(账户、可执行文件、命令)、该进程所属的 SSH 登录(来源、登录时间、认证方式),或无法查明的原因。
get_file_scan_guard_status返回文件扫描防护(ClamAV)的配置及其所用隔离 Vault 的状态(这是面向 Server Edition 的工具,不是客户端版恶意软件防护的设置)。
get_port_anomaly_incidents返回端口异常防护的基线状态、当前自动阻断的端口,以及最近 50 条事件。响应中明确注明:当前自动阻断的端口是不带时间戳的当前状态快照,与带时间戳的事件历史是两回事。
get_ebpf_incidents返回 eBPF 运行时防护的当前隔离状态,以及触发隔离的事件历史。在 Server Edition 上,每条记录还会附带从检测到处置的时间(毫秒,detect_to_receive_ms、receive_to_action_ms、detect_to_action_ms),结果中还包含 Falco 心跳检测的状态(heartbeat)。
get_ssh_guard_incidents返回 SSH 暴力破解防护的状态与记录。包括:是否执行封禁还是只做检测(例如另一个防御工具已在保护 sshd)、当前被封禁的来源地址与剩余时间,以及最近的记录(封禁、仅检测、失败累积的来源地址登录成功,三者之一,最多 200 条)。尝试过的用户名本身在 Linux 上不保存(只记数量),在 Mac 上经过无害化处理后最多返回 5 个。Mac 还会返回最近的 SSH 登录(来源地址、用户、认证方式、密钥指纹)。只读,没有解除封禁的 MCP 工具。Linux 上封禁列表只有 root 能读取,因此以普通用户运行时只返回数量。Linux 可用于 Server Edition,以及 sshd 正在运行的 Client Edition;Mac 仅限 Pro 版。
get_resource_guard_incidents返回资源耗尽/进程异常检测防护的历史(内存泄漏、崩溃循环),并附置信等级(此防护属于 Server Edition。客户端版中不运行,因此结果始终为空)。
get_notification_history返回 RoamSwitch 发送的通知(安全日志审计异常、ClickFix 检测等)历史记录,最近1周、最新在前。
get_incident_timeline将各防护模块的检测结果合并为单一时间线返回(含进程谱系与 MITRE ATT&CK 标签,实验性)。

实证型漏洞检测还会为每个已确认的探测项记录自上次实际运行并确认以来经过的天数(pass_age),从而区分“该检查存在”与“该检查确实最近被验证过”。由于目前尚无定时执行机制,该数值只反映实际执行扫描的频率。CLI(roamswitch vuln-status)、GUI 与 MCP(get_vulnerability_scan_history)均可查看相同的经过天数,使运维人员或审计人员能够了解上次实际核实的时间,而不是仅凭功能存在就假定近期已核实。

MCP 服务器自身不打开任何网络连接。直接连接守护进程 Unix 套接字(/run/roamswitch/roamswitch.sock)的只有 get_sensor_audit_results,其他工具要么直接调用 roamswitch-core 的逻辑,要么只读取守护进程写入的状态文件(roamswitchkit 方向相反,是供 CLI 等将 roamswitch-mcp 作为子进程启动的 SDK)。例外是两个默认关闭的工具:run_active_vuln_scan 会向本机 127.0.0.1 发送非破坏性探测;run_npm_audit_signatures 会启动 npm 命令,由 npm 向 npm 注册表发起查询(MCP 二进制本身不含 HTTP / TLS 栈)。诊断评估(get_security_report 与 verify_security_findings)不会向局域网发送任何内容,网关的确认也只参照 ARP 缓存等本地信息。只有在重新获取网关 MAC 的其他处理(守护进程的 ARP 监控、紧急恢复、Sensor 配对、get_guard_status 等)中才会发出 ping。

在本地 LLM 实战演练中发现并修复的错误归因问题
2026 年 9 月,我们使用本 MCP 服务器让本地 LLM(Qwen 3.8 27B)进行一次事件调查实战演练(fire drill)时,发现模型误读了 get_port_anomaly_incidents 返回的数据,把一个与本次事件无关的旧端口阻断记录,错误地编入了当前正在发生的事件叙述中。原因在于:不带时间戳的「当前阻断端口」快照,与带时间戳的真实事件历史,在同一个响应中未加区分地一并返回。修复方案是新增说明字段,明确标注哪一部分是时间线数据、哪一部分只是快照。修复后,用同一场景对同一模型重新运行,模型引用了说明字段中的文字,明确拒绝将旧端口归因于当前事件。我们通过自动回归测试和真实本地 LLM 复现两种方式,验证了修复的有效性。

§9

RoamSwitch for Linux 默认发出的对外通信只有一种:每日更新程序对已签名清单的 GET 请求(§11)。其余所有诊断、检测和审计均在本地完成。只有在运维人员或用户明确启用或触发时才会发生的通信,列于 9.4。RoamSwitch for Linux 是专有免费软件,源代码不公开,因此下列检查由 lafine 实施,结果见 9.2,附录 C 的步骤仅凭分发软件包即可复现。

9.1 审核方法

  1. crate 隔离 — 只允许一个 crate 使用 HTTP 客户端/TLS 栈,即构建定时器驱动更新程序二进制的 roamswitch-updater。scripts/audit_no_network.sh(在 CI 中作为门禁运行)在下列情况下会使构建失败:roamswitch-core、roamswitch-daemon、roamswitch-app、roamswitch-mcp、roamswitchkit(以及 CLI 和 Server 系列 crate)引入了 reqwest / ureq / rustls / native-tls / openssl-sys 等 HTTP 客户端/TLS 栈;或 roamswitch-updater 及 9.4 所列文件之外的 .rs 使用了 TcpStream / TcpListener / UdpSocket / to_socket_addrs / lookup_host。唯一的额外许可是仅在 roamswitch-core 中允许 tonic / hyper,用于通过 UNIX 域套接字与 eBPF 传感器(Tetragon)进行 gRPC 通信;同一脚本禁止将其与 TLS 搭配使用。
  2. 依赖树检查 — 搜索完整的 Cargo.lock 以查找遥测/分析/崩溃报告 SDK(sentry、opentelemetry,...)。
  3. roamswitch-updater review — 它的单一请求目标,没有查询字符串和 cookies,且能表明软件身份的标头只有固定的 User-Agent,以及它下载的所有内容的 Ed25519 验证。
  4. 子进程枚举 - 每个Command::new(...)调用站点都经过审查。

9.2 结果

审核结果
审核项目结果
HTTP / TLS 堆栈仅存在于 roamswitch-updater (ureq + rustls) 中。不在 core / daemon / app / mcp / kit 中
遥测/分析/崩溃报告 SDK不存在任何地方
TcpStream / TcpListener / UdpSocket除 roamswitch-updater 外,仅存在于 9.4 所列的四个可选功能文件中
使用的 Socket APIAF_UNIX(本地 IPC)/AF_NETLINK(NFQUEUE verdict 通道、nftables 与 ip neigh 控制——不向外发送)/AF_INET 仅用于 roamswitch-updater 和 9.4 的可选功能(TLS/HTTP 交由系统的 openssl/curl 子进程处理)/AF_PACKET 仅在 ARP 复核(§4)向网关发送一个 ARP who-has 时使用(局域网内)。启用 VPN 隧道(§4)时,由 wg-quick(WireGuard 后端)或 tailscaled(Tailscale 后端)——二者均为子进程而非守护进程——向用户自己的端点/tailnet 发送流量
每日更新约每 24 小时一次,共 2 个请求:GET https://lafine.net/updates/v1/manifest 和签名 manifest.sig(数据本体仅在版本更新时额外获取)。没有查询字符串,没有 Cookie,固定的 User-Agent: RoamSwitch-Updater(唯一表明软件身份的标头),没有设备级标识符。所有有效负载均经过 Ed25519 验证。updates.enabled = false 可完全停止
VPN 隧道 (§4)默认关闭。启用后,wg-quick 会构建一条到 用户自己的 .conf 端点的 WireGuard 隧道 — 与 lafine 无关。通过 getent ahosts (glibc NSS) 解析端点主机名,而不是 Rust 套接字 API
许可证激活离线Ed25519签名验证。与激活服务器没有通信
结论

RoamSwitch for Linux 默认发起的对外通信只有一种:每天一次、仅接收的签名清单确认,以及有新版本时的签名检测数据下载,两者都不含任何用户或设备信息。除非运维人员或用户明确启用或触发,产品的其他任何部分都不会联系外部服务器(9.4)。为固定邻居表和 ARP 监控而发往 LAN 内的 ping(9.3 的 #2),目标仅限回环、私有、链路本地和 ULA 地址,不会发往公共地址。

9.3 面向外部的操作的完整列表

无论直接还是间接,可能使数据包离开本机的默认路径操作(下表;仅在运维人员或用户启用或触发时才发生的操作见 9.4):

#操作目的地触发发送了什么
1每日更新程序 — GET /v1/manifest 及其签名。各数据文件仅在版本更新时获取(内核、操作系统软件包和语言的 CVE 映射约每 30 天确认一次;尚无 Ubuntu CVE 映射的 Ubuntu 设备在下一次每日运行时获取)lafine.net/updates/v1 (CDN)systemd 计时器。启动 15 分钟后开始,此后约每 24 小时一次(最多有 3 小时的随机延迟)。updates.enabled = false 或禁用 roamswitch-update.timer 时不会发生普通的 HTTPS GET:没有查询字符串,没有 cookies,固定的 User-Agent: RoamSwitch-Updater, 与用户或机器无关。接收签名的清单 + feed
1afreshclam(ClamAV 自己的更新程序),安装 ClamAV 时由每日更新程序调用ClamAV 项目的 CDN作为 #1 的一部分。此外,当用户在界面上按下“立即更新病毒库 (freshclam)”按钮时也会启动ClamAV 本身的流量(一个单独的项目); RoamSwitch 只启动它
2ping -c 1 -w 1 -W 1 <网关 IP>(对 IPv6 路由器和 LAN 内的 DNS 服务器也各发一次同类 ping)LAN 默认网关。在确认 ARP/NDP 固定时,为 IPv6 默认路由器,以及 resolv.conf(systemd-resolved 下为 /run/systemd/resolve/resolv.conf)中列出的 DNS 服务器里,属于回环、私有、链路本地和 ULA 地址的那些。不会向公共解析器(威胁防护 DNS 的 Quad9 等)发送数据包网关仅在 ARP 监控等需要 MAC、而 ARP 缓存、邻居表和 15 秒内的备份记录中都没有时才会发送(对同一目标最多每 8 秒一次)。发往 IPv6 路由器和 DNS 服务器的 ping,发生在网关 ARP 固定(gateway_arp_lock_enabled,默认开启)重新向邻居表固定时(切换到非受信任网络时,以及切换该设置时)仅 1 次 ICMP 回显请求(含 ping 的标准数据)。无 DNS 解析
3启动系统浏览器 (xdg-open)https://roamswitch.com/linux(产品站点)、https://lafine.net/(运营者站点、使用条款、联系方式)、https://github.com/lafine1211/roamswitch-support(发布说明与支持)仅当用户单击按钮(例如“打开发行说明”)时RoamSwitch 进程未打开套接字;它将 URL 传递给操作系统浏览器
4解析 dns ...—(RoamSwitch 自身不产生通信)不受信任网络上的威胁保护 DNS(默认开启,可关闭)。 <iface> 是每个带有默认路由的物理网卡(同时有线+无线;lo / tailscale* / wg* / docker* / veth* 除外);当 dns_scope = always_on 时也应用于可信网络;协调每个哨兵周期仅更改操作系统的解析器设置;RoamSwitch 本身不发送 DNS 查询。设置后,该网卡的域名解析会指向所选的威胁防护 DNS(默认为 Quad9)
5包管理器更新发行版的 apt / dnf / zypper / pacman 存储库当用户运行 apt upgrade 等,或点击 GUI 的“立即升级”按钮(通过 pkexec 运行 apt-get / dnf / zypper / pacman)时apt 系只读取本地的 apt-cache policy 输出并启动包管理器。dnf 系在打开更新标签页等时会执行 dnf check-update,可能向已配置的存储库发起查询(zypper 为 zypper list-updates,pacman 为 pacman -Qu)。这些都与 lafine 无关
6VPN 隧道 (§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 控制、neigh 表的 ARP/NDP 固定(ip neigh,不发送数据包)、链接防护(NFQUEUE,仅 AF_NETLINK)、端口扫描(解析 ss 输出)、ARP 监控、健康诊断、熵监控、金丝雀、fanotify、USB 监控(evdev 独占)、隔离、日志审计、URL 与机密信息审计)均仅通过读取本地文件和解析子进程输出完成。§4 的 VPN 隧道属于“用户明确配置的对外通信”,其目的地是用户自己的 VPN 服务器,没有任何数据流向 lafine 的基础设施。9.4 中各项功能的目的地在 9.4 中逐一说明。

9.4 仅在选择启用或用户操作时才会对外接触的操作

9.3 的表格是默认情况下发生的操作。以下是代码中打开套接字或把通信委托给外部程序(curl/openssl/npm)的位置,且只有在运维人员或用户明确启用或触发时才会运行。scripts/audit_no_network.sh 只允许 event_forward.rs、webhook_guard.rs、active_vuln_scan.rs、web_vuln_scan.rs、sensor_pairing.rs 这 5 个文件使用套接字 API(webhook_guard.rs 仅在启用默认关闭的设置 forward_webhook_block_private_addresses 时,用系统解析器解析 Webhook 目标的名称以确认其不是内部地址,并不建立连接。另有 tests/fleet_roundtrip.rs 仅用于测试,只连接 127.0.0.1 上的模拟服务器),其他源码若使用 TcpStream 等则构建失败。这些功能同样不会引入 HTTP/TLS crate:TLS 交给系统的 openssl,HTTP 交给系统的 curl 子进程。

  • 事件转发(event_forward.rs)——默认 OFF(forward_enabled = false),目的地也只有运维人员手动配置的,没有内置目的地。支持的目的地类型有:syslog(RFC 5424 或 CEF,UDP/TCP/TLS)、JSON Lines 文件、带 HMAC-SHA256 签名的 Webhook。TLS syslog 通过系统的 openssl s_client,Webhook 通过系统的 curl(URL、签名等机密通过标准输入的配置传递,不出现在命令行参数中),守护进程本身没有 TLS/HTTP crate。发送的只有主机名、事件 ID、时间、来源、严重度、规则、处置、PID、进程名和摘要这些字段(不含文件内容,也不含执行记录的原始日志)。经 TCP/TLS/Webhook 未能送达的事件会存入 0600 的磁盘队列(默认上限 20 MB/72 小时)并重试。
  • Fleet(Business 层,仅设备端)(roamswitch-core/src/fleet/)——设备端代码包含在主程序中,但未注册的设备不发送任何内容,也不解析 Fleet 主机名(工作线程只读取一个本地状态文件)。只有运维人员执行 roamswitch fleet enroll 时才会注册。管理服务端尚未部署或发布,目前没有可连接的对象。代码中体现的设计保证如下。(a) 设备只接收由组织签名的策略配置数据,并用注册时固定的组织公钥验证,验证不通过的策略不会被应用。设计上组织的签名密钥由客户持有,lafine 不持有。(b) 不存在远程命令通道(设备端没有任何可以命令其隔离、解除、收集或执行的端点、字段或代码路径)。(c) 策略无法削弱本地下限(关闭防护、妨碍解除/紧急恢复在模式中根本无法表达,取值采用“更严格的一方”)。策略要改变事件转发目的地,也只能改为本地 root 允许的主机(fleet_forwarding_allowed_hosts,默认为空)。(d) 设备发送的仅是最小状态摘要——应用版本、操作系统类别、隔离状态(正常/隔离中)、已应用的策略版本、健康评分(0~100)和密钥世代——以及签名标头中的设备 ID 和组织 ID。通信使用系统的 curl(TLS),且仅限已注册的设备。
  • Sensor 配对(sensor_pairing.rs)——默认 OFF(sensor_pairing_enabled)。只会直接连接运维人员输入的 Sensor 地址,没有内置目的地。支持 TLS 1.3(通过系统的 openssl,并固定证书指纹),但没有指纹的旧式配对会在警告后回退为明文。Sensor 是另一款产品,不在本文档 Zero Telemetry 的适用范围内。
  • 实证型漏洞检测(active_vuln_scan.rs/web_vuln_scan.rs)——默认禁用,需要在设置中选择启用。本终端版的路径仅限于对本机已观测到的监听端口向 127.0.0.1 发起探测(由模块自身的测试固定)。
  • GUI 的“检查更新”按钮 — 只有用户按下按钮时,在 apt 系发行版上,GUI 才会用系统的 curl 对 https://lafine.net/apt/dists/stable/main/<arch>/Packages 发起一次 GET 以查询最新版本(超时 4 秒;不含机密的公开 URL,不附加标识符或查询字符串)。dnf、zypper、pacman 系不使用 curl,而是分别向各自的包管理器查询(9.3 的 #5)。
  • npm 签名验证 — roamswitch audit-npm-signatures <DIR> --confirm 只有在启用设置 npm_audit_signatures_enabled(默认 false)且用户加上 --confirm 运行时才会执行,由 npm 自身向 registry.npmjs.org 发起查询。

§10

  • 没有帐户 — 无需用户注册,无需登录,也无需请求电子邮件地址。
  • 配置仅在本地 — 以明文形式存储在 ~/.config/roamswitch/config.json 处,并且永远不会离开机器。没有云同步。
  • 评估结果仅在本地 — 安全报告和日志审核结果仅显示在屏幕上,并且如果用户选择,还可以导出到本地文件。
  • 许可证密钥已离线验证 — Pro / Business 许可证令牌包含 Ed25519 签名并使用公钥进行验证。不会向 lafine 服务器发出任何查询。
  • 没有崩溃报告 — 出现紧急情况时,不会向任何地方发送堆栈跟踪。
  • 每日更新程序不会发送任何有关您的信息 — 没有查询字符串、没有cookie、没有设备或安装级别的标识符,能表明软件身份的标头只有固定的 User-Agent: RoamSwitch-Updater。这是下载,而不是报告。链接防护判决、阻止的主机和扫描结果仅写入本地活动视图/日志,从不传输。
  • 使用威胁防护 DNS 期间,DNS 查询会发送到外部解析器。威胁防护 DNS(§4.5。默认开启,仅针对不受信任的网络)会将操作系统的 DNS 解析器指向 Quad9(9.9.9.9。也可选择 Cloudflare 的 1.1.1.2)。这期间的查询由操作系统而非 RoamSwitch 发送到该解析器,RoamSwitch 自身不会发送任何内容。要停止,请将 dns_enabled 设为 false。
  • 通知历史 7 天后自动删除,且限制长度 — 已发送通知的历史保存在本地,每次写入时都会删除超过 7 天的条目。每条标题最多 200 个字符,正文最多 2000 个字符(桌面通知弹窗正文最多 1000 个字符)。除非启用事件转发(默认 OFF),该历史不会离开本机。
  • 执行记录同样仅限本地 — 进程执行记录器(§5)的记录默认也只保存在本机(/var/lib/roamswitch/exec_log,0600)。由于包含命令行参数,会按保留期限(默认 14 天)和容量(默认 200 MB)自动删除。

§11

版本
版本对于价格许可证
社区版Linux 个人使用、公司内部业务使用(不限台数)免费专有免费软件(捆绑 EULA)
业务(计划)车队管理、策略分发、签署内部 apt、组织 SLA付费商业许可证。相同的二元+层门

Business 层级开始提供时,方针是向 macOS 版 Pro Lifetime 的购买者免费授予其本人 Linux 计算机上的 Business 功能。目前尚未提供 Business 层级,也没有管理服务器。

分销和供应链

频道详情
APThttps://lafine.net/apt (stable main)。 Release 使用 RSA-4096 密钥签名
转速https://lafine.net/rpm(单独的 Fedora / openSUSE)。 repomd.xml GPG 签名
尿素尿率roamswitch-bin(筹备中。由于 AUR 已停止新账户注册,目前通过 https://lafine.net/linux/dl/PKGBUILD 用 makepkg 安装。使用 sha256 和分离签名(validpgpkeys)验证 lafine.net/linux/dl 的 tarball)
压缩包https://lafine.net/linux/dl/ 提供 x86_64 / aarch64 的 .tar.gz + .sha256 + .asc(使用存储库签名密钥的分离签名)。由于源代码存储库不公开,面向公众的获取渠道是这里,而不是 GitHub Releases
  • 固定 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,开机 15 分钟后运行,之后每 24 小时一次,最多随机延迟 3 小时)运行的独立二进制。默认情况下,它是唯一会访问外部分发 URL 的组件(9.4 的可选功能除外)。每次运行:

  1. GET https://lafine.net/updates/v1/manifest 及其独立签名。
  2. 使用专用的威胁源签名密钥验证清单的 Ed25519 签名 - 与 Sparkle 应用程序更新密钥分开。服务器端 CI 每天都会对 feed 进行重新签名,以便私钥存在于 CI 秘密中;将其范围限定为提要意味着泄漏只会让攻击者提供错误的阻止列表(污水坑/警告 - 没有代码路径,没有应用程序更新路径)。应用程序更新签名密钥永远不会离开 macOS 钥匙串。未验证的清单将被丢弃 - 更新程序永远不会回退到未签名的数据。
  3. 如果威胁源版本发生更改,请下载该源,验证其 SHA-256 和 Ed25519 签名,并为链接防护自动安装它 (§4)。其他数据只在清单版本更新时获取(实证型漏洞检测的 CVE 映射和热门 npm 包名列表)。内核、操作系统软件包和语言生态的 CVE 映射,只在距上次更新约 30 天后才获取。Ubuntu 的 CVE 映射只在受支持版本的 Ubuntu 上获取;尚未安装的设备不等 30 天,在下一次每日运行时获取。所有数据都在验证签名后才安装。
  4. 清单版本更新时,也会获取Web 应用层检测规则(§5)。先验证 SHA-256 与 Ed25519 签名,并确认其可作为规则读取,然后以原子方式安装。无法读取的文件不会被安装,正在使用的规则也不会被替换。不会新增连接目标,而是并入同一次清单获取。Server Edition 遵循 cve_kernel_map_updates_enabled,关闭后完全不进行网络通信。
  5. 如果安装了 ClamAV,则调用 freshclam。
  6. 记录最新的应用程序版本以获取通知(更新程序 never 安装应用程序;在 Linux 上,用户运行 apt upgrade)。

任何步骤的失败(离线、签名错误、freshclam 不存在)都会记录到 updates.json 的 lastError,并且进程始终以 exit 0 退出,因此计时器运行永远不会产生 systemd 失败通知。整个机制可通过 updates.json 中的 "enabled": false 或 systemctl disable --now roamswitch-update.timer 禁用;此时产品仅使用软件包内置的数据运行。威胁情报源也随软件包提供,因此全新安装后即可使用,并且即使禁用了更新程序,也会通过 apt upgrade 更新。

§12

  • OS:Ubuntu 22.04 / 24.04 及更高版本、Debian 12 及更高版本、Linux Mint / Pop!_OS / elementary / Zorin、Fedora / RHEL 系、openSUSE、Arch 系(AUR 筹备中,通过 PKGBUILD 安装)、Raspberry Pi OS 64 位 (Bookworm)、Ubuntu for Raspberry Pi
  • 架构:x86_64 / aarch64
  • 必需:systemd、nftables、iproute2(ip neigh、ARP/NDP 固定)、conntrack、libnotify(桌面通知)(systemd 以外的组件通过 .deb / .rpm 依赖项自动安装)
  • 可选:clamav(恶意软件扫描)、nmcli(Wi-Fi 加密判定)、iw(无线信息的替代)、bluez(蓝牙防护)、systemd-resolved(威胁防护 DNS 的设置)、lsof(监听端口检测的替代)、nmap(主动漏洞验证的辅助)、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;可选的 Sensor 配对除外)
跟踪/跟踪订阅者日志记录(本地/日志)无
这个错误/无论如何错误处理无
正则表达式字符串解析(诊断、CSS 缩放)无
网址URL 解析(audit_url_safety 启发式)无(不获取)
计时日期/时间处理无
ed25519-dalek对许可证签名,以及更新程序的清单、威胁情报源等已签名数据进行离线验证(选择启用的 Sensor 配对也会使用)无
Base64/SHA2许可证令牌、金丝雀哈希无
库chown / kill / 系统调用无
伊德娜解码 IDN/punycode 主机以进行同形异义词检测无
nfq(仅限守护进程)链接防护的 NFQUEUE 绑定 — 纯 Rust,AF_NETLINK无(判定通道,无出口)
打开为打开“打开发行说明”以及 EULA、支持页面的链接而调用 xdg-open进程本身不进行通信
GTK3 / ksni(仅限应用程序)GUI 和托盘无
serde_yaml读取 /etc/roamswitch/guard.yaml(core)无
toml解析 Cargo.lock / poetry.lock(语言软件包的 CVE 扫描,core)无
md-5Link Guard 的 JA3 指纹哈希(不用于认证或完整性用途,core)无
bs58加密资产私钥的 Base58 校验(机密信息检测,core)无
rand生成选择启用的 Sensor 配对的设备密钥(core)无
tonic / tonic-prost / prost / tower / hyper-util与 Tetragon 传感器之间的 gRPC。仅限 Unix 域套接字,无 TLS(core)无(仅 Unix 套接字)
flate2RHEL 系软件包 CVE 映射的 gzip 解压(仅 updater。纯 Rust)无(仅解压)
inotify监控对蜜令牌(凭据诱饵)的访问(roamswitch-honeytokens)无
aya实验性 XDP 启动门的加载器(roamswitch-xdp-gate。由 daemon 引用)无(仅 bpf() / netlink)
urlencoding / gdk / gdk-pixbuf / glib / pangoGUI 的 URL 解码以及 GTK3 的周边 crate(仅 app)无
ureq + rustls(仅限 roamswitch-updater)每日更新程序的 HTTPS GET(清单、签名、有更新的数据)树中唯一的 HTTP/TLS 堆栈;仅限于更新程序二进制文件
要点

roamswitch-core、roamswitch-daemon、roamswitch-app、roamswitch-mcp 和 roamswitchkit 中均未出现连接互联网的 HTTP 客户端、TLS 堆栈或遥测 SDK。roamswitch-core 所含的 tonic / hyper 用于与 Tetragon 之间 Unix 域套接字上的 gRPC,不带 TLS,也不会连接网络。ureq + rustls 仅存在于 roamswitch-updater 中(CI 中的 scripts/audit_no_network.sh 强制执行此项)。任何地方都没有遥测 SDK。

附录 B

table inet roamswitch {                 # 配置文件防火墙(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 为 policy accept 且无规则。balanced / lockdown 为 policy drop(仅 balanced 允许 ICMP、ICMPv6)
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
}

table inet roamswitch {                 # Air-Gap(隔离)期间重建同一张表。input / output 均为 policy drop,仅放行 lo(已建立的连接也不放行)
    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 — 独立于配置文件表
    chain output {
        type filter hook output priority 0; policy accept;
        udp dport 443 reject with icmpx type port-unreachable   # 仅在启用 block_quic 时(默认关闭)
        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   # 每个连接仅将最初的 14 个数据包送去检查
    }
}

table inet roamswitch_dev_guard {        # §4 端口异常防护/手动端口隔离,独立的表
    chain input {
        type filter hook input priority -10; policy accept;
        iif != "lo" tcp dport { … } drop
    }
}

table inet roamswitch_portscan {        # 端口扫描的检测与阻断(默认开启)
    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 {     # 面向恶意 IP 的出站通信封锁列表(默认开启)
    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 {     # 针对 Docker 发布端口绕过防火墙的对策(仅在存在 Docker 原生 nftables 后端时)
    chain forward { type filter hook forward priority -10; policy accept; … iifname { … } drop }
}

table inet roamswitch_isolate {          # eBPF 检测时按进程隔离(按 cgroup / UID 指定)
    chain process_block { type filter hook output priority -100; policy accept; … 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-daem)" -a -i
# → 无输出 = 没有任何 TCP/UDP 互联网连接
sudo lsof -p "$(pgrep -x roamswitch-daem)" -a -U | grep roamswitch
# → 仅 /run/roamswitch/roamswitch.sock 和 events.sock(用于接收 eBPF 检测事件)(链路防护会额外使用 AF_NETLINK 套接字,
#   但这是内核通道,并非互联网套接字)

RoamSwitch 唯一的出口是每日更新:

watch -n 5 'sudo ss -tupn | grep -E "roamswitch-(daem|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(清单、签名、有更新的数据),随后退出。如果已安装 ClamAV,freshclam 还会连接 ClamAV 的镜像。

禁用它并确认完全沉默:

sudo systemctl disable --now roamswitch-update.timer
# 或将 /var/lib/roamswitch/updates.json 编辑为 "enabled": false
sudo strace -f -e trace=network -p "$(pgrep -x roamswitch-daem)" 2>&1 | grep -i 'connect('
# → 仅 connect 到 AF_UNIX / AF_NETLINK。AF_INET / AF_INET6 从未出现。

C.2 可重复的防御验证(Docker 测试套件)

RoamSwitch 的各种防御机制(Air-Gap 阻断、防火墙被强制清除时的自我修复、勒索软件检测、/tmp noexec、Yama LSM、同形异义词检测等),可以使用官方支持存储库(roamswitch-support)中公开的 Docker 测试套件在本地重现运行。该套件只操作独立的网络命名空间和容器内的文件,因此不会影响主机的网络和文件。但由于以 --privileged 运行,Yama 的 ptrace_scope、suid_dumpable 等由整个内核共享的设置会反映到主机上,即使删除容器也不会恢复。请在一次性环境中运行。

# 获取并复现验证套件(使用官方发行包,在隔离容器内自动验证)
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)获取发行包,并在独立的网络命名空间和挂载命名空间内执行验证并作出判定。公开的套件是 pentest.sh(PENT-1 至 7 共 7 个场景)和 selftest.sh(Air-Gap、自我修复、金丝雀、突发检测、隔离 Vault 等守护进程的破坏性自测)。下表中的 ID 是验证记录上的连续编号,与脚本内的检查并非一一对应。BadUSB(SP-6)、链接防护的各模式(SP-7 系列)、ARP 固定与 VPN 终止开关(SP-8 至 10)、内核强化的单项确认(SP-11 系列)以及 PENT-8、9,不包含在公开套件中,而是通过实机、独立 netns、手动注入事件等其他步骤确认的项目。

(1) 自主网络防御和气隙自愈

身份证号场景验证方法通过标准
SP-3气隙启用 → 出口下降发送 enable_air_gap IPCpolicy drop 应用于输出挂钩;所有出站流量均被阻止
SP-3b防火墙强制清除和自我修复通过 nft delete table inet roamswitch 清除规则Anti-Clobber 自愈功能可在 5 秒内恢复掉落规则
SP-3c跨守护进程重新启动时失败关闭在 Air-Gap 期间终止守护进程(selftest.sh 中通过 kill 发送 SIGTERM)并重新启动出口丢弃策略在重启后无缝持续
SP-3d授权气隙恢复发送授权disable_air_gap IPC取消丢弃策略,删除所有标记文件,恢复出口
SP-8预防性 ARP/NDP 固定在隔离的netns网关环境中启用→发送欺骗性的免费ARP网关邻居条目仍为 PERMANENT;欺骗 ARP 被忽略
SP-9VPN 终止开关 (WireGuard)单独网络中的 Live WireGuard 服务器,导入 .conf → vpn_up握手通过inet roamswitch_vpn完成;隧道死亡时零泄漏
SP-10VPN 终止开关(Tailscale)存根 tailscale CLI + 单独的网络,tailscale0 IF隧道外明文 TCP/80 和本地 DNS 被阻止;允许 DERP 和 MagicDNS

(2) 内核和挂载强化

身份证号场景验证方法通过标准
SP-11a网络系统强化运行 apply_kernel_sysctl_hardening IPCrp_filter=1、tcp_syncookies=1、accept_redirects=0 达到目标值
SP-11b内存保护和 Yama LSM验证 ptrace_scope 和 suid_dumpablekernel.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-2bshred 上没有误报跨测试文件运行 shred -n 3 -u允许列表匹配可防止误报检测或冻结
SP-2c关键流程从未冻结在突发检测期间验证冻结目标逻辑关键守护进程(dockerd、systemd、containerd)永远不会被冻结
SP-4EICAR 测试字符串仅用于通知将 EICAR 测试字符串写入 ~/Downloads/不发出通知,仅记录到通知历史;文件永远不会被自动隔离
SP-4b真正的恶意软件签名首先确认在监视文件夹外写入真实签名引发模式批准对话框;仅在用户确认后才隔离
SP-4cQuarantine Vault 权限强化检查 Vault 目录和示例文件的权限保管库目录设置为 0700,隔离样本设置为 0400
SP-4d特权IPC安全原始文件恢复发送 restore_quarantine_file IPC文件已使用用户权限 (0644) 恢复到原始路径并添加到排除项

(4) 被动链接防护和网络钓鱼防御

身份证号场景验证方法通过标准
SP-7mode = block 严格执行连接到 feed-listed 或 blocklistExtra 主机在 TLS 层丢弃(SNI 解析);未列出的安全主机达到 HTTP 200
SP-7bmode = warn 故障关闭保持连接到警告主机,无用户响应(超时 8 秒)超时时丢弃数据包 (fail-close);仅在明确批准的情况下才允许
SP-7cmode = off 完整拆解禁用链接防护inet roamswitch_linkguard 表已完全卸载

(5) BadUSB 缓解和设备控制

身份证号场景验证方法通过标准
SP-6连接键盘祖父检查守护进程启动时连接的键盘现有设备不再适用;仅抓取后续热插拔

(6) 模拟攻击的自查项目(PENT-1 至 7 为公开的 Docker 套件,PENT-8、9 为另行的手动验证)

身份证号攻击向量模拟细节防御/检测标准
彭特-1C2 渗漏在气隙下尝试将出口 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-3ptrace内存窥探PTRACE_ATTACH 目标提取秘密/令牌被 Yama LSM kernel.yama.ptrace_scope=1 拒绝
PENT-3b核心转储秘密抓取触发进程崩溃以获取内存核心转储由 fs.suid_dumpable=0 阻止
PENT-4金丝雀篡改~/Documents/ 中的清零金丝雀诱饵文件实时检测大小异常和哈希变化
PENT-4b金丝雀批量擦拭大规模删除金丝雀诱饵文件立即检测到文件丢失,引发事件
PENT-5IP地址欺骗发送带有欺骗源IP的数据包由 rp_filter=1 删除(反向路径过滤器)
PENT-5bSYN 洪水 DoS淹没半开 TCP 连接受 tcp_syncookies=1 保护的状态表
PENT-5cICMP 路由劫持注入伪造的 ICMP 重定向数据包由于 accept_redirects=0 被忽略
PENT-6西里尔文同形异义词网络钓鱼导航至 gооgle.com(西里尔字母同形异义词)由 LinkGuard 识别,得分 40(危险/块)
PENT-6b原始 IP 和明文流量直接出站连接到原始 IP准确标记为安全风险因素
PENT-7OpenAI API 密钥泄露在代码或日志中公开 sk-proj-... 令牌通过香农熵分析检测并屏蔽
PENT-7bGitHub 令牌泄漏在代码中公开 ghp_... 个人令牌通过token模式和熵验证进行匹配
PENT-8内核 LPE(碎片间隙)抢占非特权 unshare -U -r(CVE-2026-53362 尝试)在 v1.1.0 的测量中,user.max_user_namespaces = 0 使其立即被以 Operation not permitted 拒绝。当前的 Client Edition 默认不执行这种拒绝(guard.yaml 中的 auto_enable_userns_restriction 默认禁用。Server Edition 始终应用)
PENT-9eBPF 威胁传感器和流程冻结通过 Falco eBPF 套接字注入关键/紧急警报进程在几毫秒内通过 SIGSTOP 冻结(状态:Ts);已部署气隙

关于验证类型:上表中,PENT-2/2b(noexec)、PENT-3/3b(Yama、核心转储)、PENT-5/5b/5c(rp_filter、SYN Cookie、ICMP 重定向)是用于确认 RoamSwitch 所配置的内核或 mount 标准功能确实生效的项目。PENT-8(禁止用户命名空间)也属于同一类型,但这是 v1.1.0 时点的记录,当前的 Client Edition 默认不应用。从攻击方测试 RoamSwitch 自身检测与阻断逻辑的项目是 PENT-1/1b/1c(Air-Gap 与自我修复)、PENT-4/4b(金丝雀)、PENT-6/6b(链接防护)、PENT-7/7b(机密信息检测)和 PENT-9(eBPF 检测后的冻结与阻断)。PENT-2c 用于确认日常工作不受影响。

(7) 自查总结(无 Falco [独立运行] 与有 Falco [eBPF 联动])

这是在独立的 Docker 验证环境(目标:Ubuntu 24.04 + RoamSwitch Client Edition;攻击者:Nmap / curl / hping3 等)中,通过模拟攻击实施的完整自查的结果总结。下表是 2026-09-06 对 Client v1.1.0 的测量结果;2026-09-25,由 1.10 系列源码构建的修复版(非公开发布版本)在客户端版测试套件的 11 个项目中全部 11 项通过(请参阅公开仓库中的 audit/RESULTS-PENTEST-2026-09-25.ja.md)。

验证类别不带 Falco(独立强化)使用 Falco(eBPF 增强型)辩方判决
外部端口扫描电阻(Nmap SYN)PASS(100% 过滤/放入 Lockdown)PASS(100% 过滤/放入 Lockdown)总外部隐身
外部直接访问截止(TCP/80、3306)PASS(超时下降/零响应)PASS(超时下降/零响应)零入境暴露
内核 LPE(碎片间隙)缓解合格(v1.1.0 的测量。在 user.max_user_namespaces = 0 的环境中阻断 unshare。当前的 Client Edition 默认关闭)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 及其他 roamswitch_*)进行隔离,但应避免诸如 nft flush ruleset 之类的整个规则集操作
链接防护只能通过 TLS SNI 查看浏览器的 DoH;加密的 Client Hello (ECH) 也隐藏了这一点一旦 ECH 被广泛部署,使用 ECH 的连接到支持 ECH 的站点就无法通过主机名来识别本地阻止列表和启发式仍然作用于 DNS/HTTP 和非 ECH TLS; DNS 威胁保护(§4.5,默认开启)涵盖解析器路径
当 updates.json 中的 enabled 为 false 时,数据源的新鲜度取决于发布间隔关闭更新后,提要仅与最后一个 apt upgrade 一样最新最严重的检测(品牌同形异义词)无需馈送;选择重新加入或启用威胁防护 DNS,以获得始终最新的覆盖范围
本文档基于 RoamSwitch for Linux v1.11.1(内核 LPE 加固/eBPF 运行时防护/攻击进程 SIGSTOP 冻结/Falco 低负载优化/实时监控 Vault 隔离及特权 IPC 原样还原/Air-Gap 自愈机制/DNS 隧道检测/取证证据保全/蜜令牌/漏洞检测 pass_age 追踪/NIST CSF・CIS Controls 映射/诊断复评/MCP 配置审计)当时的实现。逐项变更历史请参阅 roamswitch-support 的 CHANGELOG。最新信息请参阅 roamswitch.com/linux。