「外部通信禁止」と言われたので、
Copilot CLIをWSL2に閉じ込めた

GitHub Copilot CLI × WSL2

inoinoino

自己紹介

プロフィール画像のジョウビタキ

GitHub: in0ho1no / X: @in0ho1no

普段は組み込み開発

C言語一筋10年 / 趣味でPythonを数年

AIは個人利用が主

業務での利用は推進中

今日の流れ

アジェンダ

  1. 背景:外部通信禁止の要件と、案件混在の防止

  2. Copilot Chatでは足りなかった

  3. Copilot CLIをWSL2に閉じ込める

  4. 運用に落とす(但し、現場投入はマダ)

  5. まとめ:多層防御の一層と、残る制約

扱う情報によって、求められる機密性は違う

01 背景

多くの案件

AIを普通に使える。Web検索もMCPサーバも使える

ある案件

AIの「Web検索」をオフにするよう求められた。これを「外部通信禁止」と解釈した

それでも、AIは使いたい。いいとこどりをしたい。

求められたことは2つ

01 背景

外部通信の遮断

公式サーバ(GitHub)以外へ通信しない

案件の分離

案件Aの情報を、案件Bの作業に混ぜない

(ファイル、会話履歴、認証情報)

しかも「設定し忘れ」や「AIによる設定の書き換え」で崩れないこと

エージェント自身が、遮断を認めた

01 先に結論

Copilot CLIでcurlを実行すると「Could not resolve host」で失敗し、エージェントが「サンドボックスのポリシーで通信が遮断された」と説明している画面

これを案件ごとに、設定し忘れても崩れない形で作る

VS CodeのSandboxを有効にしても、通信は止まらない

02 Copilot Chatでは足りなかった

外向き通信

既定で許可。トグルはオン・オフだけで、通信は止まらない

fetch・統合ブラウザ

サンドボックスの対象外。別のポリシーで止める必要がある

Localセッション

対象外。効くのはAgent Hostセッションだけ

案件単位で強制できない

02 Copilot Chatでは足りなかった

分離

VS Code Serverが、案件をまたいで共有される

起動

起動のしかたを揃えられず、案件ごとの設定を強制する入口がない

案件ごとにプロセスを分けて起動できる、Copilot CLIを選んだ

なぜ、WSL2の専用ディストリなのか

03 Copilot CLIをWSL2に閉じ込める

Copilot CLIのサンドボックスは、WindowsではMXCでも動く。それでもWSL2を選んだ

Claude Codeも見据える

Claude Codeのサンドボックスは、ネイティブWindowsでは動かない。WSL2なら同じ構成で揃えられる

案件分離を意識させる

ディストリを丸ごと分ける。プロンプトのホスト名で、今どの案件にいるかが見える

構成:案件ごとに専用ディストリを作る

03 Copilot CLIをWSL2に閉じ込める

Gitリポジトリの1ファイルから案件ごとに専用ディストリAI-caseAとAI-caseBを構築し、通信するのは各ディストリのCopilot CLI本体から公式サーバ(GitHub)へだけであることを示す構成図

3つの層で、止めるものを分担する

03 Copilot CLIをWSL2に閉じ込める

サンドボックス+管理設定

止める:AIが実行するコマンド

bubblewrapとslirp4netnsで、通信と作業フォルダ外を遮断。管理設定で、緩められない下限を置く

ランチャー

止める:起動の条件のばらつき

許可リスト外の環境変数を捨て、決まった引数で起動する。直接起動は案内に置き換える

専用ディストリ

止める:案件をまたぐ参照

案件ごとに分ける。wsl.confで、Windowsのファイル(/mnt/c)とアプリ(powershell.exe)への経路を断つ

管理設定で、緩められない下限を置く

03 Copilot CLIをWSL2に閉じ込める

/etc/github-copilot/managed-settings.json
"sandbox": {
  "enabled": true,
  "failIfUnavailable": true,
  "allowBypass": false,
  "auth": { "git": false, "gh": false },
  "userPolicy": { "network": {
    "allowOutbound": false,
    "allowLocalNetwork": false
  } }
}
エージェントの手が届かない場所に置く

rootだけが書き込めるファイルにする

ここより緩められない

ユーザ設定、リポジトリの設定、起動時の引数のどれでも

効いているかを見る

条件外は黙って無視される。/sandboxに(managed)と出るか

検証結果:何がどう遮断されたか

03 Copilot CLIをWSL2に閉じ込める

確認したこと

結果

シェルからの通信(curl)

名前解決できずに失敗。ネットワークはloだけ

Web取得・GitHub MCP

接続できず、ツールは使えない

作業フォルダ外のファイル

シェルからは存在ごと隠れ、組み込みツールは拒否

トークン・設定ファイル

~/.copilot/config.jsonは読めず、~/.copilotにも書けない

環境変数の鍵

ランチャーで除かれる(GITHUB_TOKENは未設定)

Windowsアプリ

command not found

別の案件

別ディストリのファイルは見えない

メモリ機能

有効だと全リポジトリ共通の記憶に入った。無効化後は作業フォルダのAGENTS.mdへ

Copilot CLI 1.0.107、Ubuntu 24.04.5、bubblewrap 0.9.0、slirp4netns 1.2.1で確認

エージェント側からは緩められない

03 Copilot CLIをWSL2に閉じ込める

起動時の引数

--no-sandboxを付けても無視される

--no-sandbox was ignored because an enterprise policy requires the sandbox.

作業フォルダの設定

エージェントが書き込める場所に緩める設定を書いても、効かない

フック

disableAllHooksでも作業フォルダのフックは動いた。ただしフックもサンドボックス内で動き、通信できない

別ターミナルでの人の操作は、対象外。運用ルールで扱う

遮断は、無害な結果に見えることがある

03 Copilot CLIをWSL2に閉じ込める

$ cat ~/outside/secret.txt
No such file or directory

ファイルは実在する。サンドボックスが存在ごと隠している

$ touch ~/probe ~/.copilot/probe
(作成できたと表示)

実際のホームには残っていない。仮のホームに書かれて捨てられる

防御としては問題ない。ただ、利用者が混乱しないよう「遮断されるとこう見える」を周知しておく

利用者へのお願いは「ランチャーから起動する」だけ

04 運用に落とす

$ copilot
Copilot CLIはランチャー(/opt/ai-env/launch-copilot.sh)から起動してください
$ /opt/ai-env/launch-copilot.sh

ランチャーがやること

  • 許可リスト以外の環境変数を捨てる

  • Gitリポジトリを保護してから起動する

  • --sandboxと--disable-builtin-mcpsを付ける

直接起動はガード関数で案内に置き換える。設定し忘れる余地を作らない

Git:コミットはエージェント、Pushは人

04 運用に落とす

エージェント

  • コミット:できる

  • Push:できない

Could not resolve host: github.com

人(別ターミナル)

fine-grained PATでPushする

対象リポジトリだけ、Contentsの読み書きだけ、有効期限は短く。credential.helperのcacheでメモリに1時間だけ持つ

使わない:gh auth login、credential.helper store、SSH鍵。どれもディスクに残るため

仕込まれたフックは、人の操作で動く

04 運用に落とす

保護する前は、エージェントが置いたフックが、人のコミットをきっかけにWSL2内でも、Windows側のVS Codeからも実行された

対象

所有者と権限

.git

root:worker 3775(作れるが消せない)

.git/config

root:root 0644(書き換えられない)

.git/hooks

root:root 0755(追加できない)

自動で保護する

ランチャーの起動前と、1分ごとのタイマーで実行。既に怪しいフックや設定があれば警告する

消すときは、トークンを取り消してから

04 運用に落とす

削除は専用のスクリプト(Remove-AiDistro.ps1)で行う。

取り出す

ディストリ内のトークン。値は表示しない

取り消す

GitHubのRevoke API(202)

確かめる

そのトークンが401になるまで待つ

削除する

名前を再入力してから登録を解除

どこかで失敗したら、ディストリは消さずに止まる

先にwsl --unregisterで消すと、トークンを取り出せず手作業で取り消すしかない。手作業は必ず漏れる

VS Codeは、Windows側から開いて使う

04 運用に落とす

\\wsl.localhost経由で開く

automountを切っているので、WSL拡張では接続できない

AI拡張は使わない

サンドボックスを通らずに、案件のファイルを扱えてしまう

制限モードで開く

エージェントが置いたtasks.jsonなどが、Windows本体で自動実行されるのを防ぐ

「これで保証」ではなく、多層防御の一層

05 まとめ

残る制約

扱い

管理設定が、契約形態や更新で効かなくなる可能性

構築時に(managed)の表示を確認する

Copilot CLIは自動で更新される

更新後に、同じ手順で再検証する

別ターミナルでの人の操作

運用ルールで扱う

Windows側からの参照(\\wsl.localhost)

運用ルールで扱う

意図的な迂回(command copilot、wsl -e)

サンドボックスは外れない。残りは運用ルール

機密性に応じて環境を選び、
外部通信禁止の案件でもAIを使い続ける

05 まとめ

案件ごとの専用ディストリ + サンドボックス + 管理設定 + 運用ルール

ありがとうございました