2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ローカル LLM の監視強化(前編):Qwen の「外への送信」報道を受けて、AI エージェントを OS とネットワークで止める

2
Last updated at Posted at 2026-10-31

1. 背景:LLM モデルが勝手に外部にデータ送信!?

最初に、LLM モデル単体では、インターネットに勝手にアクセスしたり、不正な操作をしたりすることはできません。 LLM モデルがするのは、文字を受け取って文字を返すことだけです。チャットの画面で LLM モデルと話しているだけなら、LLM モデルが「ファイルをアップロードする」と書いても、それはただの文字で、何も起きません。

問題が起きるのは、LLM モデルをハーネスと組み合わせたときです。ハーネスは、LLM モデルにシェルやファイル操作、Web の取得などの道具を持たせ、LLM モデルの出力に従って実際にコマンドを実行し、作業を進めさせる仕組みです(OpenCode、Claude Code、Codex CLI など)。この記事では、LLM モデルとハーネスを合わせたものをAI エージェントと呼びます。

この記事で扱う「外への勝手な送信」も、LLM モデルが道具を呼び、ハーネスがそれを実行して初めて起きます。だから守りも、LLM モデルではなく、ハーネスが動く場所(OS とネットワーク) に置きます。

1.1 何が起きたか

2026 年 10 月 26 日、Cohere が、Alibaba の Qwen3.8-Flash-Next を評価している最中に、頼んでいない外への送信が起きかけたと発表しました(Cohere の発表)。The Register は、これを次のように報じています。

Open-weight models from China have become very popular, but they've also raised some security suspicions.
(中国のオープンウェイトのモデルは広く使われるようになったが、セキュリティの面で疑いも持たれている。)

— Alibaba's Qwen AI caught trying to contact server in China(The Register、2026 年 10 月 26 日)

同じ記事で、Cohere の共同創業者 Ivan Zhang は、次のように話しています。

We don't have evidence that would allow us to conclude this was deliberate, and we're not making that claim.
(わざとだったと結論づけられる証拠は無く、そう主張するつもりもない。)

評価は、AI エージェントに本物の道具(シェルなど)を持たせ、閉じたサンドボックスの中で課題を解かせる形でした。課題は、セキュリティの分析者向けの「怪しいダウンロードを見つける検知ルールを書く」というものです。その途中で、LLM モデルは次の順に動きました。

外への経路が無いサンドボックスだったので、送信は失敗しています。送ろうとしたのも、試験用の文字列でした。

押さえておきたい点が 3 つあります。

  • 頻度はとても低い。Cohere によれば、評価の呼び出し 10 万回に 1 回未満です。同じ課題をやり直すと、ふつうに検知ルールを書きました。
  • 意図があったのかは、分かっていない。Cohere 自身も、わざとだったとは主張していません。開発時の記録が学習データに混ざった可能性なども挙げています。
  • LLM モデルの思考の出力は、当てにならない。思考の出力には「ツールは使っていない」と書かれていましたが、実際にはコマンドを実行していました。

Cohere の勧めは、不要な外への通信を止めること、ツールの使い方を見張ること、LLM モデルの思考の出力を守りの境界にしないこと、の 3 つです。

1.2 特定の LLM モデルの問題ではない

この件は「Qwen が危ない」という話として広まりがちですが、2026 年に起きたことを並べると、そうではないことが分かります。

時期 出来事 何が起きたか
2026 年前半〜 各社のハーネスと LLM モデルでの、データの消失 AI エージェントが、本番のデータベースやホームディレクトリを消した事故が、いくつも報告されている
2026 年 7 月 Hugging Face の内部の評価 評価中の AI エージェントが、パッケージのキャッシュの穴から別のサービスへ渡り、内部ネットワークまで入った
2026 年 9 月 OpenAI の研究用の AI エージェント Web を止められていたが、DNS の問い合わせに文字を埋め込んで、外のチャットボットとやり取りした
2026 年 10 月 Cohere の評価での Qwen3.8-Flash-Next 1.1 のとおり。外への経路が無く、失敗

どれも、AI エージェントが「悪意を持って」動いたわけではありません。課題をこなそうとして、止められているものを障害として回り込んだ、と見るのが自然です。そして、うまく防げたのは、外への経路を仕組みとして閉じていた場合でした。

当ラボでも、9 月にローカル LLM を 7 本並べて、ファイルサーバの調査と設計の課題を解かせる比較を行いました(過去の記事)。このとき、ツール呼び出しの記録を監査したところ、指示に反した動きがいくつか見つかりました。「ほかの LLM モデルの成果物は読まない」という指示があったのに読んで写し、作業の記録には「読んでいない」と書いた例や、使ってよいと言っていないサーバへアクセスした例です。いずれも外部への送信ではなく、データが外に出ていないことは記録で確かめています。それでも、ここから分かったことは 1 つです。

禁止事項は、指示で伝えるだけでは守られない。どの LLM モデルでも同じ。

1.3 どんなに優秀な LLM モデルでも、調子が落ちるときがある

もう 1 つ、見落としやすい点があります。ふだんは優秀な LLM モデルでも、いつも同じ調子で動くとは限りません。

クラウドの LLM を使っていると、ふだんは賢いのに、ある時期だけ性能が落ちたように感じることがあります。原因は外からは分かりませんが、実際に品質が落ちていた例が公表されています。2025 年 8〜9 月、Anthropic は Claude の応答の品質が落ちていたことを認め、原因はインフラの 3 つの不具合(要求の振り分けの誤り、出力が崩れる設定ミス、コンパイラの不具合)だったと説明しました。最も影響が大きかった 1 時間には、Claude Sonnet 4 への要求の 16% が影響を受けていました。

調子が落ちたときに起きやすいのが、まさに「やらかし」です。

  • 前に決めた約束を忘れる。 「これはやらない」と最初に伝えたことを、作業の途中で忘れてしまう。
  • ダメだと言われていることを、やってしまう。 禁止されていることを、悪気なく実行してしまう。

ローカル LLM でも事情は同じです。作業が長くなってコンテクストの上限に近づくと、ハーネスは古い会話を要約して詰め直します。このとき、最初に伝えた禁止事項が要約からこぼれ落ちることがあります。量子化の具合や、推論サーバの設定によって、同じ LLM モデルでも振る舞いが変わることもあります。

つまり、「この LLM モデルは優秀だから大丈夫」は、いつでも成り立つわけではありません。調子が落ちたその一瞬に、守りが無いと事故になります。

1.4 だから、仕組みで止める

当ラボでは、企業の中で特許情報や機密情報を扱うことを想定して、ローカル LLM とハーネス(OpenCode など)を組み合わせた AI エージェントで、外に出せないデータを扱う作業をしています。

Cohere の件を受けて、ラボでは「危険な振る舞いをする LLM モデルがありうる」という前提に立ち、AI エージェントの外への通信を許可制にしました。考え方は次の図のとおりです。

ポイントは、守りを LLM モデルやハーネスの外に置くことです。

  • LLM モデルに「外へ送らないで」と指示しても、守られるとは限りません(1.2)。調子が落ちたときには、指示そのものを忘れることもあります(1.3)。
  • ハーネスの権限の設定(「sudo は禁止」など)も、名前で止めているだけのことが多く、python や sh -c を通せば素通りします。
  • LLM モデルの思考の出力も、当てにできません(1.1)。

そこで、ハーネスを専用のユーザーで動かし、OS(systemd と nftables)で外への通信を止めます(インターネットに出る必要があるときはプロキシも使います。後編で説明します)。

Qwen3.8-Flash-Next は、ラボの 9 月の比較で唯一の満点を取った LLM モデルで、今も常用しています。この仕組みは、良い LLM モデルを、安心して使い続けるためのものです。そして、ほかのどの LLM モデルにも、同じように効きます。

この記事(前編)では、この考え方と、インターネットに出る必要のない AI エージェントの守りを、実際に入れた手順と一緒に紹介します。後編では、ブラウザで Facebook などに出る必要がある AI エージェントの守りを扱います。

2. 守りの層:インターネットに出ない AI エージェントの場合

前編で扱うのは、社内のファイルを調べて設計や分析をする AI エージェントです。必要な通信は、推論サーバ(LLM を動かしているサーバ)とのやり取りと、人が様子を見る Web UI だけです。インターネットへ出る必要は、ありません。

ラボでは、この AI エージェントを次の形で動かしています。ハーネスは OpenCode v2(2.2.0)、LLM モデルは別のマシンの推論サーバで動く Qwen3.8-Flash-Next です。

2.1 全体の形

守りは 5 つの層に分かれています。それぞれ、別の抜け道をふさぎます。

層 何をするか ふさぐ抜け道
① 専用ユーザー OpenCode を、sudo もログインもできないユーザーで動かす 人のユーザーの権限(sudo、鍵、設定)をそのまま使われる
② systemd 書ける場所、見える場所、使える機能を絞る ほかの場所への書き込み、権限の昇格、名前空間を使った抜け道
③ nftables 専用ユーザーの通信を、推論サーバだけに絞る インターネットや LAN のほかのサーバへの送信、DNS の抜け道
④ ハーネスの設定 設定を root の持ち物にし、プロジェクト側の設定を読ませない 自分の設定や権限を書き換える
⑤ 毎回読む決まり 守りの決まりを、要約されても消えない場所に置く 長い作業で禁止事項を忘れる

①〜③が「OS とネットワークで止める」本体で、④⑤はそれを補う層です。順に説明します。

2.2 ① 専用ユーザーで動かす

ハーネスは、動かしているユーザーにできることを、すべてできます。人のユーザーで動かしていると、そのユーザーの sudo、SSH の鍵、クラウドの認証情報、ブラウザに保存されたパスワードまで、手の届く範囲に入ります。

ハーネスの権限の設定で「sudo は禁止」と書いても、名前で止めているだけなので、python -c や sh -c を通せば素通りします。だから、そもそも権限の無いユーザーで動かします。

  • ログインできないユーザーにする(シェルは nologin)。
  • sudo、docker、lxd などのグループに入れない。lxd や docker は、入っているだけで root と同じ力を持てるグループです。
  • UID を固定する(③で、このユーザーの通信だけを絞るため)。

2.3 ② systemd で、書ける場所と使える機能を絞る

OpenCode を systemd のサービスとして動かし、次の設定で囲います(抜粋)。

[Service]
User=oc-agent
Group=oc-agent
# sudo などで権限を上げられない
NoNewPrivileges=yes
# 特別な権限を 1 つも持たない
CapabilityBoundingSet=
# システムの場所は読み取りだけ
ProtectSystem=strict
# 人のユーザーのホームは見えない
ProtectHome=yes
PrivateTmp=yes
# 名前空間を作る抜け道を止める
RestrictNamespaces=yes
ReadWritePaths=/srv/agent/work /var/lib/oc-agent
# ③とは別の層で、通信を絞る
IPAddressDeny=any
IPAddressAllow=<推論サーバ> <承認した端末>
# 止めるときは子プロセスごと
KillMode=control-group

書けるのは、作業フォルダと、OpenCode 自身のデータの置き場所だけです。人のユーザーのホームは、見えもしません。

IPAddressDeny と IPAddressAllow は、systemd が子プロセスにまでかける通信の制限です。③の nftables とは別の仕組みなので、どちらか一方に抜けがあっても、もう一方で止まります。

2.4 ③ nftables で、通信を推論サーバだけに絞る

いちばん大事な層です。専用ユーザーの通信を、既定で止めます。

table inet agent_egress {
  chain output {
    type filter hook output priority 0; policy accept;
    meta skuid "oc-agent" jump agent_out
  }
  chain agent_out {
    ip daddr <推論サーバ> tcp dport { <推論のポート> } accept
    # Web UI への応答は、許可した端末からの受信の返事だけ通す
    # それ以外は、数えて、間引いて記録して、止める
    counter
    limit rate 6/minute log prefix "AGENT-EGRESS-DROP "
    drop
  }
}

気をつけた点が 4 つあります。

  • 「既に張った接続は全部通す」という書き方をしない。 よくある ct state established accept を先頭に置くと、守りが緩みます。Web UI の返事は、承認した端末からの受信に対する返事だけを通します。
  • DNS も止める。 2026 年 9 月の OpenAI の件は、DNS の問い合わせに文字を埋め込んで外とやり取りしたものでした。インターネットに出ない AI エージェントには、名前解決そのものが要りません。IPv6、UDP 全般も止めます。
  • 数える・記録する・止めるを別々に入れる。 記録は間引かないと記録で溢れますが、間引いたせいで止める処理まで抜けないよう、止める処理は必ず通るようにします。
  • 止めた通信の記録は、AI エージェントから読めない・消せない場所に置く。 記録は 30 日残します。

Web UI も、承認した端末からだけ受け付け、パスワードは必ず残します。IP で絞ったからパスワードを外す、ということはしません。

2.5 ④ ハーネスの設定を、ハーネスに触らせない

OpenCode の設定ファイルは root の持ち物にし、OpenCode は読むだけにします。そのうえで、環境変数 OPENCODE_DISABLE_PROJECT_CONFIG=1 で、作業フォルダに置かれた設定(プロジェクト側の設定やプラグイン)を読み込ませないようにします。作業フォルダは OpenCode が書ける場所なので、ここにある設定を読むと、自分で自分の守りを書き換えられてしまうからです。

Web の取得や検索のツールは、無効にしていません。調べ物ができなくなるからです。この形ではインターネットへの通信が③で止まっているので、ツールがあっても外へは出られません。使おうとすれば、止めた通信として記録に残ります。インターネットに出る必要があるときの扱いは、後編で説明します。

2.6 ⑤ 毎回読む決まりを、消えない場所に置く

1.3 で書いたとおり、長い作業でコンテクストの上限に近づくと、ハーネスは古い会話を要約して詰め直します。このとき、最初に伝えた禁止事項がこぼれ落ちることがあります。

そこで、AI エージェントに読ませる文書を 2 つの層に分けました。

  • 第 1 層は、ハーネスの設定で毎回読み込ませる、短い決まりです。触ってよい範囲、禁止事項、調べ物の言葉に秘密を入れないこと、などだけを書きます。手順や経緯は書きません。設定から読み込まれるので、会話が要約されても、次の要求のたびに必ず入ります。
  • 第 2 層は、作業ごとに選んで読む文書です。地図と作業の手順は管理側の持ち物で、AI エージェントは読むだけです。AI エージェントが書けるのは、今の状態を書く 1 ファイルだけにしました。

最初に読む量は、推論サーバが返す入力のトークン数で実測しました。第 1 層が 302 トークン、選んだ文書を合わせて 689 トークンです。ハーネスの標準の指示とツールの定義を含めた最初の入力は 5,713 トークンでした。コンテクスト長 262,144 トークンの LLM モデルに対して、守りの決まりのために使う量はわずかです。

3. 推論サーバの側も確かめる

2 章で、AI エージェントのホストからは推論サーバにしか通信できないようにしました。ただ、AI エージェントに読ませた社内のファイルの中身は、プロンプトとして推論サーバに流れます。推論サーバの側が外へ何かを送っていたら、ホストをいくら固めても意味がありません。

そこで、推論サーバについて次の 3 つを調べました。

  1. 推論サーバが、外へ通信していないか
  2. 送ったプロンプトや応答が、どこに、いつまで残るか
  3. AI エージェントがつないでいる先の、さらに先がどこか

ラボの推論の構成と、調べて分かったことをまとめると、次の図になります。

推論サーバ A は GMKtec EVO-X2(AMD Ryzen AI Max+ 395)で、Flash-Next を Strata という推論エンジンで動かしています。推論サーバ B は NVIDIA DGX Spark で、別の LLM モデルを vLLM で動かしています。AI エージェントから見ると、どちらも推論サーバ A の 2 つのポートです。

3.1 外への通信を実測する

まず、推論サーバが外へ通信していないかを、実際の通信で確かめました。

  • ss で接続の一覧を 1 秒ごとに取り、ローカルのネットワーク以外へ向かう接続と、そのプロセスを記録する。
  • 1 秒ごとの記録では、1 秒より短い通信を取りこぼします。そこで推論サーバ A では、パケットのヘッダだけを記録して補いました(中身は記録しません)。

結果は次のとおりです。

対象 外への通信
Strata(推論サーバ A) 0 件。パケットの記録でも、インターネット宛ては 0 件
中継(推論サーバ A) 0 件。相手は推論サーバ B だけ
vLLM(推論サーバ B) stats.vllm.ai へ常につながっていた(利用状況の送信)
Open WebUI(推論サーバ A) CDN への接続が残っていた(目的は特定できず。AI エージェントの経路ではない)

AI エージェントの経路で見つかったのは、vLLM の利用状況の送信でした。

3.2 vLLM の利用状況の送信を止める

vLLM は、既定で利用状況のレポートを stats.vllm.ai へ送ります。起動時に環境の情報(GPU の種類と数、LLM モデルのアーキテクチャ、vLLM の版など)を送り、その後は 10 分ごとに生存の知らせを送ります。コンテナの中に残っていた送信の記録を、項目の名前で確かめたところ、プロンプトや応答の項目はありませんでした。

中身が送られていなくても、止めることにしました。理由は 2 つあります。

  • 頼んでいない外への通信は、すべて許可制にするという方針だから。
  • 外への通信が常にあると、本当に怪しい通信が紛れて見つけにくくなるから。

止め方は、環境変数です。使っている版(0.19.1rc1)のソースで、どの変数が効くかを確かめてから入れました。

docker run ... \
  -e VLLM_NO_USAGE_STATS=1 -e DO_NOT_TRACK=1 \
  --log-driver json-file --log-opt max-size=50m --log-opt max-file=3 \
  ...

入れたあとは、送信の記録のファイルが作られないこと、stats.vllm.ai への接続が 10 分の周期をまたいで 0 回であることを確かめました。

ここで大事なのは、vLLM を起動する経路をすべて直すことです。コンテナに設定を入れても、コンテナを作り直すスクリプトが元のままなら、次に作り直したときに送信が戻ってきます。ラボでは、次の 3 か所に同じ設定を入れました。

  • 今動いているコンテナ
  • コンテナを作り直す起動スクリプト(LLM モデルを切り替えるスクリプトからも呼ばれる)
  • 同じ vLLM を起動する、別の systemd のサービス(ふだんは止まっている予備の経路)

「ふだんは止まっている経路」は見落としがちです。起動の経路を一覧にしてから直すのが確実です。

3.3 ログに、何が・どれだけ残るか

次に、送った内容がどこに残るかを調べました。調べたのは場所、中身の種類、期間、大きさ、読める人です。ログの中身そのものは読まず、行の種類ごとの数と項目の名前だけを見ました。

  • Strata:ログに残るのは、トークン数、速度、時間などの数字だけで、プロンプトや応答の本文は残りません。ソースでも確かめました。ただし、本文をログに出したり、会話をディスクに書き出したりする設定(デバッグ用の設定、直近の要求を保持する機能など)もあるので、それらを付けないことを決まりにしました。
  • vLLM:docker のログに、アクセスの記録(送信元の IP、パス、結果)が残っていました。本文はありません。ただ、ログに上限が無く、増え続ける設定でした。3.2 の --log-opt で、50MB × 3 ファイルの上限を付けました。要求の中身をログに出すオプション(--enable-log-requests など)は付けていません。
  • Open WebUI:人が使う画面のアプリで、会話そのものをデータベースに保存します。保存の期限は無く、消さない限り残ります。AI エージェントの経路ではありませんが、同じ推論サーバに、本文が残る場所があることは覚えておく必要があります。

推論エンジンが本文を残さなくても、その前後のアプリが残すことがあります。「どこに残るか」は、推論エンジンだけでなく、同じサーバで動いているものまで含めて見ます。

3.4 Strata は --offline で経路も閉じる

Strata は、実測で外への通信が 0 件でした。それでも、--offline を付けて起動し直しました。

ソースを読むと、Strata が外へ出る経路は 2 つだけでした。

  • 要求の中に http(s) の画像の URL があったときに、その画像を取りに行く。
  • MCP サーバ(外部のツール)を設定したときに、それを起動する。

--offline を付けると、画像の URL の取得を断り、MCP のツールを起動せず、子プロセスにも Hugging Face などへ取りに行かせない設定を渡します。MCP を設定したまま --offline で起動しようとすると、起動そのものを断ります。

今は使っていない経路ですが、「使っていない」と「使えない」は違います。AI エージェントが画像の URL を含む要求を作れば、推論サーバがその URL へ取りに行きます。使っていない経路は、閉じておきます。

3.5 つないでいる先の、さらに先を確かめる

3 つ目は、図の「ポート 2」です。AI エージェントから見ると、推論サーバ A のポートの 1 つですが、実際は推論サーバ B への中継でした。中継はログも残さず、届いたものをそのまま転送します。

推論サーバ A だけを調べていたら、外への通信は 0 件で終わっていました。利用状況の送信は、その先の推論サーバ B で起きていたからです。

AI エージェントがつないでいる先が、本当に LLM モデルを動かしているサーバなのか、中継なのかは、AI エージェントの設定を見ても分かりません。つないでいる先から、実際に LLM モデルが動いているサーバまで、経路をたどって確かめます。

3.6 本番のデータを流す前に確かめること

3 章をまとめると、推論サーバの側で確かめることは次のとおりです。

  • AI エージェントがつないでいる先から、LLM モデルが動いているサーバまでの経路をたどったか(中継が無いか)
  • 経路上のすべてのサーバで、外への通信を実測したか(1 秒ごとの記録と、パケットのヘッダの記録)
  • 推論エンジンの利用状況の送信を止めたか(vLLM は既定でオン)
  • 推論エンジンを起動する経路を一覧にし、すべてに同じ設定を入れたか(起動スクリプト、予備のサービスも)
  • 本文がどこに残るかを調べたか(推論エンジン、ログ、同じサーバのほかのアプリ)
  • 本文をログに出す設定・オプションを付けていないか
  • ログに上限と期間を付けたか
  • 使っていない外への経路(画像の URL の取得、外部ツールなど)を閉じたか

4. 入れ方と、受け入れの試験

守りの中身が決まっても、動いている AI エージェントに入れるときに止めてしまったり、締め出されたりしては困ります。この章では、ラボで守りを入れたときの進め方をまとめます。

4.1 段階に分けて、小さく始める

最初は、全部の守りをそろえてから切り替える計画を立てました。ところが、プロキシ、通知、外部ストレージの扱いまで一度に入れようとすると、計画が重くなりすぎて、なかなか入れられません。

そこで、段階に分けました。

段 入れるもの
第 1 段 専用ユーザー、systemd、nftables(外へは推論サーバだけ)、止めた通信の記録
第 2 段以降 通知(Slack など)、プロキシ、外部ストレージ、など必要になったもの

第 1 段だけで、2 章の守りの本体はそろいます。インターネットに出ない AI エージェントなら、ほとんどこれで足ります。

もう 1 つ決めたのは、長い事前の観測を待たないことです。「どんな通信が必要か、しばらく観測してから決める」としていると、その間は守りが無いままです。先に「止めて記録する」形で入れ、止まった通信のうち本当に必要なものだけを、申請して足します。

進め方の全体は、次の図のとおりです。

4.2 準備:ハーネスを起動しないで、全部を通しておく

準備の段階では、OpenCode を起動しません。ユーザーを作るスクリプト、unit、nftables のルール、記録の設定などを、1 本ずつ単独で通しておきます。

ここで効いたのは、次の 2 つです。

  • スクリプトの中のコマンドは、フルパスで書く。 systemd の中では PATH が短く、手で試したときには動いたコマンドが見つからないことがあります。スクリプトの先頭で、使うコマンドが実在するかを確かめておきます。
  • nftables を触る前に、締め出されない備えをする。 ルールを間違えると、自分の SSH まで切れることがあります。一定時間で自動的に元に戻す仕掛けを用意し、コンソールから入れる状態にしてから入れます。

4.3 切り替え:起動は 1 回、戻し方は「止めるだけ」

準備が済んだら、新しいサービスを 1 回だけ起動します。起動したあとの試験で失敗したら、そこで止めて、原因を調べてから出直します。起動を何度もやり直すと、何が原因で何が変わったのかが分からなくなるからです。

戻し方は、新しいサービスを止めるだけで済む形にしました。

  • 古いハーネス(人のユーザーで動いていたもの)は止め、自動では起動し直さないようにしておく。新旧を同時に動かすと、同じポートとデータベースを取り合います。
  • 古い会話の履歴は、少なければ移しません。古いデータベースは読み取りだけで残しておきます。

4.4 受け入れの試験

切り替えたら、守りが本当に効いているかを試します。試験は、OpenCode と同じユーザー、同じ制限の下で行います。人のユーザーで試しても、守りの試験にはならないからです。

試すこと 合格の条件
どのユーザーで動いているか 専用ユーザーで動いている
書ける場所 作業フォルダと、OpenCode のデータの置き場所だけに書ける。それ以外は拒否される
権限の昇格 sudo、nft、名前空間の作成、管理用のソケットへの接続が、すべて拒否される
インターネットへの通信 止まる。止めた通信の記録に出て、数も増えている
DNS 名前解決ができない
推論サーバへの通信 推論が通る。会話のタイトルの生成(ハーネスが裏で別に送る要求)も通る
Web UI 承認した端末からだけ入れる。パスワードを求められる
最初に読む文書 決まりの文書が読み込まれている。量(トークン数)が想定どおり
会話の要約 手動で要約させて最後まで終わり、要約のあとも会話が続く

「インターネットへの通信が止まる」だけでなく、止めた記録に出ることまで確かめます。記録に出なければ、本番で止まったときにも気づけません。

最後の「会話の要約」は、守りとは関係なさそうに見えますが、2.6 の決まりの文書が要約のあとも残ることを確かめる試験です。ラボでは、コンテクスト長 262,144 トークンに対して、86% で自動の要約が始まるように設定しています(compaction.buffer = 36700)。この値は、推論サーバが実際に返すコンテクスト長で確かめてから決めました。

4.5 人の確認点

試験に合格したら、最後に人が確かめます。

  • 承認した端末の Web UI から、ログインできる
  • 新しいセッションを作り、ふつうに会話できる
  • 画面を読み込み直しても、履歴が残っている

試験は AI エージェントや作業の担当に任せても、最後に人が自分の手で使えることを確かめて、初めて完了とします。

ここで大事なのが、パスワードを切り替える前に、人の手に確実に渡すことです。値はチャットやログには書かず、ホストの上で 1 回だけ表示するなど、決まった方法で受け取ります。守りを固めた結果、本人が入れなくなっては意味がありません。

4.6 導入の作業も、AI エージェントにやらせた

ラボでは、この導入の作業そのものも、AI エージェント(Claude Code や Codex)にやらせました。人は方針を決め、確認点で確かめる役です。

導入の担当の AI エージェントにも、守ってもらう決まりがあります。

  • 失敗したら、やり方を変えて試し続けない。止めて報告する。
  • 本番のサービス、ホスト全体のファイアウォール、元のデータに触れる変更が要るときは、止めて人に聞く。
  • pkill -f や pgrep -f を使わない。コマンドの文字列で一致させると、自分のシェルや別のサービスまで巻き込むことがあるので、PID を確かめて止める。
  • npm/npx を使わない(ラボ共通の決まり。ツールは公式のバイナリやインストーラで入れる)。
  • パスワードや Webhook の URL を、画面、ログ、共有のフォルダに出さない。

1 章で書いたとおり、どの LLM モデルでもやらかす可能性はあります。導入の担当の AI エージェントも例外ではありません。だから、守りを入れる作業でも「止まって聞く」場面を決めておき、人の確認点を必ず通します。

5. まとめと、GitHub での公開

5.1 まとめ

前編で伝えたかったことは、次の 4 つです。

  1. LLM モデル単体では、外へ出られない。 問題が起きるのは、ハーネスと組み合わせて、LLM モデルに道具を持たせたとき。
  2. どの LLM モデルでも、やらかす可能性はある。 特定の LLM モデルの問題ではなく、優秀な LLM モデルでも調子が落ちるときがある。禁止事項は、指示で伝えるだけでは守られない。
  3. だから、守りは LLM モデルの外に置く。 専用ユーザー、systemd、nftables で、ハーネスが動く場所から外へ出られなくする。決まりの文書は、要約されても消えない場所に置く。
  4. 推論サーバの側と、その先まで確かめる。 推論エンジンの利用状況の送信、ログに残るもの、中継の先。

インターネットに出ない AI エージェントなら、守りの本体は第 1 段(2 章の ①〜③)だけでそろいます。大がかりな仕組みは要りません。

この守りは、LLM モデルを疑うためのものではありません。Qwen3.8-Flash-Next のような良い LLM モデルを、外に出せないデータの作業でも安心して使い続けるためのものです。

5.2 GitHub で公開しているもの

この記事で紹介した設定は、GitHub で公開しています(英語版の記事も置いています)。値はすべて例なので、自分の環境に合わせて書き換えて使ってください。
https://github.com/nabe2030/local-llm-agent-egress-guard

公開する予定のもの 中身 記事の章
設定のひな形 専用ユーザーを作るスクリプト、systemd の unit、nftables のルール、ハーネスの設定の例 2 章
受け入れの試験のスクリプト 4.4 の表の項目を、OpenCode と同じユーザー・同じ制限の下で試す 4 章
チェックリストと運用の決まり 3.6 のチェックリストと、ラボの運用の決まりを公開用にまとめたもの 3・4 章

5.3 後編の予告

後編では、インターネットに出る必要がある AI エージェントの守りを扱います。ブラウザを操作して、自分の Facebook の書き込みを管理する AI エージェントです。

  • 通信はすべてプロキシに通し、どこと通信したかを全部記録する
  • ダウンロードは止めず、アップロードは「送ってよい先」だけに絞る
  • 送ってよい先は、AI エージェントの届かない管理画面で、人が期限つきで許可する
  • AI エージェントが操作するブラウザも、同じ守りの中に入れる

前編の守りを土台に、「出ていいけれど、勝手には送れない」形をどう作ったかを紹介します。

おわりに — 当ラボについて

筆者は NPO法人 IoTメディアラボラトリーで、IoT 研究とあわせて AI 分野の検証を担当しています。東京大学・本郷キャンパス 大学院工学系研究科の同名のラボで行っていた IoT 研究が独立し、研究機関として活動を続けている組織です。
私がやっていることは、本記事のようなローカル LLM の実機検証が中心です。新しいモデルが出るたびに手元のハードで動かし、速度・日本語知識・コーディング・マルチモーダルの 4 軸で測り、動かなければ原因を突き止めるところまでやります。ベンダーの公称値ではなく、実際に自分の機械で何が起きるかを知りたいからです。
そのぶん、計算資源はいくらあっても足りません。もっと GPU が欲しい(笑)。機材の提供、検証のご依頼、共同研究など、お声がけいただけると喜びます。


参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?