はじめに
Claude Code を Docker のサンドボックスに閉じ込めつつ、ADE(Agentic Development Environment)の Orca で worktree ごとに並列開発する環境を作っていました。
サンドボックスの構成はおおむね次のとおりです。
- Claude Code のコンテナは
internal: trueの Docker ネットワークにだけ参加させ、外部へ直接出られないようにする - 外部への通信は、同じネットワークにいる中継プロキシ(px)経由だけにして、接続先をログに残す
-
cap_drop: ALL・no-new-privileges・設定ファイルの read-only マウントで、コンテナ内の権限を絞る
ソースを Windows 側に置いてコンテナにマウントすると、WSL2 経由のファイル共有(9p)がボトルネックになり、Vite の dev サーバーがかなり遅くなります。そこで「ソースごとコンテナ(Linux 側)に置き、Orca の SSH 接続機能でコンテナに入れば速くなるのでは」と考えて検証しました。
Orca の機能面は問題なく動いたのですが、その過程で SSH 接続先のコンテナの一般ユーザーから、Orca を踏み台にして Windows 側の Orca を操作できることがわかりました。この記事はその調査結果のまとめです。
検証環境は次のとおりです。
- Windows 11 + Docker Desktop(WSL2 バックエンド)
- Orca 1.4.210
- ソースコードの確認は stablyai/orca の 2026-09-29 時点の main ブランチ
問題
試した構成
Windows
└ Orca ──SSH──→ 127.0.0.1:2222 ──→ コンテナ(sshd)
├ リポジトリ・worktree(Linux 側)
├ Orca relay(Orca が自動で配置)
└ エージェント(Claude Code など)
Orca の SSH 接続では、接続先に Orca relay という Node.js 製の補助プログラムが自動で配置され、worktree の作成、ターミナル、ファイル操作、git などを接続先で実行します。エディタや差分表示は手元の Orca に残ります。
PoC 用のコンテナ(sshd・git・Node.js・make・g++ 入り)に接続したところ、worktree 作成、ターミナル、差分表示、内部ブラウザでのコンテナ内 localhost の表示まで、すべて動きました。
接続先に置かれる orca コマンド
ここで気になったのが、Orca relay を誰の権限で動かすかです。Orca は、SSH でログインしたユーザーで relay もエージェントも起動します。つまり、エージェント(Claude Code)は relay と同じユーザーで動き、relay のファイルやソケットに触れられます。
Orca のリポジトリにある設計文書(docs/reference/ssh-execution-boundary.md)を読むと、SSH 接続先には ~/.orca-relay/bin/orca という orca コマンドの中継役が置かれ、relay のソケットを通じて手元(クライアント)の Orca 本体に命令を送り返すと説明されています。文書では「コントロールプレーンだけがクライアント側にある」という位置づけです。
さらにソースコードを追うと、次のことがわかりました。
- 中継役は、接続を確立する処理(
src/main/ssh/ssh-relay-session.ts)の中で、特に条件なく用意される - 接続先から届いた命令は、手元の Orca に同梱されている CLI にそのまま渡して実行される(
src/main/ssh/ssh-remote-cli-host-passthrough.ts)。除外されているのは、対話的に動くため中継できない一部のコマンドだけ - CLI には、ターミナルの作成・入力の送信、worktree、ファイル、ブラウザ操作、デスクトップ操作(
computer)などのコマンドがある
実際に確認したこと
Orca から PoC コンテナに接続した状態で、コンテナ内の一般ユーザーとして中継役を実行してみました(ID などは一部伏せています)。
まずは状態の確認です。
$ docker exec -u dev orca-ssh-poc bash -lc '~/.orca-relay/bin/orca status'
[relay-connect] Handshake OK at version=0.1.0+xxxxxxxxxxxx
appRunning: true
pid: 18968
desktopWindowStatus: available
runtimeState: ready
runtimeReachable: true
runtimeConnectionState: connected
...
pid はコンテナ内のものではなく、Windows 上で動いている Orca のプロセス番号です。コンテナ内から、特別な権限なしに Windows 側の Orca と通信できています。
次に、ターミナルの一覧を取得しました(一覧を表示するだけのコマンドです)。
$ docker exec -u dev orca-ssh-poc bash -lc '~/.orca-relay/bin/orca terminal list --json'
{
"ok": true,
"result": {
"terminals": [
{
"worktreePath": "/home/dev/repo",
"executionHostId": "ssh:ssh-xxxxxxxx",
"writable": true
},
{
"worktreePath": "G:/.../wt/<project>/<worktree>",
"executionHostId": "local",
"writable": true
}
],
"hostScope": {
"hostIds": ["local", "ssh:ssh-xxxxxxxx"],
"omittedHostIds": []
}
}
}
(主要な項目だけを抜粋しています)
2件目は Windows 側(local)で動いているターミナルで、writable: true(入力を送れる)になっています。hostScope にも local が含まれていて、コンテナからの問い合わせに対して Windows 側を対象から外していません。
何が問題か
まとめると、次の3つが重なっています。
- Orca が SSH 接続先に「手元の Orca を操作する入口」を自動で置く
- その入口は、SSH でログインしたユーザーなら誰でも使え、Orca はエージェントもそのユーザーで起動する
- 手元の Orca は、接続先から届いた命令の対象を、その接続先の範囲に制限していない
そのため、接続先で動くエージェントは、Orca を踏み台にして Windows 側のターミナルに入力を送る=Windows 上でコマンドを実行することができる状態にあると考えられます。コンテナ側にかけたネットワーク分離・プロキシ経由の強制・特権の削除はコンテナの中を縛るもので、Orca 経由で Windows 側に命令を渡す経路は縛れません。
なお、今回は安全のため、Windows 側のターミナルに実際に入力を送る試験はしていません。上の結論は、writable: true の結果とソースコードの読み取りに基づくものです。
また、これは Orca の不具合というより設計どおりの動作です。Orca は SSH 接続先を「利用者が信頼して操作する実行先」として扱っており、自分で管理するリモートマシンで使う分には、この仕組みは便利な機能です。問題になるのは、今回のように接続先で動くエージェントを信頼せず、閉じ込めたい場合です。
relay 用とエージェント用でユーザーを分ける案も考えましたが、Orca はエージェントを relay と同じユーザーで起動するため、この構成のままでは分けられません。
解決方法
「エージェントをサンドボックスに閉じ込めたい」という目的では、Orca の SSH 接続先でエージェントを動かす方式はやめることにしました。
代わりに、次の方式に戻しています。
- Orca は Windows 上で普通に使う(worktree は Windows 側に作る)
- Orca の Claude Code の起動コマンドを自作の起動スクリプトに差し替え、worktree ごとにコンテナを起動して、その中で Claude Code を動かす
- コンテナには、その worktree と必要な設定ファイルだけをマウントし、Orca のソケットや
orcaコマンドは渡さない
この方式なら、コンテナ内のエージェントから Orca 本体に届く経路がありません。
遅さの問題は、読み込みの大半を占める node_modules だけを worktree ごとの Docker volume(Linux 側)に置くことで、方式を変えずに改善する予定です。
どうしても SSH 接続を使う場合は、次の点に注意してください。
- SSH 接続先で動くプログラム(エージェントを含む)は、手元の Orca を操作できる前提で扱う
- 信頼できないコードやエージェントを動かす接続先には、Orca から接続しない
- 使い終わったら接続を切断する(切断中は中継役がクライアントに届かなくなります)
おわりに
Docker 側のサンドボックスをどれだけ固めても、手元のツールが接続先に「手元を操作する入口」を用意していれば、そこから外に出られてしまう、という学びになりました。
今回は、次の順番で確かめたのがよかったと感じています。
- Orca の機能が動くかを、サンドボックスと切り離した PoC で確認する
- 接続先で動くものの権限(誰として動くか)を洗い出す
- 設計文書とソースコードで仕組みを確認する
- 一覧を見るだけの安全なコマンドで、実際に届くかを確かめる
エージェントをリモート実行できるツールを、サンドボックスと組み合わせるときの参考になれば幸いです。
参考
- Orca: SSH worktrees https://www.onorca.dev/docs/ssh
- Orca: Settings reference https://www.onorca.dev/docs/settings
- stablyai/orca(GitHub) https://github.com/stablyai/orca
- SSH Execution Boundary(Orca 設計文書) https://github.com/stablyai/orca/blob/main/docs/reference/ssh-execution-boundary.md
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼
https://projisou.jp
