Lafine系統設計·技術文件

RoamSwitch 架構與安全白皮書

本文檔解釋了 RoamSwitch 使用哪些權限運作以及它在該邊界上執行的操作。 它不包含任何行銷語言;此處所述的所有內容都可以根據運輸應用程式進行驗證 二進制及其實際行為。

版本 v1.5 適用對象 RoamSwitch 1.9.47 (build 104) 需求 macOS 13.0+ / Apple Silicon 發布日期 2026-09-15 Team ID GV76B6G4YU

§1

RoamSwitch 是一款適用於 Mac 的選單列應用程式。取決於您對網路的信任程度 目前連接到時,它會自動切換 macOS 防火牆、共享服務、AirDrop、 和 DNS。它還監視 ARP 欺騙、向外部開放的連接埠、USB 儲存和 類似勒索軟體的加密活動,當它檢測到危險的東西時,它會盡可能地 使用資料包過濾器 (pf) 切斷流量-緊急氣隙。

換句話說,RoamSwitch 安裝了一個具有 root 權限的守護進程,如果它願意的話,可以停止所有 Mac 上的網路流量。它是一個人建造的,「請相信我」是不夠的 證明這種特權是合理的。因此,本文檔以您可以驗證的形式解釋了設計。

這是給誰的

  • 工程師決定是否安裝
  • 想要在評論或文章之前了解內部結構的安全研究人員和記者
  • 合作夥伴公司的安全人員評估內部部署或 OEM 捆綁

本文檔不涵蓋的內容

它不涉及檢測閾值調整、誤報統計或 UI 演練。 它解釋的是四件事:特權、進程邊界、資料流和 密碼學。功能規格本身位於捆綁的 MCP 資源中 roamswitch://docs/features 和應用程式內幫助。

揭露內容以及揭露程度(揭露政策)

本文檔是在攻擊者已經擁有分散式二進位檔案的假設下編寫的。 此處顯示的每個端點 URL、識別碼、檔案路徑、XPC 協定和嵌入式公鑰 可以在幾分鐘內從運輸 RoamSwitch.app 中取出 stringscodesign -d 或流量代理。因此將它們寫在這裡 給攻擊者帶來任何新東西。它唯一進步的是審稿人的理解。

另一方面,從二進位檔案看不到的伺服器端實作-rate-limit 閾值、金鑰、管理端點、資料庫架構、Firebase 專案佈局 - 不包括在內。 OEM 和合作夥伴整合設計也超出了本文的範圍,並在單獨的章節中介紹。 內部文件。 「透過觀察客戶你可以學到什麼」是這則訊息的揭露線 文檔。

補充

本文檔對應於頂部註明的版本的來源。行為改變的地方 在較高的版本中,該文件被修改,版本號和目標版本也被更新。 如果您發現文字和程式碼之間存在差異,請透過以下方式告知我們 lafine.net/contact.html

§2

RoamSwitch.app 由三個可執行檔組成。其中只有一個人享有特權;的 另外兩個以登入使用者權限運行。所有三款產品均啟用了強化運行時、開發人員 ID 簽名並經過公證。

RoamSwitch 元件與信任邊界示意圖 MCP 用戶端 Claude Desktop / Code MCPServer 使用者權限 僅唯讀 RoamSwitch.app 使用者權限 選單列 / 監控 / UI Helper root / LaunchDaemon SMAppService.daemon 系統二進位檔 pfctl / socketfilterfw … stdio (JSON-RPC) 僅唯讀 * XPC mach service 透過程式碼簽署授權 程序執行
* MCPServer不連接到應用程式本身;它直接讀取共享首選項域和監控模組(§8)。
每個組件的權限和角色
執行檔特權可以做不能做
漫遊交換器
.app
登入用戶 監控網路狀態、執行診斷、繪製 UI、透過 XPC 呼叫幫助程式、透過 defaults 更改 AirDrop、啟動 ClamAV(可選) 直接操作防火牆、pf或系統守護程序(所有這些都透過幫助程序)
漫遊開關
助手
HelperProtocol(第 3 表)中所列的操作:防火牆/隱形、共用守護程式的載入/卸載、套用 pf 規則集、變更 DNS、傳送訊號 還有什麼。沒有用於運行任意命令的介面。它也沒有網路發送權限
漫遊交換器
MCPServer
登入用戶 讀取並格式化診斷值,搜尋本機知識庫;結果透過stdio傳回給客戶端 變更設定、切換鎖定、隔離連接埠、彈出裝置。它不打開套接字。它不會透過網路發送任何內容

權利和簽署

  • 三個目標都有 ENABLE_HARDENED_RUNTIME = true
  • 應用程式沙箱已停用 (com.apple.security.app-sandbox = false)。幫助程序和 MCPServer 的權利是空字典。
  • 分發版本經過開發者 ID 應用程式簽署、Apple 公證並裝訂 (§10)。
設計權衡

未使用應用程式沙箱。 RoamSwitch需要從IOKit讀取硬體UUID,使用CoreWLAN並 DiskArbitration,枚舉其他程序的監聽套接字(lsof),開啟一個XPC 連接到 LaunchDaemon,並產生系統二進位。這一切在內部都是不可能的 沙箱,因此它被禁用。

有四件事可以彌補這一點。首先,強化運行時。二、開發者ID簽章和 公證。第三,只有一個可執行檔以 root 身分運行——助手——以及該助手可以做什麼 是固定和枚舉的(§3表)。四、與helper的連接受代碼限制 簽名(§3)。

§3

如何註冊

此幫助程式使用 SMAppService.daemon(plistName:) 註冊為 LaunchDaemon。的 嵌入到應用程式中的 plist (Contents/Library/LaunchDaemons/com.tetsuharu.RoamSwitch.Helper.plist) 僅聲明 LabelBundleProgram、單一 MachServices 條目,以及 AssociatedBundleIdentifiers。由於 SMAppService 的工作原理, 除非應用程式位於 /Applications 中,否則甚至無法註冊。在第一 註冊後,只有用戶在系統中手動批准後,助手才會激活 設定。

它接受誰的連結 (ClientValidator)

助手檢查 NSXPCListener 的連接過程 shouldAcceptNewConnection 並且只將 HelperProtocol 分發給那些 通過。檢查使用 audit_token 而不是 PID, 避免PID重複使用和TOCTOU。

# Release 版本對連線方要求的程式碼簽署條件
identifier "com.tetsuharu.RoamSwitch"
  and anchor apple generic
  and certificate leaf[subject.OU] = "GV76B6G4YU"

此要求透過 SecCodeCopyGuestWithAttributes 檢查並 SecStaticCodeCheckValidity,不通過則斷開連接。僅 為了開發方便,DEBUG 版本刪除了 team-ID 引腳。實際運送的始終是 發佈建置。

邊界要點

助手的安全取決於這個單一的程式碼簽章要求。任何滿足它的東西(a 正確簽署的 RoamSwitch.app) 可以呼叫下表中的每個操作。有 沒有通道可以向其提供任意命令,但該表中的操作在功能上並不弱 他們自己。如果 RoamSwitch.app 本身被接管,這些操作將會傳遞給 攻擊者。

助手可以做什麼(完整清單)

Shared/HelperProtocol.swift 中定義的特權操作都是這些。 沒有此處未列出的特權 API。

HelperProtocol — 以 root 身分執行的所有內容
方法它的作用二進位/API 呼叫
設定區塊全部(_:) 開啟或關閉應用程式防火牆和隱身模式 /usr/libexec/ApplicationFirewall/socketfilterfw
--setblockall / --setstealthmode
取得區塊所有狀態(...) 讀取上面的當前值 socketfilterfw --getblockall
設定共享服務啟用(_:) 卸載/載入 SSH/SMB/螢幕共享守護程式。停止時,它僅記錄“正在運行的”並僅恢復那些 /bin/launchctl list / unload -w / load -w
(固定為三個:ssh.plist / com.apple.smbd.plist / com.apple.screensharing.plist
啟用NetworkAirGap(...)
disableNetworkAirGap(...)
應用並解除緊急完全阻擋 (block drop all)。經歷 PFRulesetCoordinator (§4) /sbin/pfctl -f / -e / -sr
setGuardedDevServerPorts(_:) 使用 pf 僅阻止 external 連接到給定的開發伺服器連接埠(本機主機通過)。傳遞一個空數組會提升所有這些 /sbin/pfctl(也透過協調器)
setSecureDNSServers(_:)
restoreOriginalDNSServers(...)
getCurrentDNSServers(...)
將活動網路服務的 DNS 切換為阻止惡意軟體的 DNS (Quad9 9.9.9.9 / Cloudflare 1.1.1.2),備份原始設定並還原它們 /usr/sbin/networksetup
-listallnetworkservices / -getdnsservers / -setdnsservers
setLinkGuardSinkhole(_:) 連結防護(§5)。將給定的網路釣魚/詐騙網域寫入 /etc/hosts 的分隔託管部分作為 0.0.0.0,然後刷新 DNS 快取。空數組會刪除該部分。域被標準化和重複資料刪除; IP 和垃圾被丟棄;上限為 60,000;透過臨時檔案+原子替換寫入 重寫 /etc/hosts (FileManager.replaceItemAt)
/usr/bin/dscacheutil -flushcache
/usr/bin/killall -HUP mDNSResponder
lockGatewayARP(_:)
unlockGatewayARP(...)
getGatewayARPLockStatus(...)
預防性閘道 ARP/NDP 鎖定 (§5)。將給定的 IP → MAC 映射(IPv4 網關、IPv6 預設路由器、連結 DNS 解析器)固定為 permanent 鄰居快取項目。 IP 和 MAC 格式經過驗證;協調模型(不在請求中的引腳將被刪除)。引腳集保留為 gateway_arp_lock.json /usr/sbin/arp -s / -d
/usr/sbin/ndp -s / -d
wireGuardImport(_:)
wireGuardForget(...)
VPN 隧道 (§5)。幫助程式儲存/刪除 0600 處的 WireGuard .conf 文字 文件只寫
wireGuardUp(端點IPv4:端點IPv6:連接埠:)
wireGuardDown(...)
wireGuardStatus(...)
將隧道開啟/關閉/讀取狀態。端點主機名稱是 由應用程式 解析,並且 IP 被傳遞給助手(助手的 DNS 可以被終止開關切斷) 自製程式 wg-quick up/downwg showwireguard-tools;若未安裝則停用)
終止進程(pid:forceKill:) 掛起 (SIGSTOP) 或強制退出 (SIGKILL) 流程。用於包含類似勒索軟體的進程。僅 pid > 1 Kill(2) 系統呼叫(不是子程序)
取得助手版本(...) 傳回助手的版本字串(用於應用程式相容性檢查)
設計權衡

terminateProcess 可以將 SIGKILL 傳送到任何進程,只要 pid > 1setSecureDNSServers 接受任何 DNS 伺服器字串。那就是 特徵所需的寬度,但並不窄。判斷它的前提是 它前面的代碼簽名檢查 (ClientValidator) 是唯一的門。

助手內部的狀態

  • HelperTool.shared 是跨連線共享的單一實例。它曾經是每個連線的單獨實例,因此打開新連線的緊急遏制可能會引發一場失去「要帶回哪些服務」的競賽。
  • 共享服務和 DNS 備份僅在序列佇列 (stateQueue) 上變更。

§4

touch pf 的四個功能:緊急氣隙、開發伺服器連接埠防護、VPN 隧道 WireGuard 終止開關(§5,1.7.6+)及其 Tailscale 終止開關(§5,1.8.0+ - 僅選定的後端)。他們所有人總是會走 透過單一入口點 PFRulesetCoordinator,並且從不執行 pfctl -f 他們自己。

為什麼只有一個入口點

先前,這兩個功能各自獨立加載帶有 pfctl -f 的規則,相互競爭 超過 pf 的單一主要規則集。如果港口警衛的狹義規則 block ... port {…} 是 在氣隙的 block drop all 之後加載,您最終可能會處於螢幕 說“隔離”,但 Mac 仍然可以訪問。這個bug是透過實際攻擊發現的 來自另一台主機的機器,並在 1.4.3 中修復(故事寫在 docs/marketing/zenn/03_lan_side_attack_test.md)。

現在如何運作

  • 每次都完全重建。 整個所需的規則集從當前狀態重建並一次應用。它永遠不會作為 diff 來應用。
  • 一個串列佇列。 每個 pf 變更都在同一個 DispatchQueue 上執行,因此無論它來自 XPC 連接、幫助程式的啟動還是故障安全計時器,變更都會按順序處理。
  • 優先順序如下(排在上面的優先)。
    1. 緊急氣隙 → set skip on lo0block drop all(不考慮其他)
    2. VPN 終止開關 → block drop allpass quick 僅適用於:lo、隧道介面 (utunN)、與固定端點 IP 的 UDP 握手、DHCP 和 ICMP
    3. 開發伺服器守衛 → block drop in quick proto tcp ... port { … }
    4. None → 重新載入 /etc/pf.conf 並將 pf 返回原始狀態
  • 應用後讀回。 pfctl -sr讀回規則以確認block drop all或每個連接埠的規則確實已載入。 pfctl -f 被默默忽略的情況不被視為成功。
  • 臨時檔案被寫入包含 UUID 的路徑,並在應用後刪除(v1.4.5 放棄了固定路徑,取而代之的是難以猜測的路徑)。狀態目錄是/Library/Application Support/RoamSwitch
API 行為

來自 enableNetworkAirGapsetGuardedDevServerPorts 的 XPC 回應, (Bool, String?),報告操作是否一路通過 回讀。呼叫者(例如 ARPSpoofContainmentManager)在失敗時重試,並且如果 它仍然失敗,它將訊息直接顯示在螢幕上:「交通尚未停止。請關閉 現在有無線網路。 」

§5

塊的種類各不相同

塊的種類
種類適用範圍觸發環回
緊急氣隙 停止所有傳入和傳出的流量 當偵測到類似勒索軟體的加密活動時。在偵測到 ARP 欺騙時,它僅在鎖定狀態下立即觸發;在平衡/可信任網路上,它會通知(您手動觸發它)。當在 shell 歷史記錄中偵測到「ClickFix」型的已知可疑指令模式時,例如將以 curl 等取得的內容進行 Base64 解碼後直接傳給 shell/AppleScript(1.8.7+,Pro,預設關閉)。 不用於日常外出防護 透過 set skip on lo0
VPN 終止開關 除了隧道、握手、DHCP 和 ICMP 之外的所有內容 當 VPN 隧道(§5,Pro,預設關閉)開啟並且您加入不受信任的網路。一直保持到隧道建立(以及隧道關閉時) 透過 set skip on lo0
開發伺服器連接埠防護 external TCP 連接到給定連接埠 一鍵手動隔離,或偵測到陌生監聽連接埠時自動阻斷(Pro) 來自本機主機,未更改
日常不可信網路保護 防火牆和隱身,共享停止(§6)。未使用 pf 當您連接到尚未註冊的網路時
連結衛士 僅網路釣魚/詐騙網域的名稱解析(0.0.0.0 透過 /etc/hosts)。未使用 pf 威脅來源上的目的地,或品牌同形異義詞。預設開啟(專業版) 不受影響
預防性 ARP/NDP 鎖定 僅網關的 MAC、IPv6 路由器和連結 DNS(鄰居快取)。未使用 pf 加入不受信任的網路時(專業版,預設為關閉)。重新固定每次網路變化 不受影響

如何防止氣隙揮之不去

  • 最多在 10 分鐘後解除。助手本身仍會在啟動後立即以及每 60 秒的計時器上執行 releaseAirGapIfExpired(),但作為與之完全獨立的安全網,一個完全不帶 KeepAlive 的專用 LaunchDaemon(AirGapFailsafe)會每 3 分鐘單獨喚醒一次,執行相同的判定與解除。即使助手本身當機、連同其自身的自動解除計時器一起失效,這個安全網也不受影響(1.8.6+)。
  • 這個專用守護程式完全不會觸碰互動式助手本身的啟動設定。先前(1.8.5)曾使用助手自身的 KeepAlive.PathState 來實現這一恢復機制,但它偶爾會與正常的應用程式結束操作發生衝突,導致非預期的重新啟動,因此該方案已被撤回,改用目前完全獨立的守護程式方式。
  • 時間戳的判定採用單調遞增時間(ProcessInfo.systemUptime)與掛鐘時間相結合的混合方式。通常優先使用單調遞增時間,因此不受 NTP 校正或手動修改系統時間的影響。僅在單調遞增時間不可信時(即重新啟動後:目前值小於已儲存的值,判定為發生了重新啟動)才會回退到掛鐘時間。
  • 在重新啟動或守護程式重生後重新套用。 啟動時,幫助程式使用 reapplyFromDisk() 讀取磁碟上的狀態,並依氣隙、連接埠防護、系統預設的順序自行恢復(10 分鐘規則也適用於此)。
  • 失敗的提升被視為失敗。 如果 block drop all 實際上無法刪除,則時間戳會被寫回,以便故障安全計時器和重試有一些收斂。螢幕絕不會錯誤地顯示「抬起」。
  • 您可以隨時收回控制權 - 使用模式中的提升按鈕,或只需關閉 Wi-Fi。

如何恢復端口防護誤報

未知端口自動阻止(Pro,啟動 Pro 後預設開啟)可以阻止合法端口 LAN 接收器 — LocalSend、Syncthing、啟用防護後啟動的任何內容。當那個 發生時,請透過通知橫幅上的「允許」按鈕或中的匹配行來允許它 “暴露連接埠”螢幕。一次允許的可執行檔被記錄為已知且不會被阻止 再一次(PortAnomalyGuard.allowPort(_:))。請注意,通用腳本解釋器 (Python、Node.js、Netcat 等)嚴格受 path:port 限制,而非二進位 獨自一人以防止離地生活的攻擊。 Apple系統守護程式滿足 anchor applerapportd,支援切換等)不是 首先觀看。

自癒勒索軟體金絲雀誘餌和遏制抑制

如果金絲雀誘餌檔案被篡改或重新命名並觸發緊急氣隙,則釋放 驗證安全性後的遏制會自動重新產生遺失或損壞的金絲雀文件 返回從嵌入模板產生的真實基線雜湊值,立即恢復不間斷的 kqueue 監視(防止對手基線污染)。 此外,在主動遏制期間(顯示緊急模式時),會自動抑制冗餘通知警報和定期後台完整性輪詢的重新觸發事件,以避免在事件回應期間分散用戶的注意力。

下載檔案的多層偵測(靜態簽章 + ClamAV)— 1.8.7 新增靜態簽章層

新出現在下載、桌面或文件資料夾中的檔案,會先經過 StaticSignatureScanner 的輕量級靜態簽章比對(僅檢查前 4MB,只做簡單的 AND/OR 位元組樣式比對,不進行正規表示式或熵值計算)。它不需要 EndpointSecurity entitlement,即使未安裝 ClamAV 也能持續運作。偵測範圍被刻意限定在無需真實惡意軟體樣本也能確保準確性的部分——即業界標準的 EICAR 測試簽章,以及公開的攻擊手法文件(如 PayloadsAllTheThings)中記載的 5 種經典反向 shell 單行指令(bash/sh/dev/tcp/nc -e、Python 的 pty.spawn、Perl 的 Socket、PHP 的 fsockopen)。我們不會在沒有真實樣本的情況下,為特定惡意軟體家族撰寫位元組層級的「簽章」——那只會帶來一種無法驗證的虛假安全感。這些樣式本身以 XOR 0x5A 混淆後儲存,以避免 RoamSwitch 自身的執行檔因明文包含 EICAR 字串等內容而被 ClamAV 誤判。

只有被靜態簽章層判定為「乾淨」的檔案,才會繼續進入 ClamAV 掃描。新出現在下載、桌面或文件資料夾中的檔案,無論是否帶有 .tmp 副檔名或 com.apple.quarantine 屬性(例如透過終端機複製而來),都會立即被 ClamAV 掃描。EICAR 等業界標準測試簽章會被視為「無害」,不發出通知,僅記錄至通知歷史,也不會被隔離或攔截(只有真正的惡意軟體樣本才會被隔離)。此外,即使隔離資料夾(Quarantine)中已存在同名的歷史隔離檔案,新的威脅也會以帶時間戳記的唯一檔名可靠地移入隔離資料夾,徹底避免因命名衝突導致威脅殘留在原始位置的漏判情況。

監控新的自動啟動註冊(LaunchAgent/LaunchDaemon)— 1.8.7

透過 FSEvents 即時偵測新放置到 ~/Library/LaunchAgents/Library/LaunchAgents/Library/LaunchDaemons 中的 .plist 檔案(PersistenceMonitorGuard)。由於不使用 EndpointSecurity 的 ES_EVENT_TYPE_AUTH_CREATE,因此無法攔截寫入行為本身,能做到的是在寫入後的數秒內察覺並判定其內容。

判定的關鍵不在於「執行檔本身的簽章」,而在於「它直接要啟動的是什麼」。2026 年報告的實際手法——例如偽裝成 Google Update 的 LaunchAgent 中隱藏經 Base64 編碼的 bash 指令碼,或 root 權限的 LaunchDaemon 在每次啟動時重新執行經 Base64 編碼的 AppleScript 酬載——都是把惡意程式碼藏在指令碼本身裡,同時直接呼叫簽章合法的 /bin/bash/usr/bin/osascript。只檢查執行檔本身簽章的驗證方式會被這類手法完全繞過。因此,任何新註冊的 LaunchAgent/LaunchDaemon,只要直接啟動裸腳本直譯器(bashshzshosascriptpython3perlrubyphp 等),即使該直譯器本身的簽章合法有效,也會被無條件發出警告。傳遞給它的引數還會進一步交給 StaticSignatureScanner 判定,如果包含已知的可疑樣式,會一併寫入通知。透過 BundleProgram(指向應用程式套件內已簽章的編譯執行檔的形式,包括 RoamSwitch 自身的 Helper)註冊的項目,則照常僅透過 codesign --verify --strict 進行簽章驗證。

偵測 ClickFix 並觸發緊急遮斷 — 1.8.7,預設關閉

「ClickFix」是一種社交工程攻擊手法:偽造的 CAPTCHA 或錯誤提示畫面會指示您「開啟終端機並貼上以下指令以完成驗證」,誘使使用者親自執行該指令。偵測到的數量在 2024 至 2025 年間激增超過 500%,截至 2026 年,它已被視為 macOS 上最猖獗的攻擊途徑之一。由於實際執行的是使用者自己合法的 shell 操作,因此會完全繞過 Gatekeeper 的簽章驗證。

選單列 →「惡意軟體防護」→ ClickFix 防護(Pro,預設關閉)。透過 FSEvents 監控對 ~/.zsh_history~/.bash_history 的附加寫入(僅針對該防護啟動後新增的行,不會回溯既有歷史記錄),只偵測與 StaticSignatureScanner 共用的反向 shell 單行指令,以及將 Base64 解碼內容直接透過管線傳入 shell 或 osascript 的特定組合樣式。正規安裝程式中極為常見的單獨 curl | bash,由於與 Homebrew、rustup、nvm 等官方安裝步驟難以區分,被刻意排除在偵測範圍之外。一旦偵測到符合項,就會觸發前文所述的緊急氣隙。由於指令已經執行完畢,這屬於事後應對,但仍有可能攔截正在進行中的多階段酬載下載或憑證外洩。與其他氣隙觸發條件一樣,它最多會在 10 分鐘後自動解除(見本節開頭)。

已知的限制。 由於偵測的前提是寫入 shell 歷史記錄,因此無法應對繞過終端機、直接透過 applescript:// URL scheme 呼叫 Script Editor、且完全不向磁碟寫入酬載的無檔案(fileless)變種。針對此類變種,Apple 自身也導入了一定的緩解措施(對未識別指令碼彈出確認對話框),但也有報告指出存在繞過該措施的變種。我們在真機上使用 macOS 統一日誌(log showlog stream)對這條路徑進行了驗證,但未能找到可用於判定的特有日誌訊號。與其把無法涵蓋的部分標榜為已涵蓋,我們選擇如實說明這項限制。

Docker 風險偵測防護 — 1.8.9,Pro・預設關閉

選單列 →「惡意軟體防護」→「偵測 Docker 特權容器・docker.sock 掛載」(DockerEventGuard)。由於該程式碼庫中沒有像 docker events 那樣建立持久串流連線的先例,因此採用與 PortAnomalyGuard 相同的定時輪詢方式。每 20 秒透過輕量的 docker ps -q 取得容器 ID 集合,僅對與上次的差異(新啟動的容器)透過 docker inspect --format 進行詳細確認。用於判定的格式字串特意與 Linux 版的 roamswitch_core::health::LinuxHealthChecker::DOCKER_INSPECT_RISK_FORMAT 保持一致,因此兩個平台會偵測完全相同的條件(--privileged 啟動,或 /var/run/docker.sock 綁定掛載)。

即使偵測到也不會自動封鎖。特權容器或 docker.sock 掛載是可能導致容器逃逸的高風險「設定」,但並非已確認的入侵(也存在正當用途,例如刻意以 privileged 模式執行監控代理)。由於大多數使用者並不使用 Docker,因此即使在 Pro 授權下也預設保持關閉。不使用 EndpointSecurity entitlement;docker CLI 依 Docker Desktop 預設安裝路徑・Homebrew・which 的順序尋找。已在真實 Docker Desktop(29.7.2)環境中驗證 privileged 容器・docker.sock 掛載容器・一般容器三種情境,確認無誤判也無漏判。

機密資訊・API 金鑰外洩稽核 — 1.8.4 新增,1.8.9 支援資料夾掃描

選單列 →「惡意軟體防護」→「機密資訊・API 金鑰外洩稽核」。貼上文字後會立即偵測 API 金鑰與權杖的外洩模式,顯示行號、已遮罩的字串以及依類型給出的建議處理方式(SecretLeakAuditor,完全本機處理)。1.8.9 新增「選擇資料夾掃描」功能,使用相同的偵測引擎對目錄(如原始碼簽出目錄)進行遞迴稽核(auditDirectory(at:))。會自動排除 .gitnode_modulestargetvendordistbuild__pycache__venv,並跳過超過 2MB 的檔案或判定為二進位的檔案。處理在獨立執行緒中進行以避免阻塞主執行緒,且絕不會向外部傳送任何內容。

Link Guard(阻止網路釣魚連線)— 1.7.2 及更高版本,在 1.8.0 中得到強化

選單列→「惡意軟體防護」→「連結防護」(專業版)。阻止裝置上所有瀏覽器和應用程式與網路釣魚/詐騙網站的連線。 有兩個執行點,依優先權順序一起使用。

  • ① 內容過濾系統擴充(RoamSwitchLinkFilter,1.8.0+,一旦獲得批准即首選)。 NEFilterDataProvider 系統擴充 — 未經 Apple 審查(內容過濾器提供者為自助服務,無核准佇列)。它檢查 名稱解析之後的實際出站 TCP 流 。目標名稱來自作業系統解析的主機名,或未能從流的第一個位元組解析 TLS SNI — 因此瀏覽器執行自己的 DoH/DoT 並連接到裸 IP 仍會被阻止。它從不重寫 /etc/hosts 並將每個流歸因於一個進程。 QUIC (UDP/443) 沒有可讀的 SNI,因此在 block 模式下它會被丟棄,迫使瀏覽器回退到 TCP 上的 HTTP/2。需要在系統設定中一次性批准。
  • /etc/hosts 水槽(回退,當擴充未核准/拒絕時)。 特權幫助程式將目標網域寫入 /etc/hosts 的分隔託管部分作為 0.0.0.0 並刷新快取。上限為 60,000。一旦擴充功能處於活動狀態,此部分就會被刪除。
  • 三種模式。 「關閉」停用它。 「僅警告」 暫停 匹配的連線並詢問使用者(請參閱下方的 Real warn)。 「自動阻止明顯的詐騙網站(建議)」立即下降。 從 1.7.2. 開始預設為區塊
  • Real warn(1.8.0+,僅限擴充)。 warn 上點選系統擴充功能 holds 流(作業系統命名的路徑和 SNI 路徑)與 .pause() 且應用程式會引發「允許/阻止」通知。當使用者的點擊達到 allowlist.txt / extra.txt 時(擴充功能每 1.5 秒輪詢一次小狀態檔案),保留的流將恢復(允許)或刪除(封鎖)。 25 秒內沒有答案將失敗 open(警告是建議性的)。此決策會按主機快取(允許 5 分鐘/阻止 1 小時),因此頁面的其他流程和後續存取都是即時且強制執行的。 block 通知未更改:「已封鎖…」和「允許一次(5 分鐘)」按鈕。
  • 只有明確的案例才會被阻止。 威脅來源上的清單或品牌名稱 Unicode 同形異義詞 - 其他所有內容(高風險 TLD、子網域模仿等)都是警告。判決引擎與 Linux 版本共享,並且不會向任何地方發送 URL。
  • 從錯誤的區塊中恢復。 從通知或選單允許網域(5分鐘或永久)。重新產生該部分時,將減去允許清單。
  • Pro-gate. 強制執行 (applyMode()) 僅發生在有效的 Pro 許可證上。如果沒有 Pro,模式會被存儲,但 /etc/hosts 永遠不會被觸及。許可證的啟動或失效將在會話期間生效。
  • 源和捆綁種子。 阻止清單來自簽署的威脅來源(第 7 節,使用來源專用金鑰進行驗證)。即使在第一次提取之前,它也適用於應用程式的捆綁種子(約 60,000 個條目),並且關閉「自動更新」意味著沒有出站流量。

預防性閘道 ARP/NDP 鎖定 — 1.7.5 及更高版本

選單列→「連接埠和裝置監視器」→「將網關的 ARP/NDP 固定在不受信任的網路上(預防性)」(專業版,預設為關閉)。

  • 工作原理 加入不受信任的網路時,會從 route / scutil --dns / arp -n / ndp -an 收集 IPv4 閘道、IPv6 預設路由器和連結 DNS 解析器的目前 MAC,並且說明程式將每個項目固定為具有 __X65 / arp -n是可信的)。然後,這些 IP 的欺騙性 ARP/NDP 回復將被忽略,因此無法設定中間人攻擊。
  • Scope. 僅固定這三種條目。可信(開放)網路永遠不會被固定。每次網路更改時,它都會解鎖一次並重新固定。離線公共解析器(8.8.8.8,...)沒有線上 ARP 條目,並且自動排除。
  • arp -s 是預防性的;氣隙是事後發生的。 此引腳存在,因此欺騙無法成功; ARP 欺騙偵測 (ARPSpoofContainmentManager) 和緊急氣隙一旦被發現,就會「比人類更快地切斷」。他們獨立運作。
  • Persistence. 引腳集儲存到 /Library/Application Support/RoamSwitch/gateway_arp_lock.json,因此可以透過 XPC 重新連接或幫助程式重新啟動來解鎖。

VPN 隧道 (WireGuard / Tailscale) 及其終止開關 — 1.7.6 及更高版本,1.8.0 中可選擇後端

功能表列→「連接埠和裝置監視器」→「VPN 隧道」(Pro,預設為關閉)。這是 primary 反 MITM 防禦 - 它不依賴 L2 (ARP/NDP) 的完整性。 後端可以在「WireGuard(設定檔)」和「Tailscale(退出節點)」之間選擇(子選單→「後端」)。 RoamSwitch 本身不實現加密;只有選定的後端是武裝的。

  • (A) WireGuard 後端。 驅動 Homebrew 的 wireguard-tools(無 Apple 網路擴充權利)。導入你自己的 .conf;助手將其保存在 0600 處。 Kill-switch:pf block drop all 加上 pass quick 僅用於 lo、隧道介面、與固定端點 IP、DHCP 和 ICMP 的 UDP 握手。非全隧道 AllowedIPs 會引發分割隧道警告。
  • (B) Tailscale 後端 (1.8.0+). 對於已經執行 Tailscale 的使用者。 RoamSwitch not 執行 tailscale up/登入/安裝它 — 它讀取 tailscale status --json 並執行 tailscale set --exit-node=<node>建議使用獨立的 CLI (brew install tailscale) — App Store (GUI) 建置無法從應用程式外部(沙箱)驅動;使用它,您可以在 Tailscale 應用程式中選擇退出節點,而 RoamSwitch 僅顯示狀態。 需要出口節點(透過隧道路由所有流量);如果它離線或無法訪問,它會自動撤防。
  • 斷開連線時的網路重新配置。 當退出節點被清除時,獨立的 macOS tailscaled 不會完全恢復路由 + DNS,因此 RoamSwitch 會反彈活動的網路服務(與手動切換 Wi-Fi 相同;僅具有 IPv4 位址的服務,對於多 NIC 和靜態 IP 是安全的)。流量下降約 5-10 秒,並且在此視窗期間保護等級不會降級。
  • Tailscale 終止開關預設為關閉(選擇加入)。 出口節點已隧道化所有內容; pf block drop all(僅允許 CGNAT/MagicDNS/STUN/DERP/DHCP/ICMP/DNS)適用於高級用戶,並且「防洩漏,而不是防洩漏」(允許使用 DNS,因此 tailscaled 仍然存在)。
  • 自動/許可證遺失。 在不受信任的網路上出現,在受信任的網路上出現;當專業許可證失效時解除。

§6

當您連接到未註冊的網路時,「保護等級」開關不使用 pf。它只是以稍後可以逆轉的方式更改標準作業系統設定。

每個保護等級的操作
操作實施特權是如何恢復的
防火牆+隱身模式開啟 socketfilterfw --setblockall on / --setstealthmode on 根(幫助者) off 當您返回安全網路時
停止 SSH/SMB/螢幕分享 launchctl卸載-w 根(幫助者) 僅記錄 停止時正在運行的 和返回時的 load -w (SSH 也耦合到 /usr/sbin/systemsetup -setremotelogin on 以確保現代 macOS 恢復)
停用空投 預設寫入 com.apple.sharingd DiscoverableMode 用戶(應用程式本身) 保存先前的值並在返回時將其寫回

這些都不是 RoamSwitch 新增的新阻止機制 - 它只是切換作業系統設定。如果 你刪除應用程序,唯一停止的是依賴網路的切換;最後一個作業系統 已套用的設定保持原樣。沒有任何東西被鎖定,但如果你想犯錯 安全起見,在卸載之前,在受信任的網路上將其設定回「開啟」。

§7

Mac 上保留的內容

資料儲存在磁碟上
數據地點內容
許可證令牌鑰匙圈
com.tetsuharu.RoamSwitch.license
Ed25519 簽署的令牌。 kSecAttrAccessibleAfterFirstUnlock
應用程式設定/防護開關用戶預設套件
com.tetsuharu.RoamSwitch
可信任網路註冊、保護策略、排除清單等
PF狀態/庫/應用程式支援/RoamSwitch/氣隙時間戳記、受保護連接埠的 JSON、VPN 終止開關狀態、所套用的規則集的暫存文件
Link Guard 威脅來源〜/庫/應用程式支援/RoamSwitch/threatfeed/feed.txt下載的網路釣魚/詐騙網域清單(如果尚未獲取,則為應用程式的捆綁種子)。 Feed 版本位於 UserDefaults 中
Link Guard 託管部分/etc/主機# BEGIN RoamSwitch link guard ... # END 界定的分隔部分,將阻止的域清空為 0.0.0.0。當模式為「關閉」時刪除 (§5)
ARP/NDP 鎖銷/庫/應用程式支援/RoamSwitch/gateway_arp_lock.jsonIP → MAC 裝置由預防鎖 (§5) 製成 permanent。解鎖後刪除
WireGuard 配置/Library/Application Support/RoamSwitch/(幫助區域,0600使用者導入的 .conf。端點主機名稱也儲存在 UserDefaults 中(應用程式解析它)
設備後備 UUID使用者預設值僅當 IOKit 不傳回 UUID 時產生的隨機值 (§9)
紀錄os.Logger/NSLog統一日誌記錄。沒有任何內容被傳送到外部

離開機器的流量(完整清單)

任何地方都沒有代碼可以收集和發送診斷結果、連接埠資訊、URL 或 日誌。不包含分析 SDK 和崩潰報告器 SDK。唯一的外部庫是 Sparkle (更新)。進入網路的就是這八個,僅此而已(僅當使用者配置 VPN 時才是第六個,僅當啟用 RoamSwitch Sensor 配對時才是第七個)。

RoamSwitch 建立的出站連接
連接方式目的地當它發生時發送了什麼
許可證啟用/停用 lafine.net
/api/v1/license/*
僅當使用者輸入許可證密鑰或停用 Pro 時 許可證密鑰、裝置雜湊、主機名稱、應用程式版本。個人資訊由 Stripe 在購買時處理;該應用程式不處理它
更新檢查 lafine.net
/updates/appcast.xml
Sparkle,每 24 小時一次以及發佈時 HTTP 請求(標準 UA 和版本)。下載的項目由 EdDSA 簽章驗證 (§10)
Link Guard 威脅來源 lafine.net
/updates/v1/{manifest, feed/<版本>.txt}
當 Link Guard (§5) 開啟並啟用「自動更新」時,每 24 小時(以及啟動時)。關閉「自動更新」會刪除此路徑 GET。沒有查詢字串,沒有cookies,沒有任何可以辨識機器的東西。 receive-only 簽署的靜態文件;清單和 Feed 正文均經過 Ed25519 驗證。簽章金鑰是 專用於 feed — 來自應用程式更新 SUPublicEDKey 獨立金鑰 (因此洩漏僅限於「壞區塊清單」)
套件 CVE 比對 / 實證型弱點診斷的更新資料 lafine.net
/updates/v1/manifest
自應用程式啟動起每 24 小時一次(始終進行。套件 CVE 比對的更新資料沒有關閉開關,因為它純粹是用於本機清單比對的僅接收資料;實證型弱點診斷的 CVE 對應表僅在啟用「實證型弱點診斷(主動可達性驗證)」時才會取得) 僅 GET。沒有查詢字串、沒有 cookie,也沒有任何可識別裝置的資訊。這是僅接收的已簽章靜態檔案;清單與來源資料本體均透過 Ed25519 驗證。與 Link Guard 威脅來源重複使用相同的金鑰與相同的清單,但實際取得的來源檔案(Homebrew/npm/PyPI/crates.io/RubyGems/Packagist/Go/Maven 的已知 CVE 對應表、實證型弱點診斷的 CVE 對應表,以及用於打字仿冒偵測的熱門 npm 套件名稱清單)是各自狝立的檔案
ClamAV 病毒定義更新 ClamAV 官方鏡像 僅當使用者安裝了 ClamAV 並使用掃描功能時。它啟動 freshclam 標準 ClamAV 定義獲取。它不包含 RoamSwitch 衍生的訊息
VPN 隧道(§5,WireGuard / Tailscale) 使用者配置的 WireGuard 端點,或 Tailscale 控制平面(Tailscale, Inc.) 僅當使用者已設定 VPN 隧道(Pro,預設為關閉)並加入不受信任的網路時才會發生。選擇 WireGuard 時,在隧道建立之前端點主機名稱會透過 DNS 解析一次。選擇 Tailscale 時,這是由使用者自行安裝並登入的 tailscaled 處理的控制平面通訊,RoamSwitch 本身僅讀取 tailscale status --json 並執行 tailscale set --exit-node= 選擇 WireGuard 時:WireGuard 握手(UDP)和隧道內的流量。目的地是使用者自己的 VPN 伺服器,內容是使用者自己的流量。選擇 Tailscale 時:使用者自己的 Tailscale 帳戶與 Tailscale, Inc. 控制平面之間的通訊(裝置註冊、DERP 中繼等,遵循 Tailscale 自身的隱私權政策)。無論哪種情況,RoamSwitch 都不會加入任何識別碼或診斷資料
RoamSwitch Sensor 配對與稽核連動 同一區域網路上的 RoamSwitch Sensor(使用者自行部署、以固定 IP 執行的裝置) 僅在啟用「RoamSwitch Sensor 配對」(Pro,預設關閉) 並輸入 Sensor 的 IP 位址與配對碼完成配對時(一次性通訊)。配對後,僅在您點擊「向 Sensor 請求稽核」,以及自動取得結果期間(請求發出 5 分鐘後開始,每隔 5 分鐘一次,最多 5 次),才會再次連線到同一個 Sensor 固定 IP 配對時會傳送本端點的主機名稱和 Ed25519 公開金鑰,並接收 Sensor 發放的公開金鑰。稽核請求與結果取得均透過 Ed25519 簽章驗證,傳送的僅是帶簽章的請求。診斷結果或連接埠資訊只會作為 Sensor 的回應被接收,絕不會傳送檔案內容。通訊目的地僅為使用者自行部署的 Sensor 的固定 IP 位址,絕不會到達包括 lafine.net 在內的任何外部伺服器。相互信任僅透過配對碼這項明確操作建立
結帳頁面 條紋結帳 僅當用戶按下購買按鈕時(它在瀏覽器中打開) —(瀏覽器導航)
「Zero Telemetry」的範圍

這裡的「零遙測」意味著沒有遙測收集和發送使用資料或 診斷結果。這並不意味著根本沒有網路流量。中的八條路 上表確實存在。但它們中的每一個要么是用戶發起的,要么是 簽章驗證、僅接收 取得以及診斷結果、連接埠、URL、 Mac 上的文件內容永遠不會離開它。

Link Guard 威脅來源(第 3 行)在“它不發送任何內容”之上添加了“它確實提取更新” 防禦。兩者保持分開; Linux 白皮書 §1.1 同樣拆分了“零” 「遙測」來自「僅接收更新」。關閉“自動更新”,Link Guard 在捆綁包上運行 資料(約 60,000 個網路釣魚/詐騙網域)加上離線同形異義字偵測,且不會出現此路徑。

VPN 隧道(第 6 行)僅在使用者設定自己的 WireGuard 伺服器,或選擇 Tailscale 後端並已使用自己的 Tailscale 帳戶登入 tailscaled 時才會出現;目的地和內容都在使用者的控制之下。 RoamSwitch 僅負責建立隧道、維持斷網開關,或讀取 Tailscale 狀態以切換出口節點—— 不加入任何識別碼或使用資料。在未設定 VPN 的情況下,該路徑不存在。

RoamSwitch Sensor 配對(第 7 行)是僅限 Pro 且預設關閉的功能,除非您啟用它,否則該通訊路徑不存在。配對本身僅透過明確操作建立——輸入 Sensor 操作者發放的配對碼,以及 Sensor 的固定 IP 位址。配對完成後的所有通訊(稽核請求、結果取得)也都送往使用者自己擁有的這同一個固定 IP,並透過 Ed25519 簽章驗證。絕不會到達包括 lafine.net 在內的任何外部伺服器。診斷結果或檔案內容除了作為 Sensor 自身的回應外,絕不會被傳送。

應用程式內「連結安全性檢查」表格向目標 URL 發送 HEAD 請求以查看 縮短的 URL 到達的地方(以下重定向到私人或本地地址被 v1.4.5 SSRF 緩解)。相較之下,MCP audit_url_safety 是離線分析 當場完成並將 URL 發送到任何地方(§8)。

實測(2026-08-29)— 判定 PASS

這不僅僅是斷言。 2026 年 8 月 29 日,正在運作的 1.4.7 安裝經過了審核 tcpdump + 每個進程歸因(nettop / lsof / 已過濾 pktap capture) + LuLu,大約 2 小時的窗口,安全等級固定為 最大鎖定和強制 appcast 檢查。 結果:沒有出站流量歸因於 RoamSwitchRoamSwitchHelper、 或 RoamSwitchMCPServer,除了對 lafine.net 進行 appcast 檢查; MCP 伺服器唯一的套接字是本地主機;權利轉儲是空的.

完整的文章和任何人都可以運行來重現它的腳本: 審計/RESULTS-2026-08-29.md

此測量來自 1.4.7,在 Link Guard 威脅來源(1.7.2,第 3 行)和 VPN 之前 隧道(1.7.6,第 5 行)。在 1.7.2 或更高版本上審核,您將看到 two 僅接收 GETs 到 lafine.net(appcast 和威脅來源);如果有VPN 配置完成後,您還將看到到使用者自己的 WireGuard 伺服器的 UDP(目的地和內容 在使用者的控制下)。所有這些都會隨著“自動更新:關閉”而消失,並且沒有配置 VPN。

§8

RoamSwitchMCPServer 是一個獨立的命令列工具,捆綁在 RoamSwitch.app/Contents/MacOS/。 MCP 用戶端,例如 Claude Desktop 或 Claude Code 將其作為子進程啟動並透過 stdio (換行符分隔的 JSON-RPC 2.0)。官方 SDK 不會針對本機的 macOS SDK 進行構建,因此 在基金會的 JSONSerialization 之上手動實現。

設計約束什麼

  • 它是唯讀的。 根本沒有用於更改安全性等級、隔離連接埠或彈出設備的 API。這並不是 v1 中被遺忘的東西——它是被故意遺漏的。讓外部程式碼(此處為 LLM)重寫安全工具的保護狀態會破壞每個使用者的信任。
  • 它不打開套接字。 它既不註冊服務也不監聽。它從標準輸入讀取一行,在標準輸出上傳回一行,然後客戶端結束進程。
  • 它沒有發送任何訊息。 所有診斷均在 Mac 內部完成。
  • 它從不同的域讀取設定。 UserDefaults(suiteName: "com.tetsuharu.RoamSwitch") 明確讀取應用程式的域(其自己的捆綁包 ID 域為空)。它只會讀取;它不寫。

它公開的工具

工具tools/list返回,以及它們返回的數據
工具它返回什麼交通
取得安全報告18 項檢查(FileVault / SIP / Gatekeeper / 自動更新 / XProtect / 防火牆 / Wi-Fi 加密 / ARP / 閘道 ARP 鎖定 / SSH 設定稽核 / Sudo NOPASSWD 稽核 / 暴露連接埠 / 防護設定 等)以及分數和每項修復建議限本地
取得暴露端口偵聽 TCP 連接埠的清單。對於任何在本地主機之外暴露的內容,它會交叉引用已知危險的服務資料庫,並使用 HTTP 探針檢查 CORS / 標頭到 127.0.0.1:port (本地,已關閉)只探測到127.0.0.1
獲取守衛狀態Pro自動回應防護的開/關狀態(連接埠異常/ARP/USB/藍牙/Web+郵件下載/DNS威脅防護)、目前防護等級和可信任網路狀態限本地
審核網址安全判斷 URL 是否為網路釣魚/同形異義字(Unicode 欺騙)/品牌子網域欺騙/高風險 TLD/明文 HTTP。它是同步且完全離線analyzeURL;它不遵循重定向)
取得應用程式協助捆綁知識庫的全文搜尋(功能規格/設定/故障排除/通知訊息解釋)
audit_secrets從文字、檔案或目錄中偵測外洩的 API Key 與私密金鑰(偵測值於輸出時已遮蔽)。
audit_security_logs彙總近期安全記錄(Mac:Sudo 驗證失敗、SSH 暴力破解嘗試、Gatekeeper 攔截、XProtect 偵測等 / Linux:Sudo 驗證失敗、SSH 暴力破解、防火牆攔截、AppArmor 拒絕、ClamAV 偵測等),自動遮蔽 API 金鑰、權杖等機密資訊,並回傳以記錄範本化的新模式偵測與頻率異常(統計突增)偵測結果。限本地
run_active_vuln_scan僅限 127.0.0.1 的非破壞性實證漏洞驗證。唯一會使用網路的工具,預設關閉,需於設定中主動啟用。僅 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 對照表比對。完全不產生任何網路通訊。
get_quarantine_status回傳惡意軟體隔離 Vault 的內容(原始路徑、偵測到的威脅名稱、隔離時間與大小)。限本地
get_canary_status回傳勒索軟體誘餌檔案(Canary)的佈署狀態,以及最近 50 筆偵測事件。限本地
get_notification_history回傳 RoamSwitch 傳送的通知(安全記錄稽核異常、ClickFix 偵測等)歷史記錄,最近1週、最新在前。限本地
get_port_anomaly_incidents回傳連接埠異常防護的基準狀態、目前自動阻斷的連接埠,以及最近 50 筆事件。回應中明確註明:目前自動阻斷的連接埠是不含時間戳記的當前狀態快照,與含時間戳記的事件歷史是兩回事。限本地
get_runtime_threat_status回傳是否因 XProtect 偵測到惡意軟體而進入 Air-Gap 隔離,以及觸發該隔離的事件。排查 Air-Gap 原因時應最先檢視此工具。限本地

initialize 回應中的 instructions 欄位也明確指出, 「無法變更安全等級、隔離連接埠或彈出裝置」傳達該功能 客戶端LLM的邊界。 MCP 資源 (roamswitch://docs/*) 是唯讀的 Markdown 文件也是如此。

在本機 LLM 實戰演練中發現並修正的錯誤歸因問題

2026 年 9 月,我們使用本 MCP 伺服器讓本機 LLM(Qwen 3.8 27B)進行一次事件調查實戰演練(fire drill)時,發現模型誤讀了 get_port_anomaly_incidents 回傳的資料,把一個與本次事件無關的舊連接埠阻斷紀錄,錯誤地編入了目前正在發生的事件敘述中。原因在於:不含時間戳記的「目前阻斷連接埠」快照,與含時間戳記的真實事件歷史,在同一個回應中未加區分地一併回傳。修正方案是新增說明欄位,明確標註哪一部分是時間軸資料、哪一部分只是快照。修正後,用同一情境對同一模型重新執行,模型引用了說明欄位中的文字,明確拒絕將舊連接埠歸因於目前事件。我們透過自動迴歸測試與真實本機 LLM 重現兩種方式,驗證了修正的有效性。

要點:原始碼公開

此伺服器及其使用的偵測邏輯(ARP 監控、連接埠掃描、 連接埠稽核、18 項健康檢查、URL 安全分析)的原始碼,已於 github.com/lafine1211/roamswitch-mcp 公開(MIT 授權, 為出貨程式碼的鏡像,按每次發布加上標籤)。您可以直接在程式碼中確認它是否為唯讀、它傳遞給 LLM 的內容,以及 它沒有發送任何內容出去。其中不包含特權助手、pf 控制、實際執行攔截的各項防護, 以及授權機制——這些仍保留在應用程式儲存庫中。

測試也隨之而來——鏡像單元測試、對抗性輸入測試和突變模糊測試, 由 swift test 運行並在 CI 中驗證。模糊測試發現了一次無人防範的崩潰 (深度嵌套的 JSON 物件上的 JSONSerialization 堆疊溢位);它是固定的 在解析器之前進行嵌套深度檢查,並記錄在 SECURITY_TESTING.md 中。

§9

代幣

  • 它使用Ed25519(Curve25519簽名)。 公鑰嵌入在應用程式中(LicenseVerifier.embeddedPublicKeyBase64)。對應的私鑰僅存在於許可證後端(Firebase Functions 環境變數)中,而不存在於儲存庫中。
  • 簽章資料是規範的 JSON。 簽章是透過使用 JSONEncoder.sortedKeys.withoutEscapingSlashes 編碼 LicensePayload(許可證金鑰、層、裝置雜湊、頒發時間、到期日、席位數)產生的確切位元組來建立和驗證的。
  • 它被設計為關閉失敗。 如果嵌入的金鑰遺失或格式錯誤,或無法產生簽章或規範 JSON,則結果不會「驗證」 - 它傳回 invalidSignature

裝置綁定

device_hash = SHA-256( "RoamSwitch-LifetimeSalt-v1" : lowercase(IOPlatformUUID) )

原始硬體 UUID 不會傳送到伺服器。在極少數情況下 IOKit 不返回 UUID,它會回退到保存在 UserDefaults 中的隨機 UUID。在驗證時,如果 令牌的 device_hash 與目前設備雜湊不匹配,結果為 deviceMismatch

它可以離線工作

要點:無需伺服器即可運作

validateSavedLicense() 在啟動時所做的就是從鑰匙圈中讀取令牌並 使用嵌入的公鑰在本地驗證它。它不會連接到網路。如果 許可證伺服器關閉後,Pro 功能仍可在已啟動的 Mac 上繼續運作。 僅在新啟用和明確停用時才聯繫伺服器。的 盡最大努力向伺服器發出停用通知 - 即使失敗,本地停用也會 總是完成。

預設為一次性(終身)購買;僅當以下情況時才檢查 expires_at is_lifetime 是假的。座位數依等級表示 — 2 表示個人 Pro,5 表示個人 Pro 團隊。

§10

簽名及公證(scripts/release.sh

  1. 運行 xcodebuild archive (發布、手動簽名、開發者 ID 應用程式)。
  2. -exportArchive 匯出為 method: developer-id
  3. notarytool submit --wait 之後,在 .app 上執行 stapler staple
  4. 使用 spctl -a -t exec -vv 進行驗證。
  5. 裝訂後重新壓縮 以產生 Sparkle 更新工件(因此包含公證票並且它可以離線運行而不會出現 Gatekeeper 警告)。
  6. 建造 DMG,公證並裝訂 DMG,並使用 stapler validate 進行驗證。

更新(Sparkle 2.9.6)

更新 Info.plist 中的設置
鑰匙價值
SUFeedURLhttps://lafine.net/updates/appcast.xml
SU公有金鑰CNxzwijMzMCJzliId76Yl88S/9np6t/xg/zQ9YbYzHs=
SU啟用自動檢查真實
SUScheduledCheckInterval86400

在套用更新之前,會先驗證 appcast 中所列的 EdDSA 簽章 針對應用程式中嵌入的 SUPublicEDKey 。私有簽署金鑰僅存在於 建構環境。 appcast 透過 HTTPS 提供服務。 Delta 更新經過簽名驗證 同樣的方式。

要點:appcast 以公開為前提

appcast.xml URL 是公開的這一事實本身並不是一個弱點。其內容 僅包含版本號、發行說明、下載 URL、檔案大小和 EdDSA 簽名 每個構建——沒有什麼秘密。信任的錨不是“appcast 在傳輸過程中是否真實”,而是 使用應用程式中內建的公鑰驗證工件的簽名。攻擊者可以 完全替換appcast(MITM、DNS劫持、入侵web主機)仍無法推送 沒有簽署金鑰的惡意更新。 Gatekeeper(開發者 ID 和公證)是 第二個門。

仍然存在兩個風險。一種是更新未到達,因為主機已關閉或 appcast 已損壞 (不會發生錯誤的安裝-你只是沒有得到更新)。另一種是凍結攻擊 故意隱瞞安全更新。 Sparkle 2.x 透過檢查拒絕降級和重播 版本排序,但完整的防禦凍結需要一個專用的更新伺服器 到期。這是我們要解決的問題。

§11

RoamSwitch 旨在處理什麼

  • 來自同一 LAN 上的攻擊者或來自受感染的 IoT 設備的探測和攻擊。它透過隱密、暴露連接埠審核和與外部隔離來回應。
  • 暴露在您不信任的網路上。它會自動停止共享服務和 AirDrop。
  • 中間人攻擊(ARP/NDP 欺騙)。 從 1.7.6 開始,這是分層的:(1) VPN 隧道 + 終止開關(§5,主要防禦 - 不依賴 L2 完整性),(2) 不受信任網路上的預防性閘道 ARP/NDP pin(§5),以及 (3) 具有緊急氣隙的欺騙偵測(事後)。所有 Pro,預設關閉(檢測預設開啟)。
  • 尋找在未經身份驗證的情況下暴露在 0.0.0.0 上的開發伺服器和資料庫(Redis、MongoDB、Elasticsearch 等),並從外部阻止它們。
  • 及早捕獲類似勒索軟體的未經授權的加密活動並停止所有流量(它不依賴簽名)。
  • BadUSB 和實體鍵盤核准防護(USBKeyboardGuard、CGEventTap + IOKit)可攔截並丟棄來自未經批准的 USB 鍵盤/電纜(Rubber Ducky、O.MG 電纜等)的擊鍵,以防止自動命令注入攻擊。
  • 對未知 USB 儲存的核准提示(無法辨識的磁碟機保持唯讀狀態,而不是立即彈出),以及對連接的儲存進行自動 ClamAV 掃描(選購)。
  • 完全在本機偵測剪貼簿中的敏感 API 金鑰(OpenAI、Anthropic、GitHub、AWS 等),防止誤貼上至網頁表單或 AI 聊天中。
  • 偵測並警告從 Hugging Face 或網路下載的危險 Pickle 格式 AI 模型檔案(存在任意程式碼執行風險)。
  • 下載檔案中業界標準的 EICAR 測試簽章,以及公開文件記載的經典反向 shell 單行指令(StaticSignatureScanner,1.8.7+,即使未安裝 ClamAV 也能運作)。
  • 直接呼叫裸腳本直譯器的新增 LaunchAgent/LaunchDaemon 自動啟動註冊(PersistenceMonitorGuard,1.8.7+)。
  • 「ClickFix」型攻擊——由偽造的警告畫面誘導使用者在終端機中親自執行指令——透過監控 shell 歷史記錄中的已知可疑指令樣式來偵測,並觸發緊急氣隙(ClickFixGuard,1.8.7+,Pro,預設關閉)。
  • 在偵測到 --privileged 啟動或 /var/run/docker.sock 綁定掛載等具有容器逃逸風險的 Docker 設定的瞬間發出通知(DockerEventGuard,1.8.9 起,Pro・預設關閉・僅通知)。
  • 不僅支援貼上文字,還支援按資料夾進行遞迴掃描的機密資訊・API 金鑰外洩稽核(SecretLeakAuditor,1.8.9 起支援資料夾掃描,完全本機處理)。

我們決定不做什麼

  • 它不是防毒軟體的替代品。 ClamAV 和 XProtect 用作​​輔助工具; RoamSwitch 本身並不是通用惡意軟體偵測器。
  • 我們決定不申請 EndpointSecurity entitlement。個人開發者帳號(Apple Developer Program Individual)的核准率偏低,因此我們在此預先說明:只要這項方針不變,任何真正需要 pre-exec 攔截(例如 ES_EVENT_TYPE_AUTH_EXEC)的功能,都會被視為已關閉的非目標(non-goal)。作為無法阻止執行本身的替代方案,我們在不申請 entitlement 的前提下,實作了在實務上已足夠充分的事後(post-hoc)偵測——靜態簽章偵測、自動啟動監控,以及 ClickFix 防護。
  • 這不是一個保證。 它是深度防禦堆疊中的一層,而不是「完全防止勒索軟體」。行銷文案也是在這個前提下進行審查的。
  • 它無法保護已經受損的 root 或核心。 如果攻擊者已經擁有 root,他們也可以刪除 helper 的 pf 規則。
  • 它本身不會恢復 L2 完整性。 預防性 ARP/NDP pin 是首次使用時信任的 — 如果攻擊者在您連線之前就已經就位,它可以固定一個欺騙性的 MAC。當您不想做出這樣的假設時,VPN 隧道 (§5) 就是答案:即使 L2 中毒,內容也會被加密,終止開關會阻止明文洩漏。它不能取代企業 DHCP 監聽或動態 ARP 檢查。
  • 它不提供 VPN 伺服器。 隧道功能使用使用者提供的 WireGuard 設定; RoamSwitch 不會成為 VPN 提供者。

安裝 RoamSwitch 增加的攻擊面

增加了攻擊面,以及如何控制它
攻擊面它是如何包含的
以 root 身分執行的 LaunchDaemon 及其 mach 服務 (com.tetsuharu.RoamSwitch.Helper) 操作面固定為HelperProtocol(§3表)。沒有任意指令通道。連接由代碼簽署要求使用 audit_token 進行授權。
如果 RoamSwitch.app 本身受到損害,則所有幫助程式的操作都會傳遞給攻擊者 已啟用強化運行時,且應用程式不會被授予不必要的權限。出站流量僅限於上述八個路徑(第六個僅在使用者配置 VPN 時,第七個僅在啟用 RoamSwitch Sensor 配對時)。我們計劃讓第三方對此進行審查。
助手產生的系統二進位路徑 /sbin/pfctl 這樣的絕對路徑是直接指定的,不依賴 PATH。參數也是硬編碼的(除了連接埠號碼、DNS 字串、ARP IP/MAC 對和 VPN 端點 IP — 所有這些都經過格式驗證)。
連結防護重寫 /etc/hosts (setLinkGuardSinkhole) 寫入僅限於分隔的託管部分;它外面的行被逐字保留。域被規範化和驗證,IP 和垃圾被刪除,列表上限為 60,000,並且檔案透過臨時檔案 + 原子替換寫入。它只處理明確的案例(威脅來源清單或品牌同形異義詞),由僅限本地的判決引擎決定。阻止清單本身來自經過簽署驗證的威脅來源(來源專用金鑰)。
Link Guard 的威脅來源取得(每日僅接收 GET 沒有查詢字串和標識符的靜態文件取得。清單和提要正文均經過 Ed25519 驗證,並且驗證失敗的提取將被丟棄(不會回退到未簽署的資料)。 「自動更新:關閉」會完全刪除路徑。
預防性 ARP/NDP 接腳 (lockGatewayARP) 僅固定「網關、IPv6 路由器、連結 DNS」的鄰居快取項目。傳入的 IP/MAC 對經過格式驗證並根據請求集進行協調(它從不自行添加條目)。它僅在不受信任的網路上運行;專業版,預設為關閉。首次使用信任限制在第 11 節「我們決定不做的事情」中說明。
VPN 隧道(wireGuardImport/Up/…、自製程式 wireguard-tools .conf 由助手存放在 0600 處。端點 IP 由應用程式解析並傳遞給幫助程式(幫助程式永遠不會解析任意主機名稱)。隧道的目的地和內容由使用者控制。如果未安裝 wireguard-tools,則該功能將被停用。專業版,預設為關閉。終止開關經過 PFRulesetCoordinator (§4)。
MCP 伺服器將系統狀態傳遞給 LLM(困惑的副手) 它是唯讀的,沒有實作寫入 API。 URL 檢查處於離線狀態。設定域以唯讀方式引用。
劫持更新路徑 EdDSA 簽章驗證 (SUPublicEDKey),以及捆綁的公證票。 appcast 透過 HTTPS 進行。

附錄 A

本文檔中所述的所有內容都可以使用以下命令針對分散式工件進行驗證。

簽署及公證

# Developer ID 簽署與團隊 ID
codesign -dvvv /Applications/RoamSwitch.app 2>&1 | grep -E 'Authority|TeamIdentifier|flags'

# 公證票證是否已 staple
stapler validate /Applications/RoamSwitch.app
spctl -a -t exec -vvv /Applications/RoamSwitch.app

# 內建 Helper / MCP 伺服器的簽署
codesign -dvvv /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchHelper
codesign -dvvv /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchMCPServer

權利(無網路發送權限)

codesign -d --entitlements :- /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchHelper
codesign -d --entitlements :- /Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchMCPServer
# → 空的 entitlements 字典。沒有 app-sandbox / network client 鍵。

測量流量

# 同時執行 tcpdump,確認正常使用中不會產生通訊
sudo tcpdump -i any -n 'host not 127.0.0.1' and 'not port 53'
# 除授權啟用、更新檢查、ClamAV 更新之外沒有其他流量

若要對每個進程的歸因進行更嚴格的自動檢查,請參閱 roamswitch-support/audit/ 中的 rs-zerotel-audit.sh

防禦架構和滲透測試(5 個防禦邊界)

# 一次性自動驗證 XPC 授權邊界、pf Air-Gap 優先權、連接埠暴露偵測、MCP 唯讀、ARP 監控
git clone https://github.com/lafine1211/roamswitch-support
cd roamswitch-support/audit
./rs-defense-audit.sh all

來自 macOS VM 的即時滲透測試和多層防禦審核:RESULTS-DEFENSE-2026-08-30

DNS 威脅防護(Quad9 惡意軟體和 C2 封鎖)驗證

# 查詢 Quad9 官方用於驗證威脅攔截的測試網域
nslookup test.dns9.quad9.net
# → 應回傳 ** server can't find test.dns9.quad9.net: NXDOMAIN,即名稱解析被攔截

Web & Mail Download Guard(ClamAV 即時檢測)驗證

# 在 Downloads 中建立無害的業界標準防毒測試字串(EICAR)
echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > ~/Downloads/eicar_test.com
# → FSEvents 偵測觸發 ClamAV 立即掃描,不顯示通知,「偵測到 EICAR 測試簽章(無害)」會
#   記錄至通知歷史。由於 EICAR 是業界標準測試檔案,並非真實威脅,
#   不會被隔離或攔截,檔案將原樣保留(僅在放置真實惡意軟體樣本時才會移入隔離管理員)。

靜態簽章偵測(無需 EndpointSecurity)驗證

## 一條來自公開攻擊手法說明的經典反向 shell 單行指令(無害,不會執行)
echo 'bash -i >& /dev/tcp/127.0.0.1/4444 0>&1' > ~/Downloads/rs_test.sh
## → 即使未安裝 ClamAV,也會出現「已隔離危險的下載檔案」通知,且該檔案會被
##   立即移動到 Quarantine 資料夾(不依賴 ClamAV 的簽章資料庫)

新增自動啟動監控(PersistenceMonitorGuard)驗證

# 建立一個直接呼叫裸腳本直譯器的無害 LaunchAgent(不會真正執行)
cat > ~/Library/LaunchAgents/com.example.selfcheck.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>com.example.selfcheck</string>
  <key>ProgramArguments</key><array><string>/bin/bash</string><string>-c</string><string>echo hi</string></array>
</dict></plist>
EOF
# → 數秒內應出現「偵測到新的自動啟動註冊」通知。確認後請將其移除:
rm ~/Library/LaunchAgents/com.example.selfcheck.plist

Link Guard(封鎖網路釣魚連線)

# 是否寫入了受管理區塊(Pro + 預設模式下為 BEGIN/END 兩行)
sudo grep -c 'RoamSwitch link guard' /etc/hosts

# 區塊內容與 sinkhole 數量
sudo sed -n '/BEGIN RoamSwitch link guard/,/END RoamSwitch link guard/p' /etc/hosts | head -4
sudo sed -n '/BEGIN RoamSwitch link guard/,/END RoamSwitch link guard/p' /etc/hosts | grep -c '^0\.0\.0\.0'

# 是否真的被攔截(用區塊中的一筆記錄確認,無害)
D=$(sudo sed -n '/BEGIN RoamSwitch/,/END RoamSwitch/p' /etc/hosts | awk '/^0\.0\.0\.0/{print $2; exit}')
dscacheutil -q host -a name "$D"   # → ip_address: 0.0.0.0(名稱解析已被攔截)

# 也可透過選單 →「連結保護」→「關閉」確認上述區塊消失

只接收威脅來源(經過簽名驗證)

# 公開的資訊來源與清單(任何人都可取得並驗證)
curl -s https://lafine.net/updates/v1/manifest        # version/generated/threatfeed{...}
curl -sI https://lafine.net/updates/v1/manifest.sig   # → text/plain
# 傳送的僅是不帶查詢字串、Cookie、識別碼的 GET 請求。可搭配執行 tcpdump 加以確認

預防性 ARP/NDP 鎖定(§5、1.7.5+)

# 在預防性鎖定已啟用的狀態下連線不受信任網路 → 檢查 permanent 項目
arp -an | grep -i permanent          # 閘道器等 IP 會以 (permanent) 列出
ndp -an | grep -i 'P '               # IPv6 端(P = permanent)
sudo cat "/Library/Application Support/RoamSwitch/gateway_arp_lock.json"  # 目前固定的 IP→MAC 集合
# 在選單中解除固定 → 上述內容應消失

VPN 隧道 + 終止開關(§5、1.7.6+)

# 前提是已安裝 wireguard-tools(透過 Homebrew)
brew list wireguard-tools >/dev/null && echo "wireguard-tools: OK"

# 在不受信任網路上啟用 VPN → 通道建立前 pf 處於網路阻斷開關狀態
sudo pfctl -sr | grep -E 'block drop all|pass .*(utun|udp)'   # block drop all + 有限的 pass quick 規則

# 通道建立後(已執行 wg-quick up)
wg show                              # handshake / transfer 正常運作中
route -n get default | grep interface  # → utunN(預設路由為通道)
# 停用 VPN → 網路阻斷開關解除,pf 恢復原狀

特權幫手本身

# 已註冊的 LaunchDaemon
sudo launchctl print system/com.tetsuharu.RoamSwitch.Helper

# 目前載入的 pf 規則(Air-Gap / 連接埠防護的實際狀態)
sudo pfctl -sr

# Helper 的狀態目錄
ls -la "/Library/Application Support/RoamSwitch/"

MCP伺服器的回應(離線檢查)

BIN=/Applications/RoamSwitch.app/Contents/MacOS/RoamSwitchMCPServer
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
              '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | "$BIN"
# 會回傳 serverInfo 與 5 個工具的定義。不會產生任何網路連線

MCP伺服器的來源碼和測試

git clone https://github.com/lafine1211/roamswitch-mcp
cd roamswitch-mcp
swift build -c release        # 與發行二進位檔相同的原始碼
swift test                    # 單元測試、對抗性輸入、stdio、突變模糊測試
# 測試內容與已發現的問題記錄在 SECURITY_TESTING.md 中

裝置識別符

# 綁定到權杖的原始值(傳送的僅為加鹽 SHA-256)
ioreg -d2 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}'
RoamSwitch 架構/安全性白皮書 v1.5 · 適用對象 1.9.28 (build 85) · Lafine Systems Design · 2026-09-15
如發現錯誤或遺漏,請透過 lafine.net/contact.html 回報。