§1概要
RoamSwitch for Linux は、macOS 版 RoamSwitch のゼロトラスト・ネットワーク防衛思想を Linux(systemd + nftables 環境)向けに完全実装した、常駐型のネットワークセキュリティ&システム診断ソフトウェアです。
本エディションはクライアント端末(ワークステーション・ラップトップ・シングルボード)向けであり、ネットワーク移動に追従してプロファイルを切り替える自律動作を前提とします。サーバー/ヘッドレス基盤向けの強化版(既定拒否を基本とし、公開するサービスポートだけを明示許可してインバウンドを最小化、外向き通信も制限。ネットワーク移動への自律追従はせず、静的な署名付きポリシーとリモート管理・監査で運用)は別途、専用の RoamSwitch Server Edition として提供されます。
接続中の Wi-Fi / 有線ネットワークをゲートウェイ MAC アドレス単位で識別し、nftables のファイアウォールプロファイルを自律的に切り替えます。加えて、未信頼ネットワークでの中間者攻撃(MITM)対策を 3 層で提供します。内訳はVPN トンネル + キルスイッチ(WireGuard または Tailscale、本命)、予防的 ARP/NDP ロック、ARP スプーフィング検知。さらにランサムウェアの挙動検知(fanotify + シャノンエントロピー + カナリア)、フィッシング接続を警告・遮断する受動リンクガード(NFQUEUE + SNI)、ポート異常ガード、BadUSB 対策、カーネル特権昇格(LPE)対策(Frag Gap CVE-2026-53362 の緩和として、未信頼ネットワークでの非特権ユーザー名前空間の拒否を任意で有効化できます。既定はオフ)、eBPF ランタイム脅威センサー(Falco / Tetragon)連携による攻撃プロセスのミリ秒凍結(SIGSTOP)と自動 Air-Gap 隔離、27 項目のセキュリティ健全性診断、Model Context Protocol(MCP)サーバーを標準搭載します。
本書は、アーキテクチャ・脅威モデル・各防衛機構の設計、そして 「完全ローカル処理。既定でマシン外へ出るのは署名付き検出データの要求だけで、そこに利用者の情報は一切含まれない」 というプライバシー原則がどのようにコードレベルで担保されているかを説明します。運用者が明示的に有効にするオプトイン機能(イベント転送など)はこの既定の例外であり、下表と §9.4 にすべて列挙します。宣伝的な表現は避け、記載内容は配布パッケージ(バイナリ)の実際の動作と、本書付録のセルフチェックで確認できるようにしています。RoamSwitch for Linux はプロプライエタリ・フリーウェアで、ソースコードは公開していません(§11)。
| 原則 | 内容 |
|---|---|
| Zero Telemetry | 製品は既定で、利用者・端末に関する情報を一切外部へ送信しません。テレメトリ・解析・クラッシュレポート・利用統計・リモート設定・ライセンス認証サーバーのいずれも存在しません。運用者が明示的に有効にするオプトイン機能は、次の行「既定の例外」と 9.4 に挙げたものだけです。 |
| 既定の例外(すべてオプトイン) | 運用者が明示的に有効にした場合に限り、次の機能が外部へ通信します。宛先は運用者が設定したものだけで、組み込みの宛先はありません。(1) イベント転送(forward_enabled、既定 OFF)— 検知イベントを、運用者が指定した syslog/JSON Lines ファイル/署名付き Webhook へ転送します。(2) Fleet(Business 層、端末側コードのみ)— 登録していない端末は何も送らず、Fleet のホスト名の名前解決もしません。管理サービスは未デプロイ・未出荷です。そのほか、利用者の操作で動く更新確認なども含め、9.4 にすべて列挙します。 |
| 受信専用の更新 | 製品が既定で行う外向き通信は、1 日 1 回、固定 URL(lafine.net/updates/v1)の署名付きマニフェストを GET することだけです。ClamAV が導入されている場合は、実行のたびに ClamAV 自身の更新ツール freshclam も起動しますが、その通信は ClamAV 側のものとして §9.4 に別に挙げています。マニフェストから取得するのはリンクガードの詐欺サイト一覧・本体の最新バージョン・カーネルと OS パッケージ(Debian/Ubuntu/Arch/RHEL 系/SUSE)と言語パッケージ(npm/PyPI/crates.io/RubyGems/Packagist/Go/Maven)の CVE マップ・実証型脆弱性診断の CVE マップ・タイポスクワッティング検知用の人気 npm パッケージ名リストです。毎日取得するのはマニフェストとその署名(GET 2 回)だけです。リンクガードの一覧・実証型診断の 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 スプーフィング(不正 RA を含む)による中間者攻撃、不正 DHCP、MAC フラッディング後の受動盗聴、誤って
0.0.0.0にバインドされた開発サーバー・ローカル LLM API への不正アクセス。 - 悪意ある USB デバイス(BadUSB) — キーボードを偽装し画面ロック前にキーストロークを流し込む物理攻撃。未承認 USB マスストレージによるデータ持ち出し・マルウェア持ち込み。
- ランサムウェア/ファイルレスマルウェア — ダウンロードファイル経由で侵入し、ドキュメントを一括暗号化。
- DNS 経由の C2 通信・フィッシング — マルウェアが名前解決で C2 サーバーやフィッシングサイトへ到達。
対象外
- ルート権限を既に奪取した攻撃者。カーネル・ファームウェアレベルのルートキット。
- 物理的にディスクを取り出す攻撃(ディスク暗号化の領域。RoamSwitch は暗号化の有無を診断しますが暗号化自体は行いません)。
- AirDrop 相当の近距離無線共有制御(Linux に OS 標準機構が存在しないため非対応)。
§3システムアーキテクチャ
RoamSwitch for Linux は 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 ドメインソケット)を経由します。TCP/UDP のリスニングソケットはデーモン・アプリ・MCP のいずれにも存在しません。アプリが bind するのは二重起動防止用の Unix ソケット 1 つのみです。
# ユーザー空間(非特権)
roamswitch-app GTK3 + AppIndicator トレイ
roamswitch (CLI) シェル用の薄いフロントエンド
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 タイマー ヘルパー。HTTP/TLS スタックを
持つ唯一のクレート(日次の署名付きマニフェスト GET)
roamswitch-honeytokens デーモンが起動する補助プロセス(おとり資格情報の配置と監視)
roamswitch-incident-capture デーモンが起動する補助プロセス(封じ込め時の証拠バンドル採取)
roamswitch-core 純粋ロジックライブラリ(全クレート共通)
roamswitchkit MCP クライアント SDK
| コンポーネント | 権限 | 役割 |
|---|---|---|
| roamswitch-daemon | root(systemd、Type=notify + WatchdogSec=30) | すべての特権操作。単一の書き込み者として nftables・DNS・systemd ユニットを制御。 |
| roamswitch-app | ログインユーザー | GTK3 GUI + AppIndicator トレイ。設定編集と可視化のみ。特権操作はすべて IPC 経由。 |
| roamswitch (CLI) | ログインユーザー | シェルからの薄いフロントエンド。同じ IPC ソケットを使用。 |
| roamswitch-mcp | AI クライアントが起動するプロセス | stdio JSON-RPC。読み取り専用の診断情報を提供。 |
デーモンとの通信はすべて /run/roamswitch/roamswitch.sock(ディレクトリは 0755、ソケットは 0666)を経由します。アクセス制御はファイルの権限ではなく接続元の確認で行います。SO_PEERCRED で UID を取得し、システムアカウント(UID 1〜999 と nobody)の接続は拒否し、状態を変える呼び出しは root かローカルのログインセッションを持つ UID だけに限ります(§1)。ユーザー設定は ~/.config/roamswitch/config.json に平文 JSON で保存されます。デーモンは root 権限で /home/*/.config/roamswitch/config.json を走査して読み込みます(デーモンは HOME=/root で動作するため)。読み込むのは、そのアカウント自身が所有し、シンボリックリンクではなく、他のユーザーが書き込めないファイルだけです。設定はマシン外に出ることはなく、アカウント登録もクラウド同期も存在しません。
§4自律ネットワーク防衛
sentinel ループ(3 秒周期)が、デフォルトゲートウェイの MAC アドレスを /proc/net/arp と ip neigh から取得し、config.json の登録済み MAC と照合します。SSID ではなく MAC を用いるのは、SSID が容易に偽装可能であるためです。プロファイル切替時のデスクトップ通知には切り替えの理由(「未登録ネットワークのため」/「登録済みネットワーク『〇〇』の設定」/「ネットワーク未接続のため」)を含めます。
| プロファイル | 状況 | nftables の挙動 |
|---|---|---|
| open(信頼) | 登録済みの自宅・社内ネットワーク | policy accept。ローカル通信を許可。 |
| balanced(標準保護) | 未登録だが比較的安全と設定した環境 | 受信をデフォルト拒否。確立済み接続・lo・ICMP/ICMPv6 のみ許可。公開ポート露出を監視。 |
| lockdown(外出先保護) | 公衆 Wi-Fi・未登録ネットワーク | 全受信パケットをステルス遮断。共有サービスは自動で停止し、信頼ネットワークに戻ると復元します(既定でオン。いつでもオフにできます)。 |
すべてのルールは inet roamswitch という専用 nftables テーブルに隔離され、他のファイアウォール設定(ufw / firewalld 等)と衝突しません。手動オーバーライドの期限強制(1時間 / 2時間 / 4時間 / 今日いっぱい / 解除まで / 次回切断まで)はデーモンが単一の書き込み者として行います。GUI は期限切れの書き込みを一切行わず、デーモンが書き換えた config.json を次のティックで再読込して UI に反映するだけです(競合回避)。
Air-Gap(緊急ネットワーク遮断)
inet roamswitch テーブルの input・output 両フックに policy drop(優先度 -100)を設定し、lo のみを通す完全遮断モードです。
- フェイルクローズド — デーモン再起動時にマーカーファイル(
/run/roamswitch/airgap.active)を検出すると遮断を再適用。 - 自己修復 — 外部プロセスが
nft delete tableでルールを消去しても、sentinel ループが数秒以内に再適用。 - 低確度のトリガーは短いタイマーで解除。警告の文脈なしで押した手動の Air-Gap(GUI のボタンや
roamswitch airgap)のような低確度のトリガーは、短いタイマー(airgap_low_confidence_secs、既定 600 秒)で全面復旧します。誤操作や誤検知で端末を長時間ネットワークから切り離さないためです。ARP 異常(lockdown 外)・ポート異常・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 は既定で Wi-Fi の無線も rfkill で止め(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(ICMP・ICMPv6 の許可は balanced のみ)
}
chain output {
type filter hook output priority -100; policy <accept|drop>;
# Air-Gap(隔離)中は同じテーブルを作り直す。input / output とも policy drop、許可は lo のみ(確立済み接続も通さない)
}
}
受動リンクガード(フィッシング接続の検知)
デーモンが外向き接続の接続先を監視し、危険なホストへの接続を警告します(狭義の「明らかなフィッシング」に該当する場合は遮断します)。従来の「URL を貼り付けて診断する」機能——ユーザーの操作を要した——を置き換えます。
- 傍受 — 専用の nftables テーブル
inet roamswitch_linkguardがoutputフックにかかり、DNS 質問と新規/初期の Web 接続を NFQUEUE 経由でユーザー空間ワーカーへ送ります。NFQUEUE はAF_NETLINK上のカーネル ↔ ユーザー空間 verdict チャネルであり、デーモンはソケットを bind せずパケットも発生させません。キュー規則にはbypassが付いており、ワーカーが動作していない場合は通信がそのまま通ります(Air-Gap が別の hard stop)。 - ホスト名の取得元 — DNS 質問名、TLS ClientHello の SNI(DoH を使うブラウザでもホスト名は把握できる)、平文 HTTP の
Hostヘッダ。ECH(Encrypted ClientHello)が普及した場合はそのホストが盲点になります(既知の限界)。 - 判定(
roamswitch-coreの純ロジック)— ホストをローカルのフィッシング/脅威フィードと、オフラインのヒューリスティック(保護ブランドの IDN/混同ホモグラフ、非公式ドメインに含まれるブランド名、IP 直打ち、高リスク TLD)に照らします。
モード(linkGuard.mode) | フィード命中 or ブランドのホモグラフ | その他の疑わしいシグナル |
|---|---|---|
off | 検査しない | 検査しない |
warn | 接続を最大 8 秒保留 + 許可/ブロックの承認通知 | 通知も遮断もしない |
block(既定) | 接続を遮断 + 許可ダイアログ(ホスト単位に 1 回) | 通知も遮断もしない |
hard-block されるのはフィード命中またはブランドのホモグラフのホストだけで、ヒューリスティック単体が拾ったものはすべて警告扱いの判定に留まり、遮断も通知もしません。warn モードは危険なホストへの接続を 最大 8 秒保留 してユーザーに諮り、無応答のまま保留が切れた場合は fail-closed(パケットを drop、判定はキャッシュしない)。「確認に無応答」を「許可」とみなしません。次回アクセスで再度確認します。block モードでもホスト単位に 1 回だけ通知+許可ダイアログを表示します(同じホストへのブラウザの多数の接続で通知が氾濫しない)。危険な対象の承認ダイアログは既定ボタンが「遮断を維持」で、「許可」は目立たない配色です。遮断されたホストは、承認ダイアログで「許可」を選ぶと linkGuard.allowlist に恒久的に追加され、設定から取り除くまで通ります。
DNS は検査するが偽造しない: ブロックリストに載った DNS 質問は通知のみを生成し、強制は後続の TCP 接続で行います。RoamSwitch が偽の DNS 応答を注入することはありません。フィードはフィッシング/詐欺ドメインをコンパイルした一覧で、パッケージに同梱され、日次アップデータ(§11)で更新されます。フィードが無くてもオフラインのヒューリスティックは動作します。フィードの Ed25519 署名と SHA-256 は、日次アップデータがインストールする前に検証し、合わないデータは採用しません。リンクガードはインストール済みのフィード(無ければパッケージ同梱のもの)を読み込み、どちらも無いときは「ヒューリスティックのみ」で動作します(「すべて許可」にはなりません)。
VPN トンネル + キルスイッチ(MITM 対策の本命)
L2 の完全性に依存しない中間者攻撃対策。未信頼ネットワークでは、信頼できるエンドポイントへの常時暗号化トンネルを張ることで、ローカルの ARP/NDP スプーフィング・不正 DHCP・受動盗聴をすべて無意味化します——攻撃者に見えるのは暗号文だけです。下記の ARP/NDP 固定と検知は、この機能の補助という位置づけです。
バックエンドは 2 つから選べます——「WireGuard(設定ファイル)」または「Tailscale(Exit Node)」。RoamSwitch 自身は暗号処理を実装せず、VPN 事業者にもなりません。いずれも任意依存で、未導入なら GUI が導入方法を案内します。
(A) WireGuard バックエンド
- VPN サーバーは提供しない — ユーザーが用意した WireGuard 設定を使います。Mullvad・IVPN・Proton VPN など WireGuard 対応プロバイダーの
.conf、または自前の WireGuard サーバー(VPS・自宅サーバー)を想定。同梱のデフォルト設定はありません。 - 設定 — 標準の
.confを読み込み、デーモンが検証して/etc/wireguard/roamswitch.conf(0600、IF 名roamswitch)に保存。/etc/wireguard/以外に置けないのは、Ubuntu 24.04+ / Debian 13 の AppArmorwg-quickプロファイルがそれ以外の.conf読み取りを(root でも)拒否するためです。AllowedIPsが0.0.0.0/0(::/0)でないスプリットトンネルは「全通信は保護されない」と警告します。 - キルスイッチ — 専用 nftables テーブル
inet roamswitch_vpn(policy drop、両フック)が ループバック・トンネル IF(roamswitch、oifname名照合)・固定したエンドポイント IP への UDP・DHCP・ICMP 以外をすべて遮断します。トンネルを張る前に(エンドポイントをgetent ahostsで固定 IP に解決してから)設置されるため漏洩ウィンドウがありません。wg-quick upが失敗してもキルスイッチは維持されます。
(B) Tailscale バックエンド
- 既に Tailscale を使っているユーザー向け。RoamSwitch は
tailscale up/ ログイン /tailscaledの導入は行いません——tailscale status --jsonを読み、tailscale set --exit-node=<ノード>/(解除)を実行するだけです。 - Exit Node 必須 — 中間者攻撃対策は「全通信を経由させる Exit Node」の指定で初めて成立します。指定が無い場合は保護を有効化せず「保護されていません」と警告します。選択したノードがオフラインになった場合は保護を自動解除して通知します(死んだノードへ全通信を流すブラックホールを避けるため)。
- キルスイッチ(
inet roamswitch_ts、policy drop) — Tailscale のトランスポートは単一エンドポイント IP に固定できない(DERP リレー・NAT 越えでピアのアドレスが動く)ため WireGuard よりゆるめです。許可するのは: ループバック、トンネル IF(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)は全遮断します。 - 既知の限界 — (1) WireGuard バックエンドより許可範囲が広い(
udp/3478・tcp/443・CGNAT /10 全体)。(2) 経路上の攻撃者はメタデータ(Tailscale 利用の事実、DERP リージョン、タイミング)を観測でき、遮断・遅延も可能——ただしトンネル内容は読めません。(3) 直結 UDP 遮断時の機密性は Tailscale 自身の DERP TLS(証明書ピン)に依存します。(4) MagicDNS オフかつローカルリゾルバ利用時は、キルスイッチ有効中に DNS が引けなくなります(GUI で警告)。(5) キャプティブポータルは有効化前にログインが必要です。
共通。vpn_on_untrusted_enabled かつ lockdown(外出先保護)のときのみ作動します。open と balanced ではトンネルを張らず、Tailscale では Exit Node をクリアしてキルスイッチを撤去します(tailscale down はしません)。既定 OFF・オプトイン。外向き通信の宛先は WireGuard/Tailscale ともにユーザー自身の VPN エンドポイント/tailnet であり、lafine のインフラとは無関係です(§9)。
予防的 ARP/NDP ロック
未信頼ネットワークに接続した時点で、中間者攻撃の攻撃者が「なりすます」必要のある同一リンク上のインフラ機器の MAC アドレスをカーネルの neigh テーブルに PERMANENT(静的)エントリとして固定します。固定後はカーネルが偽の ARP 応答/近隣広告を無視するため、攻撃者は poisoning を成立させられません。
- 固定対象 — (1) IPv4 デフォルトゲートウェイ、(2) IPv6 デフォルトルーター(多くは
fe80::リンクローカル)、(3) 同一リンク上の DNS リゾルバ(公開リゾルバはゲートウェイ経由のため対象外)。IPv6 はip -6 neigh replace ... nud permanentで NDP エントリを固定します。 - 信頼済みネットワークでは固定しません——自宅ルーターの再起動で通信不能になることを防ぐため。ネットワーク切替のたびに固定し直します(TOFU)。正規のフェイルオーバーは次回の再接続で追従します。固定エントリは
/var/lib/roamswitch/arp_lock.jsonに記録され、確実に解除されます。 - 既定 ON(Community Edition の方針。macOS Pro 版と同じ内訳)。設定
gateway_arp_lock_enabled。
ARP スプーフィング検知(notify-first)
/proc/net/arp を監視し、1 つの MAC が複数の IP に対応する状態(重複 MAC)、およびゲートウェイ MAC の不審な急変を検知します。lockdown 中は、ゲートウェイが関わる検知で通信を全遮断(Air-Gap)+通知。balanced/信頼済みネットワークではいきなり遮断せず通知のみを出し、ユーザーが RoamSwitch から緊急遮断を実行できます。これは、(a) 攻撃者が偽の ARP パケット 1 つでユーザーを自らオフラインにさせる DoS を防ぎ、(b) ルーター再起動・アクセスポイントのローミングでの誤発動を防ぐためです。設定 arp_spoof_guard_enabled。
Air-Gap の解除確認(ARP 再確認)
ARP スプーフィングが原因の Air-Gap(lockdown 中の検知、または ARP 警告への対応として押した Air-Gap)は、次を満たしたときだけ「原因の消滅」とみなして解除します。カーネルの近隣テーブル(ip -j neigh show、代替で /proc/net/arp)とデフォルトルートを読み、デフォルトゲートウェイの MAC が、発動時に記録した信頼 MAC と一致し(状態が REACHABLE/STALE/DELAY/PROBE/PERMANENT のいずれかで、同じ MAC を名乗る別の IPv4 ホストが同一デバイス上に無い)、その状態が連続 arp_recheck_consecutive 回(既定 3)かつ arp_recheck_min_secs 秒以上(既定 60)続くことです。近隣テーブルの項目が無い・失敗状態で、リンクが上がっている(carrier あり・operstate up・Wi-Fi は rfkill 未ブロック)場合に限り、ゲートウェイ宛てに ARP who-has を 1 回だけ(20 秒以上の間隔、AF_PACKET)送って確認します(arp_recheck_active_probe、既定 true)。返ってきた応答に信頼 MAC 以外の MAC が 1 つでもあれば不一致です。不一致・項目欠落・重複 MAC・読み取り失敗・信頼 MAC の未記録はすべて「未解消」(fail-closed)で、連続カウントをリセットします。既知の限界: 本物のゲートウェイの MAC を複製する攻撃者は、キャッシュ判定でも能動プローブでも、MAC の比較ではどうやっても検出できません。
ARP スプーフィングの検知は、ゲートウェイが関わるときだけ、自動で Air-Gap を発動します。ゲートウェイの MAC が変わった場合と、ゲートウェイの IP が、他の IP と 1 つの MAC を共有している場合が、これに当たります。別のホストの MAC が変わった場合や、1 台の機器が複数の IP に同じ MAC で答える場合(仮想マシンを動かす PC、別名アドレスを持つ NAS など)は、通知だけで、遮断しません。同じ規則で、解除の判定でも、ゲートウェイが関わる重複だけを「原因が続いている」と数えます。1.10.7 の修正時(2026-09-26)に、実機の LAN で、別の機器から偽のゲートウェイ ARP を送ったところ、約 1 秒で Air-Gap が発動し、攻撃をやめて約 11 分後(既定の 10 分と、確認の 60 秒)に自動で解除しました。無関係な 2 つの IP が同じ MAC を名乗る状態が続いていても、解除できました(この規則にする前は、全面遮断が発動し、解除もされませんでした)。
ポート異常ガード
新たに外部 LAN に公開されたリスニングポート(バックドア・C2・誤って 0.0.0.0 にバインドされた開発サーバー/ローカル LLM API)を自動的に隔離します。「危険なポート番号」ではなく ベースライン相対 で判定します。有効化した時点で外部公開中の実行体を「既知」として学習し、以後新規に現れた公開実行体のみをフラグします(アイデンティティは実行体パス、snap は snap:<name> に正規化)。システムデーモンは対象外。隔離は inet roamswitch_dev_guard テーブルで「lo 以外からの当該 TCP ポート宛を drop」——マシン自身からの利用は継続可能。隔離時は即座にデスクトップ警告通知と確認ダイアログ(「このポートを許可する」「遮断を維持する」)を表示し、ワンクリックで恒久許可リストに登録できます。ポート診断画面からも個別許可・ベースライン再取得ができます。既定 ON。設定 port_anomaly_guard_enabled。macOS 版から移植。
DNSトンネリング/データ持ち出し検知
受動リンクガードの既存 DNS 経路の一部として常時動作します(個別の設定トグルはありません)。iodine・dnscat2 などの DNS トンネリングツールに特徴的な質問形状(ラベル長 40 バイト超 かつ シャノンエントロピー 3.8 超、または TXT/NULL レコードの qtype)のいずれかに該当する質問を「疑わしい形状」として数え、直近 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 末尾 2 ラベルによる簡易近似で、Public Suffix List の厳密な実装ではありません)単位でのバーストカウンタのキー分離によって解消しています。許可リストは既定で主要 CDN/クラウドと PaaS のサフィックス 16 件(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 の qtype 判定には許可リストの有無によらず適用されるため、許可リスト配下にトンネルを偽装する回避は防いでいます(TXT だけは、freshclam の鮮度確認に使われる clamav.net 配下を、別の小さな除外リストで数えません)。バーストカウンタは apex domain 単位にキー分離済みで、宛先を区別しない単一のグローバルカウンタによる誤検知は解消しています(60 秒のスライディングウィンドウという判定ロジック自体は変更していません。再通知のクールダウンは 300 秒から始まり、通知が続くと 1800 秒まで延びます)。iodine・dnscat2 などの実際の悪性トラフィックは単一の攻撃者管理ドメイン配下でラベル・qtype をローテーションするだけなので、apex domain 単位に絞っても検知力は低下しません。
Docker リスク検知 & ファイアウォール迂回対策
Docker の公開ポート(-p)はホストの標準ファイアウォールをバイパスする既知の問題があるため、DOCKER-USER チェーンに検査ルールを常時挿入し、許可されていない外部トラフィックを遮断します(設定不要、既定で常時有効)。加えて、--privileged コンテナの起動や /var/run/docker.sock のマウント(コンテナ脱出リスク)を docker events でリアルタイム検知し、検知した瞬間に通知します。リスクの高い設定であって確認された侵害ではないため、自動遮断は行わない通知専用です。
対応範囲 — この 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 超・バイナリファイルはスキップ)。MCP ツール(audit_secrets。text か path を渡します)と、公開 SDK の Rust クライアント roamswitch-linux-kit(テキストは audit_secrets()、ファイルとディレクトリは audit_secrets_path())からも同じ検出を呼び出せます。内容が外部へ送信されることはありません。
SSH 総当たりの検知と遮断(sshd が動いているときだけ)
サーバー版と同じエンジンを、クライアント版のデーモンも動かします(ssh_guard_enabled、既定 true)。違いは三つです。第一に、sshd が動いているときだけ働きます。SSH のポートに待ち受けがあるかを 1 分ごとに調べ、3 回続けて無ければ止めます。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 キーボード・Bluetooth)で確認ダイアログを表示します。イベント発生時の判断(不明な USB キーボード、未登録 USB メモリ、未知ポートの自動遮断、緊急遮断)はモーダルダイアログとして提示されます。
§5多層マルウェア・ランサムウェア防御
シャノンエントロピー・バースト検知と YARA スキャン
書き込みイベントを PID 単位 で追跡し、単一プロセスが 5 秒以内にエントロピー 7.92 以上の相異なるファイルを 20 個以上生成した場合にランサムウェアの一括暗号化とみなし、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の 3 パス等)は 1 件として数える。 - 重要プロセス保護 —
systemd/dockerd/NetworkManager/sshd/gnome-shell等をハードコードした保護対象プロセスの一覧(PROTECTED_PROCESS_NAMES)に照合し、名前が一致し、かつ実行ファイルがシステム領域(/usr、/optなど)にあるものは決して凍結しない(マシン全体のハングを防ぐ)。名前だけを似せた/tmpや/homeのプログラムは保護されません。
Web アプリ層の検知ルール(Web シェル・注入コード)
PHP・JavaScript・JSP・ASP の Web シェル、注入された難読化コード、画像に偽装した PHP、.htaccess による実行の乗っ取りを、専用のルールで検知します。ELF や shell を対象とする組み込みの検知とは別で、リテラルと、上限つきの間隔を挟んだ語句の列、2 つの統計値(極端に長い 1 行、巨大な Base64)だけで書く単純な言語です。正規表現を使わないため、処理が膨らんだり止まったりせず、悪意のあるルールファイルでも起こり得るのは誤検知までです。実際の検体から抜き出した署名や、特定のマルウェア名を付けた署名は含みません。公開されている手口(デコード関数の出力の実行、リクエスト値の直接実行、アップロード用ディレクトリの実行系 PHP、画像内の PHP、.htaccess のハンドラ設定、unescape で組み立てた注入スクリプト、ブラウザ内のマイニング用ライブラリ)だけを記述した 18 本です。
ルールは 3 層です。製品に同梱のもの、日次アップデータ(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 所有・書き込み不可・親ディレクトリも安全なものだけを実行し、シェルを通さず、環境変数は最小限で、30 秒で打ち切る)。オプションのアカウント対処(既定オフ)は、隔離したファイルの持ち主のログインシェルを無効にします。root、UID が account_min_uid(既定 1000)未満のアカウント、ログイン中のユーザー、デーモン自身のユーザーには何もせず、元のシェルを記録して roamswitch web-rules unlock-account で戻せます。誤検知の実測は、WordPress 本体 2,409 ファイル、phpMyAdmin 3,356 ファイル、npm の依存パッケージ 14,743 ファイルで 0 件でした。限界として、読むのは先頭 256 KB(書き込み・実行時)と 4 MB(スキャン)だけで、変数名の組み立て('ev'.'al')のような分割は追えず、既知マルウェアの個別署名は、実際の検体がないため同梱していません。
カナリア(おとりファイル)
ユーザーの Documents / Desktop / Downloads / Pictures および専用ディレクトリにおとりファイルを配置し、SHA-256 ベースラインと 3 秒周期で照合します。改名・削除・切り詰め・内容改変のいずれかを検知すると即座に 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 に無いファイルの混入)のいずれかを個別に報告します。
既知の限界 — この検証は、manifest.json の SHA-256 を、バンドルの外にある記録と照合します。記録は 2 か所です。バンドルの外の記録の鎖(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 自身の実行ファイルのアクセスと、SSH のデコイに対する OpenSSH クライアント系(ssh、scp、sftp、ssh-add、ssh-agent)のアクセスは、誤検知を避けるため記録しません。実行ファイルを特定できないアクセスは記録します。記録専用で、アクセス元プロセスの凍結や遮断などの追加対処は行いません。
既知の限界 — ~/.ssh/id_rsa デコイの本体は可読テキストを含み、正規の OpenSSH 秘密鍵の base64 本体としては無効な形式のままです。ただし多くの秘密情報スキャナは -----BEGIN ... PRIVATE KEY----- ヘッダの正規表現一致のみでフラグを立てるため、実運用上の検知率への影響は限定的と判断しています。また、アクセス元プロセスの特定は、通常は fanotify のイベントが持つ PID から行います。fanotify が使えず inotify に切り替えた場合に限り fuser を呼ぶため、読み取り後ただちに終了する非常に短命なプロセスの PID・プロセス名を取得できないことがあります。その場合は不明として扱うのみで、アクセス自体の検知(イベント発火・記録)には影響しません。
fanotify 監視・隔離 Vault・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 秒キャッシュし、デーモン自身の子プロセスは監視から除外します。 - notify-first・確認前隔離なし(fanotify ガード)——fanotify ガードは、YARA/ClamAV の単純なヒットで無確認の隔離や
FAN_DENYは行いません。実シグネチャ検出は/run/roamswitch/approvals.jsonにモーダル要求(隔離/許可/後で)を積み、ユーザーが確認するまで Vault には入りません。実際のexecveのみpre_exec_blocking_enabledが有効なときFAN_DENYします。承認タイムアウトはフェイルオープン。ダウンロード監視(既定は~/Downloadsと~/Desktop。watched_foldersで追加)は別の経路で、YARA または ClamAV が(EICAR を除いて)検出したファイルを、確認なしでその場で隔離 Vault に移し、通知します。 - EICAR 等のテスト署名は通知履歴への記録のみ——業界標準のテスト文字列は「無害」として扱い、通知は出さず通知履歴に記録するだけで、隔離も遮断もしません(fanotify ガード・ダウンロードガード双方で)。
- ユーザー設定の
scan_exclusions(配下も含む絶対パス。旧clamav_exclusionsを統合)を YARA・ClamAV 双方が尊重。承認ダイアログの「許可」と隔離タブの「復元」はどちらもこの一覧に追記され、同じファイルが再び拾われません。 - 検出された実マルウェア(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 に続けてローカル DB でスキャン。.exeなどの Windows 向け実行形式は通知し、.sh・.deb・.elfなど Linux の配布形式は履歴に記録するだけで、危険な AI モデル(.pkl・.pt)は出所つきで通知します。ClamAV は任意の依存で、未インストールでも他の防御は動作。インストールされている場合、日次アップデータ(§11)がfreshclamを実行しシグネチャ DB を最新に保つ。
プロセス実行レコーダー(ローカル専用)
netlink のプロセスコネクタ(proc connector)で exec と uid の変化を記録する、ローカル専用の記録機能です。記録するのは、実行ファイルのパス・コマンドライン引数(最大 64 個・1 個 512 文字・合計 4096 文字に切り詰め、制御文字はエスケープ)・親プロセスの系譜・uid・カレントディレクトリ・コンテナ ID・実行ファイルの SHA-256 などです。既定で有効ですが、搭載メモリが 1 GiB 未満のマシンでは既定で無効になります(exec_recorder_enabled の明示設定が優先)。自身の CPU 使用は 1 コアの 3% 以内(exec_recorder_cpu_budget_pct)に抑え、nice 10 で動作します。プロセスコネクタを開けない場合は、低頻度の /proc スキャンに縮退します(短命なプロセスは取りこぼします)。
- 保存 —
/var/lib/roamswitch/exec_logに、8 MiB 単位のセグメントに分けた JSON Lines(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) — Web サービスからのシェル起動、
/tmp・メモリ上ファイル(memfd)・削除済みファイルからの実行、ダウンロード直後の実行、base64/eval のワンライナー、setuid の経路が無い uid 0 の取得、cron/systemd 設定の直後に置かれたバイナリの実行、/etc/ld.so.preload変更後の実行、ローカルのスナップショット(btrfs、LVM)がsnapperやtimeshiftなど既知の管理ツール以外に削除されたこと(EXR-008、1.10.11)、同じ実行元が 30 秒以内に 8 種類以上の攻撃ツールをwhichなどで探したこと(EXR-009、1.11.0)、python や node などのスクリプト処理系が 2 分以内に 3 種類以上の攻撃ツールを起動したこと(EXR-010、1.11.0。sshd・login・ターミナルの配下で人が打った場合は対象外)の 10 種で、各ルールに MITRE ATT&CK の技術 ID を付けます。ルールは通知するだけで、単独では隔離しません。運用者がexec_rules_isolation_optin(既定 false)を明示的に有効にした場合に限り、高重大度のルールが Air-Gap の高確度トリガーとして扱われます(§4)。 - 端末外へは送りません — 記録の生ログは転送されません。イベント転送(§9.4)を有効にした場合に限り、ルールの通知(重大度・ルール ID・プロセス名・要約)とセグメントの封印ハッシュだけが、運用者が設定した宛先へ送られます。
認証情報ファイルへのアクセス監視(既定 ON・通知のみ)
既定で有効です(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 の認証情報については、boto3 のスクリプトや Ansible のように python/python3/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 ログインに属するときだけ、1 行を足します。仕組み、信じる値、三つの答え(見つかった/SSH のログインは見つかりませんでした/ログインの経路は調べられませんでした)の区別は、サーバー版と同じです。調べられなかったことを、ログインではないとは書きません。記録は /var/lib/roamswitch/fim_intrusions.json と、MCP の verify_fim の recentIntrusionContext に残ります。
カーネル特権昇格(LPE)防護とカーネル堅牢化
非特権ユーザー空間を取得した攻撃者やマルウェアが、Linux カーネルの脆弱性を悪用して root 権限を奪取することを多層防御で阻止します。
- 非特権 User Namespace の扱い(Frag Gap CVE-2026-53362 対策) — 現代の Linux カーネル特権昇格脆弱性の多くは、非特権ユーザーが
unshare(CLONE_NEWUSER)を呼び出して作成したユーザー名前空間内で、未初期化構造体や古いネットワークサブシステムへ不正アクセスすることに起因します。診断はuser.max_user_namespacesとkernel.unprivileged_userns_cloneの状態を確認して表示しますが、ブラウザ、Flatpak、Electron、rootless コンテナなどが動かなくなることがあるため、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経由でメモリ空間(ブラウザのセッションや SSH/GPG 秘密鍵)を読み取ることを拒絶します。 - コアダンプ抑止 —
fs.suid_dumpable = 0により、クラッシュしたプロセスのメモリイメージがディスク上に平文ダンプされることを防止します。 /tmp&/dev/shmのnoexecマウント防御 — 一時ディレクトリにペイロードを配置して実行することを防ぐため、外出先(未信頼ネットワーク)ではnoexec,nosuid,nodevでマウントする防御機構を提供します。
eBPF ランタイム脅威監視 & 自動プロセス凍結 (Desktop eBPF Guard)
RoamSwitch クライアントデーモンは、カーネル空間で動作する Modern 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 アラート
- UNIX ドメインソケット直接連携(
/var/run/roamswitch/events.sock) — Falco 等の eBPF センサーから、ディスクログを経由することなくメモリ上の UNIX ソケット経由でアラートを直接受信。ディスク I/O を発生させず、ミリ秒単位の超高速ハンドオフを実現します。 - ミリ秒単位の自律対応(プロセス凍結 & ネットワーク遮断) — 受信したイベントの優先度が
Critical(カーネル LPE、特権昇格、ランサムウェア暗号化など)の場合、デーモンは当該 PID にSIGSTOPを送出してプロセスを完全停止(状態:Ts)させ、そのプロセスの実ユーザー(コンテナの場合はその cgroup)が出す通信だけをroamswitch_isolateテーブルで遮断する「ピンポイント隔離」を行って、デスクトップ通知を発報します。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% 削減。 - カーネル事前破棄(
drop_failed_exit: true) — 失敗システムコールをカーネルドライバ段階で破棄し CPU 負荷を半減。 - 省メモリリングバッファ(
cpus_for_each_buffer: 2) — バッファサイズを 2MB に縮小し常駐メモリを 30〜50MB、CPU 負荷を 1% 未満に抑制。 - ログ上限強制ローテーション(
/etc/logrotate.d/roamswitch-falco) — 50MB 上限・7 世代圧縮保持。
- 重要度フィルタリング(
- スタンドアロン堅牢化 vs eBPF 拡張のハイブリッド耐性 — Falco 未導入時でもカーネル sysctl(Yama の ptrace 制限・コアダンプ抑止・ネットワーク保護。プロファイルの切り替えごとに適用)・nftables プロファイル・fanotify・カナリア監視で自律防衛。Falco 導入時はリアルタイム eBPF イベントによるゼロデイ迎撃・プロセス凍結が加わり、最高強度の防衛を実現します。
§6BadUSB 対策
USB イベントを監視し、キーボード(HID)クラスのデバイスと USB マスストレージを追跡します。既定 OFF(macOS 版の USBKeyboardGuard / USBStorageGuard と同じく isEnabled = false 出荷)。初回セットアップ ウィザードと USB タブからオプトインでき、有効化時には確認ダイアログを出します。
不正 USB キーボード(キーストローク注入)
- カーネル層でのポート物理切断は行いません(重要)。旧実装(≤1.0.6)は未登録キーボードに対して
/sys/bus/usb/devices/*/authorizedに0を書き、デバイスを物理的に切断していました。キーボードとマウスが一体の受信機(Raspberry Pi・ミニ PC で一般的)ではマウスも同時に切断し、内蔵入力の無いマシンを操作不能にします。現在は HID インターフェース(bInterfaceClass 03)を持つデバイスへのauthorized=0書き込みをハード拒否し、デーモンは起動時にauthorized==0の HID デバイスを再認可して過去バージョンで切断されたマシンを自己修復します。 - キーストロークの遮断はソフトウェアで行います。未登録キーボードを検知すると、そのデバイスの
/dev/input/eventN(/proc/bus/input/devicesのkbdハンドラを持つノードのみ、VID:PID 一致)に evdev のEVIOCGRABで排他グラブをかけます。マウスノードは触りません。macOS 版のCGEventTapに相当し、デバイスは給電されたままです。 - 安全策 — グラブをかけるのは「他に入力できるキーボードがある」場合(内蔵、または承認済みで接続中の USB キーボード)のみ。単一の新規キーボードしかないマシンを締め出しません。
- 承認ダイアログ — グラブと同時に
/run/roamswitch/approvals.jsonに承認要求を積み、GUI がモーダル(許可/拒否)を表示。許可でホワイトリスト登録+グラブ解除。承認されないまま 3 分が過ぎても、既定ではキーボードを解放しません(1.10.4 以降。それ以前の版は 3 分でフェイルオープンでした。usb_keyboard_timeout_releaseを有効にすると従来の挙動に戻せます)。
不正 USB マスストレージ
未登録の USB マスストレージ(HID を持たないもの)が挿入され、ガードが有効なとき、authorized=0 で保留し(HID を持たないので安全)、承認ダイアログを表示します。信頼ネットワークかどうかは問いません。マウント済みやスワップに使用中のドライブ(使用中の外付けディスク、ライブ USB など)は、取り外し同然になって壊れるおそれがあるため、保留せず、ログに残すだけです。許可で再認可+ホワイトリスト登録、拒否で保留を継続。macOS 版は 1.7.4 でこの「読み取り専用で保留+承認プロンプト」方式に統一しました。
識別と grandfathering
HID の判定は bInterfaceClass == "03" と /proc/bus/input/devices の Handlers= 行。コンボ受信機(1 つの VID:PID で kbd と mouse の両セクション)は VID:PID でマージ。既存のキーボードを許可リストに登録する grandfathering は、デーモンではなく GUI が行います。初回セットアップウィザードで有効にしたとき(および、ガードが有効で初回セットアップ済みなのに、接続中のキーボードが許可リストに 1 つもない既存環境への初回の移行)に、その時点で接続済みのキーボードを許可リストへ登録します。USB タブで後から有効にしても、登録はされません。デーモンの起動時に接続済みでも未登録のキーボードは、起動前に差し替えられた可能性があるため、未登録として扱い、他に入力できるキーボードがあればグラブ対象になります。
§7セキュリティ健全性診断(27 項目)
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 Secure Boot |
| アクセス制御 | LSM(AppArmor / SELinux)、SSH / sudo 設定監査 |
| カーネル堅牢化 | sysctl 強化、非特権 User Namespace の状態確認(Frag Gap 対策。Client 版では無効化を推奨しません)、Yama の ptrace 制限、コアダンプ制御、/tmp・/dev/shm の noexec |
| 更新 | 自動セキュリティアップデートの設定 |
| ネットワーク | ARP スプーフィング監視、ゲートウェイ ARP/NDP 固定状態、外部公開ポート監査、Wi-Fi 暗号化強度 |
| マルウェア | ClamAV(EICAR による検知の実動作テストを含む)/ fanotify(ガードの実稼働と、キュー溢れによるイベントの取りこぼしも確認)/ エントロピー監視 / noexec / 補助プログラム(ハニートークンと証拠採取)の導入確認 |
| ブラウザ | Firefox / Chrome / Chromium の Safe Browsing 設定 |
| DNS / USB | 脅威保護 DNS、USB バスのゼロトラスト状態 |
| コンテナ・脆弱性 | Docker ソケットのマウント露出、特権コンテナ(--privileged)の監査、コンテナランタイムの隔離レベル(runc / gVisor / Kata)、稼働中カーネルの既知 CVE 露出 |
診断はすべてローカルのファイル・コマンド出力(getenforce・mokutil・resolvectl・ss 等)の読み取りに基づきます。外部への問い合わせは一切ありません。
信頼ネットワークを踏まえた判定。 RoamSwitch が信頼(自宅)ネットワークで意図的に見送る堅牢化——ゲートウェイ MAC の PERMANENT 固定(ルーター再起動で 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 の各チェック項目を既存フレームワークへ機械的に対応付けられるようにしています。
§8Model Context Protocol(MCP)サーバー
roamswitch-mcp は stdio で JSON-RPC を話す MCP サーバーで、Claude Desktop・Cursor・Antigravity 等の AI クライアントから起動されます。提供ツールはすべて 読み取り専用で、ほとんどがローカルで完結します。外部ホストへ接続し得るのは、既定で無効の run_npm_audit_signatures だけです。診断の評価(get_security_report と verify_security_findings)では、LAN に何も送りません。ゲートウェイの確認も、ARP キャッシュなど手元の情報を参照するだけです。ping が出るのは、ゲートウェイの MAC を引き直す別の処理(デーモンの ARP 監視、緊急復旧、Sensor ペアリング、get_guard_status など)のときです。
| ツール | 内容 |
|---|---|
| get_security_report | 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 のアプリ内設定の値で合否が決まるため、これらはアプリ内設定の反映であって、OS の実状態の再測定ではありません。読み取り専用で、外部のホストには接続せず、修復も設定変更もしません。Linux では LAN にも何も送らず、ゲートウェイはキャッシュを参照するだけです。Mac ではゲートウェイの MAC アドレスを調べるため、LAN 内のゲートウェイに 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)、シェルやインタプリタの1行コマンドでの起動(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)、Web ページ・メール・チャット・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)、誰からでも受け付ける DM・グループ設定(openclaw_open_dm)、mDNS の full(openclaw_mdns_full)、非表示にした自己診断の指摘(openclaw_audit_suppressed)、緩い権限(openclaw_loose_permissions)を検出します。規則はすべて決め打ちで、一致は「人が読んで確認する箇所」であり、悪意の断定ではありません。引数はなく、読み取り専用で、ネットワークには何も送りません。ファイルの本文や秘密は返さず(トークンは長さだけ)、規則名・行番号・URL のホスト・設定のキー名だけを返します。読めなかったファイルは unreadableFiles に並び、監査していません。 |
| get_exposed_ports | 外部公開ポート一覧とファイアウォール遮断状態 |
| get_guard_status | 現在の保護レベルと各防衛モジュールの稼働状況 |
| audit_url_safety | URL のヒューリスティック安全性診断(対象を取得しない) |
| audit_secrets | テキスト中の API キー・秘密情報の検出(テキストを送信しない) |
| audit_security_logs | ローカルの journald / 認証ログの集計 |
| get_quarantine_status / get_canary_status | 隔離・カナリアの状態 |
| get_app_help | 内蔵ナレッジベースの検索 |
| run_active_vuln_scan | 127.0.0.1 限定・非破壊の実証型脆弱性検証。外部ホストには接続しませんが、ネットワークリクエストを送るツールのため、既定は無効・設定でのオプトインが必要です。 |
| run_package_cve_scan | インストール済みパッケージ(Mac: Homebrew / Linux: dpkg・dnf・zypper・pacman)をローカルのCVEマップと照合します。通信は一切行いません。Linux では、パッケージの一覧を取得できなかった・タイムアウトしたときは「該当なし」とせず、complete が false になり、incompleteReason に理由が入ります(完了できなかった結果は問題なしではありません)。 Linux の 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 | 各ガードの検知を1つの時系列に統合して返します(プロセス系譜・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 です)。例外は、既定で無効の 2 つのツールです。run_active_vuln_scan は 127.0.0.1 のホスト自身へ非破壊プローブを送ります。run_npm_audit_signatures は npm コマンドを起動し、npm が npm レジストリへ問い合わせます(MCP バイナリ自体に HTTP / TLS スタックはありません)。診断の評価(get_security_report と verify_security_findings)では、LAN に何も送りません。ゲートウェイの確認も、ARP キャッシュなど手元の情報を参照するだけです。ping が出るのは、ゲートウェイの MAC を引き直す別の処理(デーモンの ARP 監視、緊急復旧、Sensor ペアリング、get_guard_status など)のときです。
ローカルLLMによる実地検証で見つかった誤帰属と、その修正
2026年9月、本MCPサーバーを使ってローカルLLM(Qwen 3.8 27B)にインシデント調査をさせる実地演習(fire drill)を行った際、モデルが get_port_anomaly_incidents の返すデータを誤読し、無関係な過去のポートブロックを、現在進行中のインシデントの物語に取り込んでしまう事例を発見した。原因は、タイムスタンプを持たない「現在ブロック中のポート」のスナップショットと、タイムスタンプ付きの実際のインシデント履歴が、同じレスポンスの中で区別なく返されていたこと。修正として、どちらが時系列データでどちらがスナップショットかを明示する注釈フィールドを追加した。修正後、同じシナリオを実際に同モデルで再実行したところ、モデルは注釈の文言を引用しながら、過去のポートを現在のインシデントに帰属させることを明示的に拒否した。回帰テストと実機LLMでの再現確認、両方で修正の有効性を確認済み。
§9外部通信の検証
RoamSwitch for Linux が既定で行う外向き通信は 1 種類だけです——日次アップデータによる署名付きマニフェストの GET(§11)。それ以外の診断・検知・監査はすべてローカルで完結します。運用者・利用者が明示的に有効化または操作した場合にだけ発生する通信は、9.4 に列挙します。RoamSwitch for Linux はプロプライエタリ・フリーウェアでソースは公開していないため、下記の検査は lafine が実施したもので、結果を 9.2 に、付録 C の手順(配布パッケージだけで再現可能)を示します。
9.1 監査方法
- クレートの隔離 — HTTP クライアント/TLS スタックを許可するのは 1 クレートのみ、タイマー駆動のアップデータバイナリをビルドする
roamswitch-updater。scripts/audit_no_network.sh(CI でゲートあり実行)は、roamswitch-core・roamswitch-daemon・roamswitch-app・roamswitch-mcp・roamswitchkit(および CLI・Server 系のクレート)のいずれかがreqwest/ureq/rustls/native-tls/openssl-sysなどの HTTP クライアント/TLS スタックを引き込むか、roamswitch-updaterと 9.4 に挙げたファイル以外の.rsがTcpStream/TcpListener/UdpSocket/to_socket_addrs/lookup_hostを使う場合にビルドを失敗させる。唯一の追加の許可として、eBPF センサー(Tetragon)の UNIX ドメインソケット越しの gRPC のためにtonic/hyperをroamswitch-coreにのみ許可しており、TLS との併用は同じスクリプトが禁止する。 - 依存ツリーの検査 —
Cargo.lock全体をテレメトリ/解析/クラッシュレポート SDK(sentry・opentelemetry等)について検索。 roamswitch-updaterのレビュー — 単一のリクエスト先、クエリ文字列・Cookie の不在、ソフトウェアを名乗るヘッダが固定の User-Agent だけであること、ダウンロードするすべてに対する Ed25519 検証。- サブプロセス呼び出しの列挙 —
Command::new(...)の全呼び出し箇所を確認。
9.2 結果
| 監査項目 | 結果 |
|---|---|
| HTTP / TLS スタック | roamswitch-updater のみに存在(ureq + rustls)。core / daemon / app / mcp / kit には無し |
| テレメトリ/解析/クラッシュレポート SDK | どこにも存在しない |
TcpStream / TcpListener / UdpSocket | roamswitch-updater のほかは、9.4 に挙げたオプトイン機能の 4 ファイルにだけ存在 |
| 使用されるソケット API | AF_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 を 1 つ送る場合のみ(LAN 内)。VPN トンネル(§4)を有効にした場合は wg-quick(WireGuard バックエンド)または tailscaled(Tailscale バックエンド)——いずれもデーモンではなくサブプロセス——がユーザー自身のエンドポイント/tailnet へ送出 |
| 日次アップデータ | 約 24 時間ごとに GET https://lafine.net/updates/v1/manifest と署名 manifest.sig の 2 回(データ本体は版が新しいときだけ追加で取得)。クエリ文字列なし・Cookie なし・User-Agent: RoamSwitch-Updater(ソフトウェアを名乗る唯一のヘッダ)・端末ごとの識別子なし。全ペイロードを Ed25519 で検証。updates.enabled = false で完全停止 |
| VPN トンネル(§4) | 既定 OFF。有効時、wg-quick がユーザーの .conf に書かれた自分の VPN エンドポイントへトンネルを張る(lafine とは無関係)。ホスト名解決は getent ahosts(glibc NSS)で、Rust のソケット API は不使用 |
| ライセンス認証 | Ed25519 署名のオフライン検証。認証サーバーへの通信なし |
RoamSwitch for Linux が既定で開始する外向き通信は 1 種類だけです。日次・受信専用の署名付きマニフェストの確認と、新しい版があるときの署名付き検出データのダウンロードです。どちらにも利用者・端末に関する情報は一切含まれません。運用者・利用者が明示的に有効化または操作しない限り、製品のそれ以外の部分が外部サーバーへ接触することはありません(9.4)。近隣テーブルの固定や ARP 監視のために LAN 内へ送る ping(9.3 の #2)は、宛先がループバック・プライベート・リンクローカル・ULA のアドレスに限られ、公開アドレスには送りません。
9.3 外部に触れる動作の全リスト
直接・間接を問わず、ローカルマシン外へパケットが出る可能性のある動作(下表は既定で発生するもの。運用者・利用者が有効化または操作した場合にだけ発生するものは 9.4):
| # | 動作 | 通信先 | 発生条件 | 送信内容 |
|---|---|---|---|---|
| 1 | 日次アップデータ — GET /v1/manifestとその署名。各データは版が新しいときだけ(カーネル・OS パッケージ・言語の CVE マップは約 30 日に 1 回確認。Ubuntu の CVE マップが未取得の Ubuntu 端末は、次の日次で取得) | lafine.net/updates/v1(CDN) | systemd タイマー。起動の 15 分後に始まり、以後は約 24 時間ごと(最大 3 時間のランダム遅延あり)。updates.enabled = false または roamswitch-update.timer 無効時は発生しない | 素の HTTPS GET:クエリ文字列なし・Cookie なし・固定の User-Agent: RoamSwitch-Updater・利用者や端末に関する情報は一切なし。署名付きマニフェスト+フィードを受信 |
| 1a | freshclam(ClamAV 自身の更新ツール)。ClamAV 導入時に日次アップデータが起動 | ClamAV プロジェクトの CDN | #1 の一部として。ほかに、利用者が画面の「ウイルス定義を今すぐ更新 (freshclam)」ボタンを押したときにも起動 | ClamAV 本体(別プロジェクト)の通信。RoamSwitch は起動するだけ |
| 2 | ping -c 1 -w 1 -W 1 <ゲートウェイIP>(同種の ping を、IPv6 ルーターと、LAN 内の DNS サーバー宛てにも各 1 回) | LAN のデフォルトゲートウェイ。ARP/NDP 固定の確認では、IPv6 のデフォルトルーターと、resolv.conf(systemd-resolved では /run/systemd/resolve/resolv.conf)に載っている DNS サーバーのうち、ループバック・プライベート・リンクローカル・ULA のアドレスのもの。公開リゾルバー(脅威保護 DNS の Quad9 など)には、パケットを送らない | ゲートウェイは、ARP 監視などで MAC が必要なのに ARP キャッシュ・近隣テーブル・15 秒以内の控えのどれにも無いときだけ(同じ宛先へは 8 秒に 1 回まで)。IPv6 ルーターと DNS サーバー宛ては、ゲートウェイ ARP 固定(gateway_arp_lock_enabled、既定オン)が近隣テーブルへの固定をやり直すとき(信頼ネットワーク以外への切り替え時と、設定の切り替え時) | ICMP エコー要求 1 回のみ(ping の標準のデータを含む)。DNS 解決なし |
| 3 | システムブラウザの起動(xdg-open) | https://roamswitch.com/linux(製品サイト)、https://lafine.net/(運営者サイト、利用規約、お問い合わせ)、https://github.com/lafine1211/roamswitch-support(リリースノートとサポート) | 「リリースノートを開く」等のボタンをユーザーが押した時のみ | RoamSwitch プロセスはソケットを開かない。URL を OS のブラウザに渡すだけ |
| 4 | resolvectl dns <iface> … | — (RoamSwitch 自身の通信は発生しない) | 未信頼ネットワークでの脅威保護 DNS(既定でオン。無効にもできる)。<iface> はデフォルトルートを持つ物理 NIC 全て(有線+無線の同時接続に対応。lo/tailscale*/wg*/docker*/veth* 等は除外)。dns_scope = always_on なら信頼ネットワークでも適用。sentinel が毎サイクル reconcile | OS のリゾルバ設定を変更するのみ。RoamSwitch 自身は DNS クエリを送らない。設定後は、この NIC の名前解決が、選んだ脅威保護 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 とは無関係 |
| 6 | VPN トンネル(§4) — WireGuard バックエンド: wg-quick が張るトンネル/Tailscale バックエンド: tailscale set --exit-node で全通信を Exit Node 経由に | ユーザー自身の VPN エンドポイント/tailnet(WireGuard は .conf で指定、Tailscale は選んだ Exit Node と DERP リレー) | vpn_on_untrusted_enabled かつ未信頼ネットワーク接続時。既定 OFF | 暗号化された WireGuard/Tailscale トラフィック。宛先はユーザー自身の VPN サーバー/tailnet であり lafine とは無関係。WireGuard のホスト名解決は getent ahosts(glibc NSS)、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 は、ソケット API の使用を event_forward.rs・webhook_guard.rs・active_vuln_scan.rs・web_vuln_scan.rs・sensor_pairing.rs の 5 ファイルだけに許可しており(webhook_guard.rs は、既定オフの設定 forward_webhook_block_private_addresses を有効にしたときだけ、Webhook の宛先の名前をシステムのリゾルバで引いて内部アドレスでないかを確かめるもので、接続はしません。ほかに tests/fleet_roundtrip.rs はテスト専用で 127.0.0.1 の模擬サーバーにのみ接続)、それ以外のソースが TcpStream 等を使えばビルドが失敗します。これらの機能でも HTTP/TLS のクレートは追加されず、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 のクレートはありません。送られるのはホスト名・イベント ID・時刻・種別・重大度・ルール・処置・PID・プロセス名・要約の各項目だけです(ファイルの内容や実行記録の生ログは含みません)。TCP/TLS/Webhook で届かなかったイベントは0600のディスクキュー(既定 20 MB/72 時間で上限)に貯めて再送します。 - Fleet(Business 層・端末側のみ)(
roamswitch-core/src/fleet/)— 端末側のコードは本体に含まれますが、登録していない端末は何も送らず、Fleet のホスト名の名前解決もしません(ワーカーはローカルの状態ファイルを 1 つ読むだけです)。登録は運用者がroamswitch fleet enrollを実行したときだけです。管理サービス側は未デプロイ・未出荷で、現時点で接続先はありません。コードにある設計上の保証は次のとおりです。(a) 端末が受け取るのは組織が署名したポリシーの設定データだけで、登録時に固定した組織公開鍵で検証し、検証に通らないポリシーは適用しません。組織の署名鍵は顧客が保持する設計で、lafine は保持しません。(b) 遠隔コマンドの経路はありません(隔離・解除・収集・実行を端末に命じるエンドポイント・フィールド・コードパスを、端末側に作っていません)。(c) ポリシーはローカルの下限を弱められません(ガードの無効化や、解除・緊急復元の妨害はスキーマ上表現できず、値は「より厳しい方」を採用します)。イベント転送の宛先を変えられるのも、ローカルの root が許可したホスト(fleet_forwarding_allowed_hosts、既定は空)に限られます。(d) 端末が送るのは、アプリ版・OS 種別・隔離状態(通常/隔離中)・適用済みポリシー版・健全性スコア(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を 1 回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のみ。これはレポートではなくダウンロードです。リンクガードの判定結果・遮断したホスト・スキャン結果はローカルのアクティビティビュー/journald にのみ記録され、送信されません。 - 脅威保護 DNS を使う間、DNS の問い合わせは外部のリゾルバーに届きます。脅威保護 DNS(§4.5。既定でオン、対象は未信頼ネットワークのみ)は、OS の DNS リゾルバーを Quad9(
9.9.9.9。Cloudflare の1.1.1.2も選べます)に向けます。この間の問い合わせは、RoamSwitch ではなく OS からそのリゾルバーに送られ、RoamSwitch 自身は何も送りません。止めるにはdns_enabledをfalseにします。 - 通知履歴は 7 日で自動削除・長さも制限 — 送信した通知の履歴はローカルに保存され、書き込みのたびに 7 日を過ぎた分を削除します。1 件のタイトルは 200 文字、本文は 2000 文字までに切り詰めます(デスクトップの通知ポップアップは本文 1000 文字まで)。この履歴は、イベント転送(既定 OFF)を有効にしない限り、端末の外へ出ません。
- 実行記録もローカルのみ — プロセス実行レコーダー(§5)の記録は、既定でも端末内(
/var/lib/roamswitch/exec_log、0600)にだけ保存されます。コマンドライン引数を含むため、保持期間(既定 14 日)と容量(既定 200 MB)で自動的に削除します。
§11ライセンスと配布
| エディション | 対象 | 価格 | ライセンス |
|---|---|---|---|
| Community Edition | Linux 個人利用・社内業務利用(台数制限なし) | 無料 | プロプライエタリ・フリーウェア(同梱の EULA) |
| Business(予定) | 組織での集中管理・ポリシー配布・署名済み内部 apt・SLA | 有償 | 商用ライセンス。同一バイナリ+ティアゲート方式 |
Business 層の提供を始めた時点で、macOS 版 Pro Lifetime 購入者には、本人の Linux マシンにおける Business 機能を無償で付与する方針です。現時点で Business 層は提供しておらず、管理サーバーもありません。
配布とサプライチェーン
| 経路 | 内容 |
|---|---|
| APT | https://lafine.net/apt(stable main)。RSA-4096 鍵で Release を署名 |
| RPM | https://lafine.net/rpm(Fedora / openSUSE 別)。repomd.xml を GPG 署名 |
| AUR | roamswitch-bin(準備中。AUR が新規アカウント登録を止めているため、当面は https://lafine.net/linux/dl/PKGBUILD から makepkg で導入。lafine.net/linux/dl の tarball を sha256 とデタッチ署名(validpgpkeys)で検証) |
| tarball | https://lafine.net/linux/dl/ に x86_64 / aarch64 の .tar.gz + .sha256 + .asc(リポジトリ署名鍵によるデタッチ署名)。ソースのリポジトリは非公開のため、一般向けの入手先は GitHub Releases ではなくここです |
- glibc 下限の固定 — リリースバイナリは Debian 12(bookworm、glibc 2.36)のコンテナ内でビルドされ、Debian 12 / Ubuntu 22.04 以降でシンボルバージョン不整合が起きない。CI に「glibc フロア検証」ステップを設け、2.36 を超える依存が混入した場合はビルドを失敗させる。
- 署名鍵の管理 — apt/rpm リポジトリ署名の秘密鍵は GitHub Actions の暗号化シークレットにのみ存在し、リポジトリには公開鍵のみをコミットする。
11.3 日次アップデータ
roamswitch-updater は systemd タイマー(roamswitch-update.timer。起動 15 分後に動き、以降は 24 時間ごと、最大 3 時間のランダムな遅延)で実行される単独バイナリです。既定で外部の配布 URL に接触する唯一のコンポーネントです(9.4 のオプトイン機能を除く)。各実行で:
GET https://lafine.net/updates/v1/manifestとそのデタッチ署名。- マニフェストの Ed25519 署名を検証。脅威フィード専用の署名鍵を用いる(本体アップデート用の Sparkle 鍵とは別鍵)。フィードはサーバ側 CI が毎日再署名するため、その秘密鍵は CI シークレットに置かれる。鍵をフィードに限定するのが要点で、万一漏れても攻撃者が配れるのは不正なブロックリストのみ(sinkhole/警告のみ。コード実行経路も本体更新経路もない)。本体アップデートの署名鍵は macOS キーチェーンから出ない。検証できないマニフェストは破棄され、無署名データにフォールバックすることはない。
- 脅威フィードのバージョンが変わっていれば、フィードを取得し、SHA-256 と Ed25519 署名を検証してリンクガード(§4)向けにアトミックに設置する。ほかのデータは、マニフェストの版が新しいときだけ取得する(実証型診断の CVE マップと人気 npm パッケージ名の一覧)。カーネルと OS パッケージ・言語エコシステムの CVE マップは、前回の更新から約 30 日たったときだけ取得する。ただし Ubuntu の CVE マップは、対応する版の Ubuntu の端末だけが取得し、まだ入っていない端末では、30 日を待たずに次の日次の実行で取得する。いずれも署名を検証してから設置する。
- マニフェストの版が新しいときは、Web アプリ層の検知ルール(§5)も取得する。SHA-256 と Ed25519 署名を検証し、さらにルールとして読めることを確かめてから、アトミックに設置する。読めないファイルは設置されず、動作中のルールは置き換わらない。新しい接続先は増えず、同じマニフェストの取得にまとまる。Server Edition では
cve_kernel_map_updates_enabledに従い、無効にすると通信は一切行われない。 - ClamAV が導入されていれば
freshclamを起動する。 - 本体の最新バージョンを通知用に記録する(アップデータは本体をインストールしない。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 64bit(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(Bluetooth ガード)、systemd-resolved(脅威保護 DNS の設定)、lsof(待ち受けポート検出の代替)、nmap(能動的脆弱性検証の補助)、wireguard-tools(VPN トンネル + キルスイッチの WireGuard バックエンド、§4)、tailscale(同 Tailscale バックエンド、§4)、polkit/pkexec(GUI からのアップグレード、アンインストール、デーモン再起動) - 非対応: 非 systemd 系ディストリ(Alpine / Void / Devuan)
- 画面: 1280×720 以上(画面サイズに応じてウィンドウとフォントを自動スケール。Raspberry Pi の小型ディスプレイに対応)
付録 A直接依存クレート一覧
| クレート | 用途 | ネットワーク |
|---|---|---|
| serde / serde_json | 設定・IPC のシリアライズ | なし |
| tokio | 非同期ランタイム(features = ["full"]。既定で使うのは Unix ソケットのみ) | なし(既定では TCP 未使用。オプトインの Sensor ペアリングを除く) |
| tracing / tracing-subscriber | ログ出力(ローカル・journald) | なし |
| thiserror / anyhow | エラー処理 | なし |
| regex | 文字列解析(診断・CSS スケーリング) | なし |
| url | URL のパース(audit_url_safety のヒューリスティック) | なし(取得しない) |
| chrono | 日時処理 | なし |
| ed25519-dalek | ライセンス署名と、アップデータのマニフェスト・脅威フィードなど署名付きデータのオフライン検証(オプトインの Sensor ペアリングでも使用) | なし |
| base64 / sha2 | ライセンストークン・カナリアのハッシュ | なし |
| libc | chown / kill / システムコール | なし |
| idna | ホモグラフ検知のための IDN/punycode ホストのデコード | なし |
| nfq(daemon のみ) | リンクガードの NFQUEUE バインディング——純 Rust、AF_NETLINK | なし(verdict チャネル、送出なし) |
| open | 「リリースノートを開く」や EULA・サポートページのリンクを開くために xdg-open を呼ぶ | プロセス自体は通信しない |
| GTK3 / ksni(app のみ) | GUI とトレイ | なし |
| serde_yaml | /etc/roamswitch/guard.yaml の読み込み(core) | なし |
| toml | Cargo.lock / poetry.lock の解析(言語パッケージの CVE スキャン、core) | なし |
| md-5 | リンクガードの JA3 フィンガープリントのハッシュ(認証や完全性の用途ではない、core) | なし |
| bs58 | 暗号資産の秘密鍵の Base58 チェック(秘密情報の検出、core) | なし |
| rand | オプトインの Sensor ペアリングの端末鍵の生成(core) | なし |
| tonic / tonic-prost / prost / tower / hyper-util | Tetragon センサーとの gRPC。Unix ドメインソケットのみ、TLS なし(core) | なし(Unix ソケットのみ) |
| flate2 | RHEL 系パッケージ CVE マップの gzip 展開(updater のみ。純 Rust) | なし(展開のみ) |
| inotify | ハニートークン(認証情報のおとり)へのアクセス監視(roamswitch-honeytokens) | なし |
| aya | 実験的な XDP ブートゲートのローダー(roamswitch-xdp-gate。daemon が参照) | なし(bpf() / netlink のみ) |
| urlencoding / gdk / gdk-pixbuf / glib / pango | GUI の URL デコードと GTK3 の周辺クレート(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 はどこにも無し。
付録 Bnftables テーブル構造
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(ICMP・ICMPv6 の許可は balanced のみ)
}
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 の ARP・NDP 固定(§4)は nftables ではなく カーネルの neigh テーブル(ip neigh replace … nud permanent)で行われます。固定エントリの記録は /var/lib/roamswitch/arp_lock.json。
付録 Cセルフチェック
本書の主張は、いずれも利用者自身の手元で確認できます。以下は roamswitch をインストールした環境で実行できます。
C.1 通信の検証
daemon / app / 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 のインターネット接続は 1 つも無い
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)"。この 3 つはインターネットソケットを一切開かない。
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('
# → AF_UNIX / AF_NETLINK への connect のみ。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) 自律ネットワーク防衛 & Air-Gap 自己修復
| ID | シナリオ | 検証手法 | 判定基準 |
|---|---|---|---|
| SP-3 | Air-Gap 有効化 → 外部遮断 | enable_air_gap IPC 送信 | output フックに policy drop が適用され、外部通信が完全遮断 |
| SP-3b | 防火壁強制消去時の自己修復 | nft delete table inet roamswitch による強制ルール削除 | Anti-Clobber Self-Healing が 5 秒以内に遮断ルールを自動再展開 |
| SP-3c | デーモン再起動時のフェイルクローズド | Air-Gap 中にデーモンを終了(selftest.sh では kill による SIGTERM)して再起動 | 再起動後も Air-Gap 遮断状態が自動で引き継がれる |
| SP-3d | Air-Gap 正規解除 → 通信復旧 | disable_air_gap IPC 送信 | drop ポリシー解除、全マーカーファイル削除、外部通信が即時復旧 |
| SP-8 | 予防的 ARP/NDP 固定 | 独立 netns ゲートウェイ環境で有効化 → 偽の gratuitous ARP 送出 | ゲートウェイ neigh エントリが PERMANENT を維持し、偽 ARP を拒否 |
| SP-9 | VPN キルスイッチ(WireGuard) | 実 WireGuard サーバー(別 netns)を立て .conf import → vpn_up | inet roamswitch_vpn(policy drop)下でハンドシェイク成立。トンネル死後も漏洩ゼロ |
| SP-10 | VPN キルスイッチ(Tailscale) | スタブ tailscale CLI +独立 netns、tailscale0 IF | トンネル外の平文 TCP/80 とローカル DNS を遮断し、DERP(TCP/443)と MagicDNS を許可 |
(2) カーネル & マウント堅牢化(Kernel Hardening)
| ID | シナリオ | 検証手法 | 判定基準 |
|---|---|---|---|
| SP-11a | ネットワーク sysctl 堅牢化 | apply_kernel_sysctl_hardening IPC 実行 | rp_filter=1, tcp_syncookies=1, accept_redirects=0 等の 7 項目が目標値に設定 |
| SP-11b | メモリ保護 & Yama LSM | ptrace_scope および suid_dumpable の設定検証 | kernel.yama.ptrace_scope=1(他プロセス盗聴阻止)、fs.suid_dumpable=0(コアダンプ抑止) |
| SP-11c | /tmp & /dev/shm noexec 防御 | apply_mount_hardening IPC 実行 | /tmp と /dev/shm が noexec,nosuid,nodev で再マウントされ、シェル/バイナリ実行が拒否 |
| SP-11d | ユーザー領域の実行性維持 | 正当なホームディレクトリ(~/)でのスクリプト実行 | 影響を受けず正常に実行可能(開発・日常運用に副作用を与えない) |
(3) 多層マルウェア・ランサムウェア防御 & カナリア
| ID | シナリオ | 検証手法 | 判定基準 |
|---|---|---|---|
| SP-1 | ランサムウェアおとり(カナリア)改ざん検知 | ~/Documents/ のカナリアファイル(.xlsx)を truncate / rename | 数秒以内に改ざん・移動を検知し Air-Gap を即時発動 |
| SP-2 | ランサムウェア一括暗号化のバースト検知 | 単一プロセスが短時間に多数の高エントロピーファイルを生成 | 当該プロセスを SIGSTOP で即時凍結し Air-Gap を発動 |
| SP-2b | shred 等の正当ツールの誤検知防止 | shred -n 3 -u を複数ファイルに対し実行 | ホワイトリスト照合により誤検知・凍結が発生しない |
| SP-2c | 重要システムプロセスの非凍結 | バースト検知時の凍結対象ロジック検証 | dockerd, systemd, containerd 等の重要インフラプロセスを絶対に凍結しない |
| SP-4 | EICAR テスト文字列の非隔離 | EICAR テスト文字列を Downloads に書き込み | 通知は出さず通知履歴に記録のみ。ファイルを勝手に隔離・削除しない |
| SP-4b | 実シグネチャ検知時の確認制隔離 | 実マルウェアシグネチャを書き込み | 即時無断隔離せず、承認ダイアログを発行。ユーザー承認(quarantine_path)で退避 |
| SP-4c | 隔離 Vault の権限堅牢化 | 隔離 Vault ディレクトリおよび検体のパーミッション確認 | Vault ディレクトリ 0700、隔離検体 0400(読み取り専用・実行不可) |
| SP-4d | 原本安全復元(特権 IPC) | restore_quarantine_file IPC 送信 | 元ディレクトリの所有者権限(0644)で安全に復元し、除外リストへ登録 |
(4) 受動リンクガード・フィッシング防御
| ID | シナリオ | 検証手法 | 判定基準 |
|---|---|---|---|
| SP-7 | mode = block 強制遮断 | フィード掲載ホストおよび blocklistExtra ホストへ接続試行 | TLS 層(SNI パース)で即時 DROP。未掲載の正常ホストは HTTP 200 到達 |
| SP-7b | mode = warn fail-closed 動作 | 警告対象ホストへ接続試行し、ユーザー無応答でタイムアウト(8秒) | タイムアウト時に fail-closed(drop)。ユーザー明示「許可」時のみ通過 |
| SP-7c | mode = off 完全解除 | リンクガードを無効化 | inet roamswitch_linkguard テーブルが撤去されオーバーヘッドなし |
(5) BadUSB 対策・デバイス制御
| ID | シナリオ | 検証手法 | 判定基準 |
|---|---|---|---|
| SP-6 | 起動時接続キーボードの grandfathering | デーモン起動時に接続済みのキーボードを確認 | 既存デバイスを正常認識。起動後の新規ホットプラグのみグラブ対象 |
(6) 攻撃を模したセルフチェックの項目(PENT-1〜7 は公開 Docker スイート、PENT-8・9 は別の手動検証)
| ID | 攻撃手法 | シミュレーション内容 | 防御・検知判定基準 |
|---|---|---|---|
| PENT-1 | C2 Exfiltration | Air-Gap 下での C2 宛て外部 HTTP/TCP 通信試行 | パケットフィルタにより全パケット即時 DROP |
| PENT-1b | FW 強制消去攻撃 | nft delete table inet roamswitch による防御破壊 | Anti-Clobber Self-Healing が数秒以内に自動再展開 |
| PENT-1c | Air-Gap 正規復帰 | 認証済み disable_air_gap IPC 送信 | DROP ルール撤去・全マーカー消去・通信完全復旧 |
| PENT-2 | /tmp バックドア実行 | /tmp に設置した悪意あるシェルの実行試行 | noexec マウント防御により Permission denied 拒否 |
| PENT-2b | /dev/shm ペイロード実行 | 共有メモリ /dev/shm に配置したバイナリの実行試行 | noexec マウント防御により実行遮断 |
| PENT-2c | ユーザー通常実行性 | 正当なユーザー領域(/home/tester/)でのスクリプト実行 | 開発・日常作業への影響ゼロ(正常実行) |
| PENT-3 | ptrace メモリ奪取 | PTRACE_ATTACH でプロセスから秘密鍵・トークン抽出試行 | Yama LSM ptrace_scope=1 によりアタッチ拒否 |
| PENT-3b | コアダンプメモリ抽出 | プロセスクラッシュ時のコアダンプ生成を試行 | fs.suid_dumpable=0 によりコアダンプ生成抑止 |
| PENT-4 | カナリア改ざん攻撃 | ~/Documents/ のカナリアファイル(.xlsx)をゼロクリア | サイズ異常・ハッシュ改変を即時検知 |
| PENT-4b | カナリア一括抹消 | カナリアファイルの完全削除を試行 | ファイル消失をリアルタイム検知しインシデント発報 |
| PENT-5 | IP スプーフィング | 送信元 IP アドレスを偽装したパケットを送信 | rp_filter=1(リバースパスフィルタ)が自動破棄 |
| PENT-5b | SYN Flood DoS | 未完了接続を大量送信して接続テーブル枯渇を試行 | tcp_syncookies=1(SYN Cookies)でテーブル枯渇防護 |
| PENT-5c | ICMP リダイレクト奪取 | 不正な ICMP Redirect でルーティングハイジャック試行 | accept_redirects=0 によりリダイレクトを無視 |
| PENT-6 | キリル文字ホモグラフ詐欺 | gооgle.com 等の同形異字を用いたフィッシング誘導 | LinkGuard が偽装特定、40点(DANGEROUS / block)判定 |
| PENT-6b | 生 IP 直打ち・平文通信 | 未暗号化の生 IP 直打ち接続試行 | リスクファクターとして正確に検知・警告 |
| PENT-7 | OpenAI API キー漏洩 | コードやログに sk-proj-... トークンが含まれる事象 | エントロピー解析で検知し安全にマスク(sk-pro...YZ12) |
| PENT-7b | GitHub トークン漏洩 | コードやログに ghp_... トークンが含まれる事象 | シグネチャ・エントロピー解析で正確に特定 |
| PENT-8 | カーネル LPE (Frag Gap) 悪用阻止 | 非特権ユーザーによる 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-9 | eBPF 脅威検知 → プロセス凍結 & 遮断 | Falco eBPF ソケット経由での Critical/Emergency アラート注入 | 当該 PID を SIGSTOP でミリ秒凍結(状態: Ts)し Air-Gap を即時展開 |
検証の種類について、上の表のうち、PENT-2・2b(noexec)、PENT-3・3b(Yama、コアダンプ)、PENT-5・5b・5c(rp_filter、SYN Cookies、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 の検証環境(Target: Ubuntu 24.04 + RoamSwitch Client Edition、Attacker: Nmap / curl / hping3 等)にて実施した攻撃を模した包括的なセルフチェックの結果総括です。下の表は Client v1.1.0 を 2026-09-06 に測定した結果で、2026-09-25 には 1.10 系のソースからビルドした修正版で、クライアント版のスイート 11 項目中 11 項目が成功しています(公開版ではありません。公開リポジトリの audit/RESULTS-PENTEST-2026-09-25.ja.md 参照)。
| 検証項目 | Falco なし (スタンドアロン堅牢化) | Falco あり (eBPF ランタイム防護) | 防御判定 |
|---|---|---|---|
| 外部ポートスキャン耐性 (Nmap SYN) | 合格(Lockdown で 100% Filtered / DROP) | 合格(Lockdown で 100% Filtered / DROP) | 外部完全ステルス化 |
| 外部直接アクセス遮断 (TCP/80, 3306) | 合格(タイムアウト遮断・無応答) | 合格(タイムアウト遮断・無応答) | 外部露出ゼロ |
| カーネル LPE (Frag Gap) 悪用防止 | 合格(v1.1.0 の測定。user.max_user_namespaces = 0 の環境で unshare を遮断。現行の Client Edition では既定でオフ) | 合格(eBPF 即時検知 + SIGSTOP 凍結 + Air-Gap) | 多層防御確立 |
| 未許可ポート検知 (アノマリー) | 合格(0.0.0.0 公開プロセスを高リスク検出) | 合格(0.0.0.0 公開プロセスを高リスク検出) | 100% 検知・警告 |
| ログ肥大化・CPU負荷対策 | ログ発生なし(ゼロオーバーヘッド) | 合格(ノイズ95%カット / バッファ2MB / 50MB上限) | デスクトップ常駐最適化 |
C.3 既知の制約
| 制約 | 影響 | 対応 |
|---|---|---|
一部カーネルで tmpfs(/tmp)への FAN_MARK_MOUNT がイベントを配送しない | /tmp のファイル書き込みは fanotify で監視されない | /tmp は揮発性かつ監視対象ユーザーフォルダではない。ランサムウェアの標的は ~/。マーク失敗時はログに明示 |
| コンテナ overlayfs / tmpfs での fanotify 非配送 | Docker 内でのエントロピーバースト検知が検証不可 | 実機(ext4)で検証。ハーネスがプローブで自動 SKIP |
RoamSwitch の nftables 操作と Docker の iptables-nft が同一 netfilter 空間を共有 | RoamSwitch のルール操作が Docker の NAT ルールと干渉し得る | 専用テーブル(inet roamswitch ほか roamswitch_*)で分離するが、nft flush ruleset 等の全体操作は避ける |
| リンクガードはブラウザの DoH を TLS SNI 経由でしか把握できず、ECH(Encrypted Client Hello)はそれも隠す | ECH が普及すると、ECH 対応サイトへの ECH 接続はホスト名で識別できない | ローカルのブロックリストとヒューリスティックは DNS/HTTP と非 ECH の TLS には引き続き作用する。リゾルバ経路は脅威保護 DNS(§4.5・既定でオン)でカバー |
updates.json の enabled が false の場合、フィードの鮮度はリリース間隔に依存する | 更新を切ると、フィードは直近の apt upgrade 時点のものになる | 最重篤の検出(ブランドのホモグラフ)はフィード不要。常時最新が必要なら更新を再度有効化するか脅威保護 DNS を使う |